Repository navigation
Make Claude Stop truthful on claude -p: native interrupt, bounded exit, withdrawn prompts (CF-2) - #286
Conversation
Add failing regressions for #275 against the stream-json backend, with protocol shapes captured live from Claude Code 2.1.302: - Stop sends SIGTERM at once instead of the native interrupt, and drops every message after it, including the turn's final reply snapshot. - A process that never confirms its exit holds Stop forever. - control_cancel_request is ignored, so a cancelled approval card stays actionable, holds a drained turn open, and a late answer is written. - An answer after the turn settled writes to the ended stdin and raises an uncaught ERR_STREAM_WRITE_AFTER_END. - keep_alive re-arms the idle window; the final unterminated line, split-UTF-8 stderr, and stderr or exit codes after a failed result are lost; a killed process reports "exited with code null". - Failure details copy MCP route tokens from argv. The fake process now models exit versus close, signals it ignores, held stdio and raw chunks, and answers interrupts the way the live CLI did. The interfaces the tests use are declared without behaviour: TurnSink.approval's withdrawal signal, TurnResult.stop, the approval item's withdrawn reason, and the timing and liveness knobs. Refs #275 Coven-Harness: Claude Code / claude-opus-5-5
Stop now writes Claude Code's own interrupt control request and ends stdin, as the 2.1.302 CLI needs to stop a running tool. Every message the CLI still prints is read: the final reply snapshot, the rejected tool result and the closing result, which after Stop is no failure. The turn ends when the process exits. One that has not exited 1.5 s after Stop gets SIGTERM, then SIGKILL 1.5 s later. If no exit follows within 2 s, the stop is reported unconfirmed instead of waiting forever. The result says which of these happened (exited, terminated, killed or unconfirmed), with exit code, signal and pid. control_cancel_request withdraws the matching can_use_tool. The sink is told through an abort signal, the request stops holding the turn open, and no answer is sent: the CLI ignores one for a request it cancelled. Writes go only to an open stdin of an unfinished turn, and EPIPE is absorbed. This removes the uncaught ERR_STREAM_WRITE_AFTER_END raised by a late approval. The turn now settles on exit plus a bounded drain, not only on close, so a descendant holding the pipes cannot keep it open: - stderr is decoded across chunks, and the last stdout line is read even without its newline. - A failed result waits up to 1 s for the stderr and exit code that follow it. - A signal death says so. - keep_alive no longer re-arms the idle window. Failure details redact MCP route tokens from argv. Refs #275 Coven-Harness: Claude Code / claude-opus-5-5
The runner closes a card when its backend withdraws the request. It is patched once to answer "no" with a withdrawn reason (native, stopped or ended), so older windows and the phone show it closed. Its promise settles "no", the thread stops waiting, and a late answer finds nothing pending. A request withdrawn while the approval gate is still deciding shows no card at all, and Jev is cancelled with it. Stop and the end of a turn label their leftover cards the same way instead of "Denied". A stopped turn's notice now says how it ended: "Stopped.", or that Modex had to terminate or kill the CLI, or, as a warning, that the process never confirmed its exit. Until such a process is gone, deleting or retiring the thread is refused with its pid. The worktree, thread and attachments it may still use stay. Refs #275 Coven-Harness: Claude Code / claude-opus-5-5
Independent review of the lifecycle change found three low-severity
faults, each now covered by a regression:
- Stop pressed while a failed result was draining reported a clean stop
("exited" with no exit seen), dropped the failure card and cancelled
the SIGTERM/SIGKILL escalation. The failure now stands, and Stop's
escalation outlives the turn's settlement.
- A success result inside the failure's drain window completed the
turn. Once a turn has failed, later results are ignored.
- A launch that throws synchronously still put the raw argv, MCP route
tokens included, into the failure details.
Refs #275
Coven-Harness: Claude Code / claude-opus-5-5
The scripted Claude in titles.spec only recorded a cancelled naming run on SIGTERM. Stop now interrupts Claude natively and ends its input, and the fixture exited cleanly on that before any SIGTERM was due, so "a manual rename cancels pending naming" waited for a marker that never came. The run was cancelled all the same. The fixture now acknowledges the interrupt and closes the turn as Claude Code 2.1.302 does, and records the cancellation there, as its Codex branch already does for turn/interrupt. Refs #275 Coven-Harness: Claude Code / claude-opus-5-5
The receipt holds: - the Claude Code versions and the live 2.1.302 captures, with the probe script; - each fault on main with its change, and the timing budgets; - the red-then-green record and the full check results at 62ac15c and d11cdb6; - the independent review; - the acceptance-criteria map; - what is still unproven: a live-model Stop through the app, Windows and Linux escalation, and the Companion's label for withdrawn cards. Status moves #250's lifecycle item from Next to In flight. Refs #275 Coven-Harness: Claude Code / claude-opus-5-5
CF-1 replaced TurnSink's delta, assistant, tool and thinking calls with observe(observation) and scopes every sink to its attempt. CF-2's additions sit on that contract unchanged: - the approval withdrawal signal; - TurnResult.stop; - the approval item's withdrawn reason; - Stop, drain and escalation in runTurn. ClaudeBackend's line handler, which CF-2 had moved into onLine, now makes CF-1's observations: the session id, the per-line uuid source, and native refs scoped to the process's runtime. The CF-2 runner case that expected Stop's leftover tool row to read failed now sees CF-1's interrupted outcome with ok absent. The next commit settles that row from the CLI's own rejection. Refs #275, #274 Coven-Harness: Claude Code / claude-opus-5-5
After the interrupt, Claude Code closes each tool call it stopped with
an error tool_result ("The user doesn't want to proceed with this tool
use."). CF-1 mapped every error result Modex had not refused to failed,
so a stopped Bash call read as a failure. An error result that arrives
after Stop is now interrupted. The Claude mapping in
docs/conversation-events.md says so.
The scripted CLI now sends that rejection for the calls it was stopped
in, as the live 2.1.302 capture does.
Refs #275
Coven-Harness: Claude Code / claude-opus-5-5
The receipt names 172b13f as the tested code and records its full checks: frozen install, build, typecheck, unit tests, and desktop and site E2E. It also covers how CF-2 sits on CF-1's contract and the post-Stop tool outcome. Status notes that a stopped tool call reads Stopped. Refs #275 Coven-Harness: Claude Code / claude-opus-5-5
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Code coverage overviewLanguages: TypeScript TypeScript / code-coverage-agent
View coverage of the 9 most impacted files.
ℹ️ Calculations represent line coverage. Results may change as calculations are updated. Updated |
This brings the branch up to date with main for review. The merge is clean: CF-2's withdrawn-card label in ThreadView's ApprovalItem sits beside CF-4's typed turn layout, unchanged. Refs #275 Coven-Harness: Claude Code / claude-opus-5-5
e2e/claude-live-stop.spec.ts drives the built app with the real `claude -p` and a real model. It runs only with MODEX_CLAUDE_LIVE=1, because each run spends about five turns of the signed-in account's usage. It covers four Stops: - with a permission card waiting; - while an approved 30 s command runs; - with the same thread resumed afterwards; - while a reply streams. Against 52b159a, with Claude Code 2.1.302 and Haiku, all four passed. Each ended without a failure card: - Card: idle in 0.86 s. - Running command: SIGTERM after the 1.5 s grace, idle in 2.33 s, the row interrupted, and its file never written. - Resume: replied "resumed". - Stream: idle in 0.86 s, with 670 characters kept. The receipt adds these results and the full checks at 52b159a, including two E2E setup failures from a full disk and their passing rerun. Refs #275 Coven-Harness: Claude Code / claude-opus-5-5
) The only conflict was docs/status.md. main had moved CF-1 and CF-4 from In flight to On main, unreleased, so the slot keeps just CF-2's entry. CF-6's runner change, which settles items on load and emits the change, merges beside CF-2's stop and withdrawal code. Refs #275 Coven-Harness: Claude Code / claude-opus-5-5
|
Reconciliation at head a6ea0b0 found a reproducible process-ownership gap. If a failed result arrives just before Stop, the failure drain timer settles the backend before Stop's termination/exit confirmation. The runner records unconfirmed pids only in its interrupted-result branch. Consequently, A runner-level probe against the built PR head failed with The repair should preserve the original failure, wait for bounded Stop confirmation, carry the stop report alongside failed results, and retain unconfirmed pids regardless of turn outcome. This finding remains open; the current head is not yet ready for a merge decision. No merge or release is authorized by this reconciliation. |
Preserve the original failure while waiting for bounded Stop confirmation, and retain unconfirmed process ownership regardless of turn outcome. Cover the backend result and refusal to delete its live worktree. Coven-Harness: Codex / gpt-6
|
CF-2 reconciliation repair is now PR #291, targeting The failure-before-Stop deletion race was reproduced and repaired test-first: retain the original error, wait for exit or bounded Stop confirmation, then retain any unconfirmed PID regardless of turn outcome. Both new backend/runner regressions pass. Local signature verification passed; the prohibited-attribution check printed nothing. Verification on the repaired source tree: pinned pnpm 10.34.5 frozen install, full build and typecheck passed. Final Fresh live acceptance through Modex with Claude Code 2.1.302 / Haiku passed all four scenarios: pending-card Stop (936 ms), approved-command Stop (2,336 ms, command file never written), resume (
Main advanced to CF-5 ( |
Haiku can finish a 400-word story before Stop lands. The live run then honestly records a completed turn, but that is no test of a mid-stream Stop. The streaming step now asks for 2,000 words, and every step must end with a "Stopped." notice. Against a6ea0b0 all four steps passed: - card: 0.93 s; - running command: SIGTERM, 2.36 s, interrupted; - resume: "resumed"; - stream: 1.37 s, 834 characters kept. The receipt records the full checks at a6ea0b0, which is up to date with main at fb76aac: - frozen install, build, typecheck and unit tests; - desktop E2E 322 passed and 4 skipped, with one sidebar popover failure that passes on rerun and in CI; - site 7/7; - CI green on all 12 check runs. Refs #275 Coven-Harness: Claude Code / claude-opus-5-5
The merge is clean. CF-5 adds the Bash command to Claude's tool.start and bounds tool output in the runner, beside CF-2's stop, drain and withdrawal code. Refs #275 Coven-Harness: Claude Code / claude-opus-5-5
|
CF-2 reconciliation is ready for a separate merge decision at signed head Exact-head CI 39145074213 checked out
The owner integrated both repairs (#291 deletion ownership after failure-before-Stop; #298 evidence-fixture race/independence). The subsequent main integrations are present. I inspected #301's runner change: it compares settled items against the single stored parse; CF-2's Stop/drain/withdrawal code is unchanged. Its new migration and end-to-end coverage passed above. Evidence limits remain explicit:
Next executable step: the maintainer makes the separate merge decision for #286 at this exact head. This session has not merged or released anything, changed policy defaults, or modified the owner's worktree. CF-3 remains gated on #286 actually landing; after that, reconcile the existing #293 and its ownership before resuming CF-3 work. |
…tore grant (steward cycle 2) (#300) * Settle three claims cycle 1 left unverified Docs and plans steward, cycle 2 (#290). - modex-signin: 43821 is only the default callback registered with WorkOS. The listener has used a system-assigned port since #141 (workos-auth.ts, listen(0)), and the wildcard entry covers it. The doc now says so. - auto-routing: unknown models fall back to tier 2 of 0-3 (catalog.ts tierOf), which "the middle tier" did not pin down. - update-recovery: the test counts are from the 2026-10-12 local acceptance run the paragraph links, so it now gives that date. Checked and left as they are: the analytics demo figures (471 calls, 147 sessions, 45 days, 11 sources, 212 unpriced) match sample.ts when run, and Codex threads do keep their Pages tools until the app-server restarts, because the config is sent only when a thread is first loaded. Coven-Harness: Claude Code / claude-opus-5-5 * Point In flight at the tracker instead of listing CF PRs Val decided on #290. The In flight entry for native conversation fidelity listed open PRs by number, and the list went stale within hours. It already named #291 (merged into #286) and missed #295, #297, #298 and #299. The entry now says the tracker's child issues link their open PRs, which GitHub keeps current. Coven-Harness: Claude Code / claude-opus-5-5 * Record the GUI Connect grant as Val's manual testing Val answered on #290. In the October 11 signed Store acceptance, a GUI Connect run acquired a grant before the Host dialog was captured, and three reviews left its attribution awaiting Val. Val confirmed the grant came from Val's own manual testing. - The signed-acceptance review gains a dated "Attribution resolved" section. - The Store and Companion readiness reviews each gain a one-line dated note pointing to it. Their original text is unchanged. - status.md's Store Open list drops the attribution item. The Cancel check stays open: no Cancel success was recorded. Coven-Harness: Claude Code / claude-opus-5-5
|
For the CF-2 owner: #300 (steward docs cycle 2) landed on |
The only conflict was the In flight entry for native conversation fidelity in docs/status.md. It takes main's wording, which Val chose on #290: the tracker's child issues link their open PRs, so the entry no longer lists them. Everything else merged as is. Coven-Harness: Claude Code / claude-opus-5-5
|
For the CF-2 owner: at Val's request, I pushed one signed commit to An apology about your worktree
What I could not restore is the in-progress merge metadata ( |
|
One more The CF-2 entry is in In flight, between the v0.2.0 entry and the native conversation fidelity pointer, and it ends "Open in #286". Once this merges, that is landed work under In flight saying it's open. The pointer right below it says landed slices go under "On main, unreleased". Suggestion: move the entry into "On main, unreleased" as a sub-bullet of the conversation-fidelity bullet, as #289, #294 and #301 did, and drop "Open in #286". This won't conflict with #303. It changed the v0.2.0 entry above yours, but left its "[Candidate review]" line alone. |
|
Status for the CF-2 owner and Val: #303 landed on |
|
Val has asked this reconciliation session to patch job 117755082206 and carry #286 through merge. I have reproduced the missing rejection specifically in the bare-URL Knowledge fixture: it serves readiness before its URL-emitting CLI exits, which takes the valid owned-server path. A separate repair branch |
|
Repair PR #305 now targets this branch at verified signed head |
Coven-Harness: Claude Code / claude-opus-5-5
Coven-Harness: Claude Code / claude-opus-5-5
Coven-Harness: Claude Code / claude-opus-5-5
Coven-Harness: Claude Code / claude-opus-5-5
shared/human-requests.ts: approvals and native questions as one versioned contract. A request is bound to thread, turn, attempt, account, runtime and native request id plus a digest of the exact action; it accepts one local answer, and an unknown id, a wrong identity, a changed action, a withdrawn request or a second answer authorises nothing. Stop withdraws pending requests; one whose answer was being sent becomes uncertain, never resolved. Codex's experimental item/tool/requestUserInput (codex-cli 0.163.0) maps to typed questions, answers are validated against them, and secret answers are hidden from anything shown, saved or exported. Not wired in yet: that consumes #275's interfaces once #286 lands. Refs #276 Coven-Harness: Claude Code / claude-opus-5-5
shared/human-requests.ts: approvals and native questions as one versioned contract. A request is bound to thread, turn, attempt, account, runtime and native request id plus a digest of the exact action; it accepts one local answer, and an unknown id, a wrong identity, a changed action, a withdrawn request or a second answer authorises nothing. Stop withdraws pending requests; one whose answer was being sent becomes uncertain, never resolved. Codex's experimental item/tool/requestUserInput (codex-cli 0.163.0) maps to typed questions, answers are validated against them, and secret answers are hidden from anything shown, saved or exported. Not wired in yet: that consumes #275's interfaces once #286 lands. Refs #276 Coven-Harness: Claude Code / claude-opus-5-5
shared/human-requests.ts: approvals and native questions as one versioned contract. A request is bound to thread, turn, attempt, account, runtime and native request id plus a digest of the exact action; it accepts one local answer, and an unknown id, a wrong identity, a changed action, a withdrawn request or a second answer authorises nothing. Stop withdraws pending requests; one whose answer was being sent becomes uncertain, never resolved. Codex's experimental item/tool/requestUserInput (codex-cli 0.163.0) maps to typed questions, answers are validated against them, and secret answers are hidden from anything shown, saved or exported. Not wired in yet: that consumes #275's interfaces once #286 lands. Refs #276 Coven-Harness: Claude Code / claude-opus-5-5
…ards (CF-3) (#293) * Define the human-request contract and a pure broker with one resolution shared/human-requests.ts: approvals and native questions as one versioned contract. A request is bound to thread, turn, attempt, account, runtime and native request id plus a digest of the exact action; it accepts one local answer, and an unknown id, a wrong identity, a changed action, a withdrawn request or a second answer authorises nothing. Stop withdraws pending requests; one whose answer was being sent becomes uncertain, never resolved. Codex's experimental item/tool/requestUserInput (codex-cli 0.163.0) maps to typed questions, answers are validated against them, and secret answers are hidden from anything shown, saved or exported. Not wired in yet: that consumes #275's interfaces once #286 lands. Refs #276 Coven-Harness: Claude Code / claude-opus-5-5 * Answer each request once, route Codex questions to a card, and close stale cards The runner keeps the turn's approvals and questions in the human-request broker. The first answer is taken; every other one is refused with a reason (already resolved, withdrawn, unknown, invalid) that thread:answer now returns and the phone receives as a 409. Codex item/tool/requestUserInput (codex-cli 0.163.0, experimental) becomes a question card, answered through thread:answer-question. Answers are checked against the questions before anything reaches Codex. A secret answer goes to the CLI only and is saved as "(hidden)". Stop sends an error reply, never an empty answer. When serverRequest/resolved arrives for a request still open, Codex settled it, so the card closes as withdrawn and no answer is sent. Approvals now carry the CLI's own request id (Codex JSON-RPC id, Claude request_id). Cards a dead process left open close as ended when their thread loads, saved and emitted. Phones see a question as a notice to answer on the Mac. Part of #276. Coven-Harness: Claude Code / claude-opus-5-5 * Show question cards with a control for each question Options are radio buttons, with Other when the question takes free text. A free-text question gets a text field, and a secret one a password field. A refused answer says why on the card. An answered card lists the answers, with a secret shown as (hidden). A closed card says why, in the approval card's words. The turn header reads "Waiting for your answer" while a question waits. An answered or closed question folds into the turn's work; an open one never does. The e2e drives a fixture codex-cli 0.163.0 app-server through the real UI. It covers an incomplete answer refused in place, one reply carrying the answers, a refused second answer, Stop giving an error reply, and no secret anywhere under MODEX_HOME/app. Part of #276. Coven-Harness: Claude Code / claude-opus-5-5 * Document the human-request contract and record CF-3 in status docs/human-requests.md covers the broker's exported signatures, its states and refusals, the runner's binding and withdrawal rules, the Codex 0.163.0 and Claude 2.1.302 mappings, the clients, how secrets are kept, the tests, and what is left for CF-6 and CF-7. Part of #276. Coven-Harness: Claude Code / claude-opus-5-5 * Keep a new tab's late focus from taking the caret away Opening a tab (⌘⇧B) focuses its address bar twice: once when the tab becomes active, and again in an animation frame that newTab queues for when the panel was not yet visible. Chromium can hold frames back for an inactive or busy window. The late focus then landed after the person, or a test, had already moved on, and took the caret out of the composer mid-typing. That failed CI on #293: store-desktop.spec.ts's browser-tools recovery case filled the composer just as the late frame ran. The text went into the address bar and the composer stayed empty (job 117782462586). The same case failed on #294's first run. The late focus now applies only if focus has not moved since the tab opened. The new workspace.spec.ts case holds animation frames, moves to the composer, releases the frames and types. It failed first: the composer lost focus when the frame ran. Coven-Harness: Claude Code / claude-opus-5-5
CF-2 (#286), CF-3 (#293), CF-4's Markdown slice (#283), CF-6's request half (#308) and CF-7's clarifications (#312) are on main, with their merge commits. The candidate note says the fixture rows now run on main, and no candidate is pinned while the live-provider and human gates stay open. Refs #281 Coven-Harness: Claude Code / claude-opus-5-5
…nts at once, reconnect, a clarifying question (CF-8) (#313) * Add the request variants to the fidelity path Four cases in e2e/conversation-fidelity.spec.ts, through the real app: - A native withdrawal: the Claude fixture takes its own request back (control_cancel_request). The card closes as withdrawn, no answer reaches the CLI, a late answer is refused, and the file is unchanged. - The desktop and the phone answer at once, over the bridge and the companion's HTTPS. Exactly one is accepted, the CLI reads one decision, the card names who gave it, and the file matches the winner. - With the journal on, a phone that loaded the open card reconnects after the desktop's decision. Its delta shows the card answered by the desktop and sent, its late answer is a 409, and every count stays at one. - A clarifying question inside the path, from a codex-cli 0.163.0-shaped fixture: answered on its card, heard once, followed by a real edit and check, saved to Notes, and inert after a restart that re-runs nothing. The fixture now counts every decision the CLI reads, so the earlier cases assert that too. The golden path is marked slow: since #301 it also takes 18 reference captures and relaunches, and it ran past 90 s on a Mac at load average 100. These cases found the companion's 404 for threads started on the Mac, fixed in the CF-6 branch. Refs #281 Coven-Harness: Claude Code / claude-opus-5-5 * Record the request variants in the acceptance receipt The matrix rows for a clarifying question, a native withdrawal, simultaneous answers and a reconnect after a decision move from open to verified with a fixture, with what each case asserts. The defects list adds the companion's 404 for threads started on the Mac. The fidelity summary gains rows for questions and approvals. Refs #281 Coven-Harness: Claude Code / claude-opus-5-5 * Record CF-8's request variants in status Refs #281 Coven-Harness: Claude Code / claude-opus-5-5 * Record the landed children in the acceptance receipt CF-2 (#286), CF-3 (#293), CF-4's Markdown slice (#283), CF-6's request half (#308) and CF-7's clarifications (#312) are on main, with their merge commits. The candidate note says the fixture rows now run on main, and no candidate is pinned while the live-provider and human gates stay open. Refs #281 Coven-Harness: Claude Code / claude-opus-5-5
Closes #275 (CF-2 of #273). This brings #250's lifecycle work to
backends/claude.tsand keepsclaude -pstream-json. There is no Agent SDK and no API mode.What changes
error_during_executionnor the exit code 1 after an interrupt counts as a failure.control_cancel_requestaborts a per-request signal passed toTurnSink.approval.answer: "no"andwithdrawn: "native", and the thread stops waiting. No answer is written back, so a late click, from the window or over IPC, satisfies nothing.tool_resultthat arrives after Stop settles the tool asinterrupted, so the row reads Stopped, not Failed.close, so a descendant holding the pipes can't keep it open.keep_aliveno longer re-arms the idle window.ERR_STREAM_WRITE_AFTER_ENDin main.Already in place, and covered again by tests:
--resumezero-model-call hold;Interfaces
All additive, on CF-1's contract.
TurnSink.approval(req, withdrawn?: AbortSignal).TurnResult.stop?: StopReport:outcome:exited | terminated | killed | unconfirmed;exitCode,signal,pidandms.withdrawn?: "native" | "stopped" | "ended"on approval items.RunnerOptions.isProcessAlive.No turn, attempt or generation IDs are added. Late callbacks are scoped by:
active(), which checks abort identity and CF-1's current attempt;The branch merges
mainthrough CF-1 (#282), CF-4 (#284), CF-6 part 1 (#285), CF-5 part 1 (#287), CF-7 part 1 (#289), #292, CF-8 (#294, #301), #296 and #297. The changes to shared files were posted on #274.Evidence
Receipt, with the live 2.1.302 protocol captures and the probe script.
main's behaviour;e2e/claude-live-stop.spec.tswith Claude Code 2.1.302 and Haiku, at61cc65d:interrupted, and its file never written.MODEX_CLAUDE_LIVE=1.61cc65d: 230/230 unit tests and 28/28 Electron cases.7c9d62c, before the CF-7 merge:pnpm testpasses:71d905a, the head: up to date withmainat844ae04. The requiredunit + e2e (macOS)is green, and so are coverage, the Linux and Windows installer jobs, CodeQL and every analysis job. Locally, the targeted unit suites pass 200/200.4c850f3, with test: reconcile CF-2 evidence checks with current main #298 in: all 10 CI checks green.61cc65d: a clean full local run (unit, then 329 desktop and 7 site E2E) and the live Stop spec both passed.mainmerges (Wait for the evidence turn to render before unfolding it (e2e race) #297, Capture fidelity states in every theme, measure a long transcript, and fix what they found (CF-8) #301) were clean and leave CF-2's backend code unchanged.claude -p, passes against this branch.#291, the stacked repair from another session, was merged into this branch with Val's approval. A turn that failed before Stop now stays owned until its exit is confirmed. #298, a second stacked follow-up, was fast-forwarded in with Val's approval. It fixes the evidence-spec race.
Not established
Timing change