Never give the connector's monitor a timeout

Merged
JE
2d ago
+15-15

Context

Kitchen card: a connector session armed its request monitor with a 30-minute timeout. At each expiry the model woke, re-armed it, and said another system "is running in the background. Nothing is needed from you." That was about 48 wasted turns a day, and nothing behind the status line had checked that system, which took no work for 15 hours meanwhile.

The skill already said persistent: true. What pushed the session to a timeout is the Monitor tool itself: in current Claude Code builds (2.1.322 here) a feature gate, on by default, drops persistent from the Monitor schema, makes timeout_ms the only knob (default 5 minutes, capped at 30), and the tool's description tells the model to "set timeout_ms to the maximum and re-arm on each expiry". 30 minutes is exactly that cap. So the skill has to say what to do both where persistent exists and where it doesn't.

Change

skills/basecamp-connect/SKILL.md only:

  • What this flow needs now names a Monitor tool that takes persistent, beside Claude Code. Without one, the session goes to Without this session (--orchestrator) before starting anything, which needs no monitor and records each event once.
  • Step 1 arms the monitor with persistent: true and never a timeout_ms, in the arming sentence itself. If it expires, re-arm it once, persistent; if that one expires too (a remote session turns persistent into a 30-minute cap), tell the person. An earlier head switched to the orchestrator mode mid-run instead, which needed a stop, a wait for the lock and a drain of requests already marked handed off; the switch went rather than gaining a third patch.
  • Without this session says why a monitor that always expires can't hold the watch: each re-arm replays the whole output file (tail -n +1), and a compacted session can't tell requests it handled from new ones.
  • Keep the terminal quiet now says the session speaks for the connector alone and never says whether anything else here is working.
  • The event_id skip rule names a re-armed monitor's replay beside a restart.

To keep the per-request steps inside the 14,000-character compaction budget (TestTheConnectSkillKeepsEachRequestsStepsInsideTheCompactionCut), the opening paragraph goes: the "Three pieces" list restates it point for point. The per-request part now ends at 13,949 characters, against 13,980 before: 51 characters of headroom, so the next addition here will have to trim something.

For a decision: the Monitor in Claude Code 2.1.322 has no persistent by default (see Context), so on today's builds this sends most sessions to the orchestrator mode rather than a session watch. The alternative is a session that re-arms every 30 minutes with a cursor file the monitor writes so a replay resumes after the last line passed: about 48 turns a day, and a persisted marker to keep right. That is the trade-off to settle; this PR takes the first.

Verification

  • bin/ci green on b9fab5d (run on a 32-core host): unit and e2e tests, lint, CLI surface, and skill drift for all four skill files. On 76fd277, which changes only SKILL.md wording, the connector skill tests and make check-skill-drift pass.
  • TestTheConnectSkillKeepsEachRequestsStepsInsideTheCompactionCut passes at 13,949 characters; the first head's additions without dropping the opening paragraph measured 14,233 and failed it.
  • The Monitor schema behavior is read from the Claude Code 2.1.322 bundle: with the gate on, the tool takes no persistent and caps timeout_ms at 30 minutes; with it off, persistent: true runs with no timeout and ignores timeout_ms.
  • Not run: make -C skill-evals eval-connect (model-graded; no case covers arming the monitor).