Skip to content

fix(bin): report composer state after interrupt failure - #108

Merged
withally merged 3 commits into
mainfrom
fm/fm-control-interrupt-failure-recheck-f2
Sep 3, 2026
Merged

withally merged 3 commits into
mainfrom
fm/fm-control-interrupt-failure-recheck-f2

Conversation

@withally

@withally withally commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Intent

Fix the review residual from the landed fm-control composer-clear change: route post-interrupt failures through the shared composer reporting boundary in bin/fm-control.sh. When the interrupt key sequence fails, still perform the shared composer read, preserve and report the first 80 characters of pending text, and report the observed composer state plus successfully sent keys through the same refusal boundary. Never submit the exit command on that path. Add an executable-interface regression in tests/fm-control.test.sh using the fake backend where Muse Escape restores pending text and the interrupt sequence then fails. Run all touched tests through bin/fm-test-run.sh. Keep this to one fix in one PR and use no more than two review rounds.

What Changed

  • Routes failed interrupt or follow-up clear-key delivery through the shared composer refusal boundary, re-reading and reporting the observed state, available first-80-character excerpt, and only successfully sent keys.
  • Documents the new failure-reporting behavior and adds a fake Muse regression covering restored pending text, failed interrupt clearing, and no exit submission.

Risk Assessment

✅ Low: The narrow fix refreshes shared composer evidence after interrupt-send failures, tracks successfully sent keys, and adds an executable regression covering restored text and exit suppression.

Testing

The touched test ran through bin/fm-test-run.sh; the failure-path transcript shows fresh restored excerpt reporting, observed state, exact successfully sent keys, refusal status 1, and no exit submission. No broad suite or linter was run.

Evidence: Targeted fm-control test-runner log

Source: Targeted fm-control test-runner log

FM_TEST_BEGIN 2026-09-02T23:48:27Z tests/fm-control.test.sh family=backend-dispatch expected_gate_skip=none
ok - fm-control exit: every verified harness gets its own verified exit command
ok - fm-control interrupt: every verified harness gets its own verified key and repeat count
ok - fm-control interrupt: opencode needs a double Escape, claude a single one
ok - fm-control: a harness with no verified control mechanics is refused, not guessed at
ok - fm-control-lib: a recorded harness resolves to its verified adapter without guessing
ok - fm-control: prefixed recorded harnesses reach interrupt and exit mechanics
ok - fm-control-lib: the backend key matrix matches each adapter's real send-key surface
ok - fm-control-lib: only harnesses with verified composer-clear mechanics have a clear key
ok - fm-control-lib: adapter capability is per task kind, not per adapter alone
ok - fm-control interrupt: a backend that cannot deliver the harness's key refuses instead of sending another
ok - fm-control: a backend that cannot prove an agent stopped refuses exit and relaunch
ok - fm-control-lib: stop-proving verbs are gated on the backends that really classify agent state
ok - fm-control: a legacy window label is refused and the exact task id is named
ok - fm-control: an explicit backend endpoint is never a control target
ok - fm-control: an unrecorded task id is refused
ok - fm-control: a record whose endpoint identity names another task is refused
ok - fm-control: a remotely placed secondmate is refused by placement, not by a metadata complaint
ok - fm-control: interrupt and exit lock before task-state resolution
ok - fm-control: the verb list is closed - no raw keys, arbitrary text, or clear verb
ok - fm-control: resume is refused with the determinism reason and the alternative
ok - fm-control: profile and note flags belong to relaunch only
ok - fm-control exit: an already-stopped agent is idempotent success with no bytes sent
ok - fm-control exit: a vanished endpoint refuses instead of silently succeeding
ok - fm-control interrupt: refuses when no agent is running rather than keying a shell
ok - fm-control exit: an endpoint whose process cannot be attributed refuses
ok - fm-control exit: a busy agent receives interrupt delivery before the exit command
ok - fm-control exit: a busy interrupt cannot reintroduce pending composer text before exit
ok - fm-control exit: interrupt failure preserves and reports restored composer text without exit
ok - fm-control exit: pending composer text is cleared and reported before the exit command is typed
ok - fm-control exit: an unclearable composer refuses with concrete state and key evidence
ok - fm-control exit: an unreadable pending excerpt fails closed before clearing
ok - fm-control exit: OpenCode pending composer refuses without a verified clear key
ok - fm-task-inbox-ring: pending remains an exact defer verdict with ambient excerpt mode
ok - fm-control exit: an idle agent goes straight to its exit command
ok - fm-control interrupt: unconfirmed delivery preserves observed busy state
ok - fm-control interrupt: muse confirms cancellation from its session log
ok - fm-control interrupt: postconditions are revalidated after acknowledgement polling
ok - fm-control exit: an interrupt-stopped agent satisfies the gone-state postcondition
ok - fm-control exit: a stubborn agent reports delivered input and an unconfirmed exit
ok - fm-control interrupt: grok reports delivery without claiming cancellation
ok - fm-control interrupt: grok's idle footer does not confirm cancellation
ok - fm-control: a lifecycle command to a secondmate is unmarked and opens no reply expectation
ok - fm-control's arrival leaves fm-send's from-firstmate marking untouched
FM_TEST_END 2026-09-02T23:49:16Z tests/fm-control.test.sh exit=0 duration_ms=49219 gate_skip=false
FM_TEST_SUMMARY total=1 failed=0 skipped_gate=0 duration_ms=49280
FM_TEST_SUMMARY_FAMILY family=backend-dispatch count=1 duration_ms=49219 failed=0
FM_TEST_SLOWEST rank=1 script=tests/fm-control.test.sh duration_ms=49219
Evidence: Direct interrupt-failure transcript

Source: Direct interrupt-failure transcript

scenario: Muse Escape restores pending text; C-u then fails fm-control exit status: 1 pending composer excerpt: restored pending text 0123456789012345678901234567890123456789012345678901234567 error: ... composer state 'pending-unproven'; keys sent: Escape recorded named keys: C-u, Escape, C-u literal submissions: none

scenario: Muse Escape restores pending text; C-u then fails
fm-control exit status: 1
fm-control combined output:
pending composer excerpt: initial pending text
pending composer excerpt: restored pending text 0123456789012345678901234567890123456789012345678901234567
error: interrupt key Escape reached task t1, but C-u did not, so its composer still holds the cancelled prompt; clear it before the next lifecycle action; composer state 'pending-unproven'; keys sent: Escape
recorded named keys (excluding Enter):
C-u
Escape
C-u
literal submissions:

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 2 issues found → auto-fixed ✅
  • 🚨 bin/fm-control.sh:378 - Required criterion: “When the interrupt key sequence fails, still perform the shared composer read, preserve and report the first 80 characters of pending text.” The new failure hunk at line 378 reuses read_composer_state after an earlier pre-interrupt pending read has already set COMPOSER_EXCERPT_CAPTURED=1 and COMPOSER_EXCERPT_REPORTED=1; a busy Muse exit can therefore clear an initial draft, have Escape restore a different prompt, then fail on C-u while reporting only the old excerpt (or no new excerpt) even though the observed state is pending. Confirm whether this failure boundary must force a fresh shared excerpt capture/report.
  • ⚠️ tests/fm-control.test.sh:771 - The regression only checks that output contains keys sent: Escape at line 771. Because the fake records the failing C-u before returning nonzero, an implementation that incorrectly reports keys sent: Escape, C-u would still pass this substring assertion. Assert the exact successfully-sent-key field, and/or assert that the failed key is absent from it.

🔧 Fix: Refresh composer evidence after failed interrupts, captain
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • bin/fm-test-run.sh tests/fm-control.test.sh
  • test_busy_interrupt_failure_reports_restored_composer_without_exit
  • Direct fake Muse/tmux executable-interface reproduction of fm-control t1 exit
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@withally
withally merged commit 958572d into main Sep 3, 2026
24 of 25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant