Skip to content

Tell an abandoned draft apart from a live turn, and name a remedy that works (#21) - #91

Merged
pseudoseed merged 1 commit into
mainfrom
fix/21-mailbox-prefill-deadlock
Aug 24, 2026
Merged

Tell an abandoned draft apart from a live turn, and name a remedy that works (#21)#91
pseudoseed merged 1 commit into
mainfrom
fix/21-mailbox-prefill-deadlock

Conversation

@pseudoseed

Copy link
Copy Markdown
Owner

Addresses #21 items 1 and 3.

A message to a builder holds with reason busy and never delivers, because the agent left text in its own composer — Claude Code's suggested next action, e.g. spawn a real opencode builder and send it a message. Verified five times on 2026-08-21 by reading the terminal buffer directly via GET /api/terminals/<id>/output. Sixteen dismissed/busy rows in the mailbox are almost all from clearing these by hand.

The gate is right every time. Injecting into a composer holding text would corrupt the agent's input. What was missing is that the operator could not tell what they were looking at, and the documented escape does not work.

Two opposite situations arrived as the same word

gate detail what it is what to do
user-text a draft the agent abandoned; it will never clear itself safe for a human to clear
busy-indicator an agent mid-turn do not touch it; wait

The gate distinguished these internally and the distinction stopped there — reason read busy for both. Migration v20 records the gate's detail on the row. afx inbox shows it in place of the bare reason; afx inbox show renders it as Gate saw, with — (not recorded) for rows held before the migration.

The alert named the wrong command

Remedy: run 'afx inbox' to inspect; 'afx interrupt builder-x' clears a stuck composer.

afx interrupt sends ESC. ESC does not clear typed text, so running it changed nothing and the alert fired again three minutes later. What works is afx send <id> --interrupt, which sends Ctrl+C first — documented as a way to send a message, not as the remedy for this state, so nobody found it.

heldRemedy now picks the advice from the detail:

  • user-textafx send builder-x --interrupt "<message>"(sends Ctrl+C first, which clears the line — 'afx interrupt' sends ESC, which does not)
  • busy-indicatorThe agent is MID-TURN… Do not clear its composer — that corrupts a live turn.
  • anything elseThe gate could not read a ready prompt (no-composer-marker), which is a screen problem rather than a leftover draft.

It says outright that afx interrupt sends ESC and does not clear typed text, because someone who has been running it needs to know why it changed nothing. The same correction is applied to the afx status escalation lines and surfaced in afx inbox itself, where it is actually read.

Not done

Items 2 (auto-clear a hold that can never resolve) and 4 (afx interrupt refusing to send Enter at a selection dialog — the one that nearly deactivated a workspace on 2026-08-21). Both need a decision rather than a fix. Tracked separately.

Verification

  • 5927 passed, 0 failed (full suite, local)
  • tsc --noEmit clean
  • 9 new tests, including the one that matters: two rows identical on reason and distinguishable on hold_detail
  • Migration chain extended for v20 in the same pass rather than after the spec-1313 convergence test caught it, and hold_detail declared LAST in GLOBAL_SCHEMA to match ALTER TABLE order
  • Verified against the live database after install: column present, ordered last, _migrations at 20

🤖 Generated with Claude Code

…t works (#21)

A message to a builder holds with reason 'busy' and never delivers, because the
agent left text in its own composer -- Claude Code's suggested next action, e.g.
"spawn a real opencode builder and send it a message". Verified five times on
2026-08-21 by reading the terminal buffer directly via
GET /api/terminals/<id>/output. Sixteen dismissed/busy rows in the mailbox are
almost all from clearing these by hand.

The gate is right every time: injecting into a composer holding text would
corrupt the agent's input. What was missing is that the operator could not tell
what they were looking at, and the documented escape does not work.

Two opposite situations arrived as the same word. `user-text` is a draft the
agent abandoned and will never clear on its own -- safe for a human to clear.
`busy-indicator` is an agent mid-turn -- clearing there corrupts a live turn.
The gate distinguishes them internally and the distinction stopped there, so
`reason` read 'busy' for both. Migration v20 records the gate's detail on the
row; `afx inbox` shows it in place of the bare reason, and `afx inbox show`
renders it as "Gate saw", with "not recorded" for rows held before the
migration.

And the alert named the wrong command:

  > Remedy: run 'afx inbox' to inspect; 'afx interrupt builder-x' clears a stuck composer.

`afx interrupt` sends ESC. ESC does not clear typed text, so running it changed
nothing and the alert fired again three minutes later. What works is
`afx send <id> --interrupt`, which sends Ctrl+C first -- documented as a way to
send a message, not as the remedy for this state, so nobody found it.

`heldRemedy` now picks the advice from the detail: the clearing command for an
abandoned draft, "wait, do not touch it" for a live turn, and for anything else
an explicit "the gate could not read a ready prompt" rather than a remedy for
the wrong problem. It says outright that afx interrupt sends ESC and does not
clear typed text, because someone who has been running it needs to know why it
changed nothing. The same correction is applied to the `afx status` escalation
lines and surfaced in `afx inbox` itself, where it is actually read.

Migration chain extended for v20 in the same pass rather than after the
convergence test caught it, and hold_detail declared LAST in GLOBAL_SCHEMA to
match ALTER TABLE order.

Not done: issue items 2 and 4 -- auto-clearing a hold that can never resolve,
and `afx interrupt` refusing to send Enter at a selection dialog. Both need a
decision rather than a fix; tracked separately.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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