Repository navigation
Conversation
…Monitor The skill armed the Monitor tool with `persistent: true`. Claude Code 2.1.271 removed that option: every watch now expires within 30 minutes, so a session has to re-arm it. Re-arming and skipping handled ids with `grep -v` loses requests in a different way. In Claude Code's Bash, `grep` is the embedded ugrep, and `ugrep -v` holds each line on a stream until the next one arrives, `--line-buffered` or not. The newest request then waits for the next one. watch-requests.sh reads the connector's output from the start, prints the request lines whose event_id the handled file lacks, and exits. The session records each id, starts the watcher again, and handles the lines. The notification is the exit itself, so no stream filter sits between a line and the session, and reading from the start leaves no gap between watchers. The watcher reads only complete lines, and the handled file lives outside the run, so a line reprinted after a crash is still skipped.
…t, and fail closed on the handled file A request printed just before the connector exits could sit in the old output file after the watcher moved to the new one. watch-requests.sh --once checks the old file a last time, without needing timeout(1), which macOS lacks. The watcher's completion notice names its output file rather than carrying the lines, so the skill says to read them from there. awk -v decodes backslashes, so the handled path reaches awk through ENVIRON. A handled file the watcher can't create or read now exits 2 instead of reporting every request as new. The post-compaction notice said the session's monitor kept running. It now names the request watcher.
…he watcher A directory or a read-only file passed touch and -r, so the watcher ran against a handled file the session could never record into, and every restart replayed the same requests. It now requires a regular file it can read and write. The post-compaction notice said everything kept running. The watcher exits on each request, so it may have exited during the compaction. The notice now says to check it, and to read its output and start it again if it has exited.
The output file was checked once, before polling. If it went away later, wc failed, the line count read as zero, and the watcher slept forever while looking alive. Each poll now checks the file and exits 2 if it can't be read.
…r a memory stop A restarted connector now hands off requests made up to four hours before it started, instead of one minute, so a mention made while it was down is still answered. The skill tells the session to start the connector, and the request watcher, again at once when Claude Code stops them for low memory.
…rent watcher A request an earlier run admitted keeps the one-minute grace, so a restart never hands off a decision made under trust that may have changed while the connector was down. The run's start is taken before intake and admission begin, so nothing this run admits is stamped before it. A memory-stop notice for a watcher whose connector was also stopped no longer restarts a watcher on the old output file.
There was a problem hiding this comment.
🟢 Approved
Source review identified no blocking issues in the recovery logic or guidance; tests were not executed here.
No open issues 🎉
What changed in this PR
Improves connector recovery after downtime, building on #902’s watcher and persistent deduplication.
Changes:
- Allows four-hour catch-up for requests admitted by the current run, preserving the one-minute grace for earlier admissions.
- Records startup time before intake and admission begin.
- Updates memory-stop recovery guidance and adds boundary and skill-consistency tests.
[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or rungh pr ready --undo.
Click "Ready for review" or rungh pr readyto reengage.
| File | Description |
|---|---|
| skills/basecamp-connect/SKILL.md | Documents automatic recovery and catch-up limits. |
| internal/connector/skill_layout_test.go | Checks recovery guidance and catch-up wording. |
| internal/connector/handoff.go | Selects catch-up cutoff by admission time. |
| internal/connector/handoff_test.go | Tests current-run and earlier-run cutoff boundaries. |
| internal/commands/connect_run.go | Captures startup time before admission begins. |
Review effort: Balanced
|
🤖 Review status on 2f62103 Which commit each reviewer read:
Pi, first pass (35a423e)
Pi, second pass (5030403)
Open threads: none. This needs #902 merged first, and then a human merge. |
c5bf320 to
d5b976f
Compare
|
🤖 Local review at 2f62103, covering this PR's three commits (35a423e, 5030403 and 2f62103) on top of c5bf320. The catch-up logic is clean. The stack under it isn't. It's built on an old #902. #902 has since been rebased onto main and gained four commits (now 20449f0). This PR still sits on c5bf320, so GitHub reports it as conflicting with its base, and a merge onto #902's current head conflicts in On the change itself, nothing beyond what your two passes already handled. Only records this run decided get the 4-hour window, and |
Summary
When Claude Code stops
basecamp connectbecause memory is low, the agent stops answering. Mentions made while it's down are also lost when it starts again. This PR makes two changes:Stacked on #902 (
connect-one-shot-watcher). The skill text changes #902's watcher steps, and #902's handled-ids file is what stops a request from being handled twice across restarts. Merge #902 first.Card: Claude Code kills the connector for low memory, and the agent stops answering. These are options 2 and 3 of the proposal on the card.
Why
Claude Code 2.1.303 stops an idle session's background Bash tasks under critical memory pressure. All of these have to hold:
Its notice says "stopped because the system is running low on memory … Do not start it again on your own". The session then waits for a person, and the agent stays deaf. Claude Code ends the task's process group and descendants, and it doesn't look at the task's own memory, so a small connector gets stopped too. A task started again within 30 minutes of such a stop isn't stopped again until someone types in the session.
On restart,
handOffclosed every admitted request made more thanHandoffGrace(one minute) before the run started asbefore_this_run. The feed resumes from its checkpoint, so intake does see the mentions made during the outage. Handoff then dropped them.Change
internal/connector/handoff.gohandoffCutoff. If this run wrote a record's admission decision (Decision.DecidedAtat or afterStarted), the record may have been made up tohandoffCatchUp(4 hours) before the run.HandoffGrace(one minute). An earlier run's decision was made under whatever trust held then. Replaying it hours later could hand off a request from someone who was removed from--allowwhile the connector was down. Requests made during an outage are admitted by the new run, under current trust.before_this_runlog line now carries the cutoff.internal/commands/connect_run.go: the run's start is taken before intake and admission start, not after. Otherwise something this run admits could be stamped beforeStartedand get the one-minute rule.skills/basecamp-connect/SKILL.mdTests
handoff_test.go:before_this_runat 4 hours and 1 second.skill_layout_test.go: the memory-stop steps exist, including the watcher rule. The skill states the catch-up in the same number of hours ashandoffCatchUp, so changing one without the other fails.startedearlier inrunConnect. It removes an ordering race between the handoff's start time and admission's first decision. I found no seam to test that without a real run.bin/ciis green locally (MISE_BATS_VERSION=1.13.0, as in Watch for connector requests with a one-shot background watcher, not Monitor #902).Trade-offs
HandoffOptions.Graceand changes the samehandOfflines. It keepsHandoffGraceas the earlier-run rule, which this PR leaves alone, so its e2e test's expectation still holds. A textual conflict is likely, and it's easy to resolve.connect releasehelp says a restart doesn't replay older requests. That stays true for an earlier run's records, but would read better if it mentioned the catch-up.Summary by cubic
A restarted Basecamp connector now answers requests made while it was down (up to 4 hours before the run), instead of dropping anything older than a minute. The skill also tells the agent to restart the connector at once after Claude Code stops it for low memory.
Written for commit 2f62103. Summary will update on new commits.