---
name: patchcord-subscribe
description: >
  Start the Patchcord background listener so new messages wake this agent.
  Run this when the user asks to start Patchcord push delivery.
---

jcode has no Monitor tool. A background task here wakes the agent on a
STALL — no new output for N seconds — not per line the way Claude Code's
Monitor does. Following Claude Code's own subscribe instructions verbatim on
jcode produces a listener that connects, receives, and never wakes you: the
pipe fills with messages you are never told about.

This skill exists to run correctly on jcode specifically, using
`patchcord subscribe --stall-signal`, a mode built for exactly this gap:
while idle it writes a `HEARTBEAT:` line often enough that the pipe never
looks silent, and on a real message it goes quiet on purpose for long enough
that jcode's own stall detector fires. This is this repo's global skill
directory (`~/.jcode/skills/`), shared by every jcode project on this
machine — nothing below may name a specific namespace, agent, or project
path.

# Start

1. **Drain the inbox first.** Call the Patchcord `inbox` MCP tool. Process
   every pending message (see the patchcord-inbox skill) before starting the
   listener — a backlog can accumulate while no listener was running, and
   the listener does not replay what happened before it started.

2. **Prefer REAL push if the harness API bridge is running.**

   jcode has a socket API with `soft_interrupt` - "inject a message at the next
   safe point without cancelling" - which is what a notification is. That is an
   immediate wake, not a timer trip. It needs `jcode api-bridge` running; it is
   opt-in and not on by default.

   Add `--jcode` to the command below. It costs nothing when the bridge is
   absent: subscribe reports that in one line, names the command that starts it,
   and keeps the stall wake as the floor. **Always pass BOTH** - `--jcode` is
   the fast path, `--stall-signal` is what works when the bridge is not there.

   ```
   Bash(
     command: "patchcord subscribe --jcode --stall-signal",
     run_in_background: true,
     stall_wake_seconds: 30
   )
   ```

   With the bridge running you are woken within a second of a message arriving.
   Without it, in 30 to 40 seconds by the stall path. Tell the user which one
   they got - subscribe says so in its output - and never promise the first
   while running the second.

3. **Spawn the listener with bash, in the background, with a stall wake:**

   ```
   Bash(
     command: "patchcord subscribe --stall-signal",
     run_in_background: true,
     stall_wake_seconds: 30
   )
   ```

   `stall_wake_seconds: 30` IS READ FROM JCODE'S SOURCE, not chosen. In
   `crates/jcode-base/src/background.rs`:

   ```
   pub const MIN_STALL_WAKE_SECONDS: u64 = 30;
   let stall_wake_seconds = stall_wake_seconds.max(Self::MIN_STALL_WAKE_SECONDS);
   const STALL_POLL_INTERVAL: Duration = Duration::from_secs(5);
   ```

   Anything below 30 is SILENTLY RAISED to 30 - the tool answers "armed"
   either way and never says it substituted a number. An earlier version of
   this skill said 15 and forbade 30; that was exactly backwards and it made
   the wake unreachable, because `--stall-signal`'s 15 s quiet window could
   never elapse a 30 s stall. The keepalives resumed, the output file grew,
   the watchdog reset, and no message ever woke anyone.

   The defaults now match: quiet window 40 s against a 30 s stall, which
   clears the window plus jcode's own 5 s sampling interval. Pass no inline
   value and they are correct. If you do pass a triple
   (`keepaliveMs:quietMs:stallMs`), `stall_wake_seconds` must equal your
   `stallMs`, and `quietMs` must be at least `stallMs + 5000` - subscribe
   refuses the pair otherwise rather than running silently wrong.

   The wake lands about 30 to 40 seconds after the message. That is the cost
   of a wake-on-silence primitive, and it is the honest number to give the
   user.

   **DO NOT PIPE THIS THROUGH `grep`.** Every other subscribe command in this
   plugin filters stdout down to `^PATCHCORD:` lines, and doing that here
   breaks the entire mechanism: your harness measures the stall on what comes
   OUT of the pipeline, and `grep` removes exactly the `HEARTBEAT:` keepalives
   that are supposed to keep the pipe looking alive. Filtered, the stream is
   silent all the time, so the wake fires on nothing every 15 s and the quiet
   window after a real message is indistinguishable from idle. That is the
   noisy, useless wake loop `--stall-signal` exists to replace.

   So the raw stream is what the background task must see. It carries
   `HEARTBEAT: <timestamp>` every 5 s and `PATCHCORD: ...` on a real message.
   Ignore the heartbeats when you read the output; they are for the stall
   detector, not for you.

4. **Tell the user one line:** "Patchcord listener active — I'll check
   regularly for new messages." Do not say "as messages arrive" — the
   mechanism approximates that, it does not guarantee it, and promising more
   than jcode can deliver is worse than being accurate.

# When the background task wakes you

A wake means the pipe went quiet for `stall_wake_seconds` — with
`--stall-signal` running, that quiet is now DESIGNED to correlate with a real
message, but two other things also produce exactly the same silence, and you
cannot tell which one happened from the wake alone:

1. **Check the inbox every time.** Call the Patchcord `inbox` MCP tool. If it
   has pending messages, handle them (see the patchcord-inbox skill). If it
   is empty, say nothing to the user and do not report the wake as an event —
   an empty check is not news.
2. **If the inbox is empty, check whether the listener is still running**
   before assuming this was a harmless false positive. A dead listener also
   produces silence — that is a feature (it is how you notice), but only if
   you actually look. If the background task has exited, restart it with the
   command in Start, step 2.

   **If the restart is refused with `already running (pid N)`, add
   `--replace`.** That message means a listener process is alive while your
   background task is not tracking it — which is the worst state available
   here, because it receives messages and can no longer wake you, and it
   holds the pidfile that refuses the correct respawn. It happens when a
   cancel is rejected as stale and the process outlives the task that owned
   it.

   ```
   Bash(
     command: "patchcord subscribe --replace --stall-signal",
     run_in_background: true,
     stall_wake_seconds: 30
   )
   ```

   `--replace` terminates the pidfile's holder and takes over. It signals only
   a pid read from patchcord's own pidfile, and only after confirming that pid
   is a patchcord listener — a reused pid belonging to something else is left
   alone and the file is treated as stale. This is the ONLY sanctioned way to
   remove a running listener. Do not reach for `kill`, `pkill`, or the pidfile
   yourself.
3. **Never read the last `PATCHCORD:` line in the task output as news.** It
   is scrollback — output already displayed. It may be the same line you
   already handled. The inbox call in step 1 is the source of truth; a
   `PATCHCORD:` line without a corresponding inbox check is not confirmation
   of anything.

# Stopping

Tell the user one of:

- End this session. The listener stops with it.
- `patchcord subscribe --stop`, run from the project directory. It finds the
  right agent on its own, so this skill never has to know the namespace or
  the agent name.

# If the background task ends on its own

Read its output. Scan for one of:

- `no patchcord config found` — not run from a project with `.jcode/mcp.json`
  (or a parent project directory).
- `ticket: token rejected (HTTP 401|403)` — the bearer token is invalid or
  expired.
- `already running (pid N)` (exit 2) — another listener is active for this
  agent. Do not respawn it unchanged, and do not kill it by hand: restart with
  `--replace` as described above.
- `subscribe: fatal: ...` — report the fatal line verbatim.

If none of those appear, it likely ended with the session or because its
output consumer closed. Restart it only when the user asks to resume Patchcord
push delivery.

**Forbidden on failure:** no hand-rolled polling loop in place of this
mechanism, no pidfile editing, no re-arm loop after every wake — the
background task under `run_in_background` keeps running across wakes; you
are only checking in, not restarting it, unless step 2 above found it dead.
