Skip to content

Mailbox deadlocks on Claude Code's suggestion prefill, and the stuck-message alert names a remedy that cannot clear it #21

Description

@pseudoseed

A message to a builder holds with reason busy and never delivers, because the agent left text in its own composer. The gate is correct. Nothing clears the text, so the wait never ends.

Hit five times on 2026-08-21, each needing manual intervention. mailbox currently records 16 dismissed/busy rows, nearly all from clearing these by hand.

Mechanism

After a turn ends, Claude Code leaves a suggested next action sitting in the composer, e.g.

spawn a real opencode builder and send it a message
keep it, re-request the gate
Retry the deactivate call

Verified by reading the terminal buffer directly via GET /api/terminals/<id>/output on five separate occasions.

classifyBuffer sees a non-empty composer and returns not-clean. Correct: injecting there would corrupt the agent input. But:

  • the agent will not clear its own composer, it is idle and waiting
  • the gate will not deliver until it is clear
  • nothing else touches it

Deadlock. The message sits until a human intervenes.

The alert names the wrong remedy

The stuck-mailbox notification says:

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

afx interrupt sends ESC. ESC does not clear typed text in the composer. Running it changes nothing and the alert fires again three minutes later.

What actually works is afx send <id> --interrupt, which sends Ctrl+C first and clears the line. That is documented as a way to send a message, not as the remedy for this state, so nobody finds it.

Worse, afx interrupt defaults to ESC followed by Enter. When the composer holds a selection dialog rather than free text, that Enter selects the highlighted option. On 2026-08-21 the highlighted option was Retry the deactivate call, which would have deactivated the whole workspace and killed the architect session. ESC dismissed the dialog first, so the Enter landed harmlessly, but that was ordering luck.

Fix

  1. Correct the alert. Name afx send --interrupt, not afx interrupt. This alone removes most of the pain.
  2. Make the hold recoverable. A message held on busy for N minutes against an otherwise idle agent should either clear the composer itself, or escalate with the working command. A hold that can never resolve on its own is not a hold, it is a drop.
  3. Distinguish idle-with-prefill from genuinely busy. They are the same busy reason today. The first is safe to clear, the second is not. The render gate already knows the screen; it can tell an idle composer holding text from an agent mid-turn.
  4. afx interrupt should not send Enter by default, or should refuse when the screen shows a selection dialog. Sending Enter at an unknown dialog is a destructive act chosen by whatever happens to be highlighted.

Note

This is not the render gate being wrong. It is right every time. The gap is that nothing owns clearing a prompt the agent abandoned, and the documented escape does not work.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/towerTower, afx, terminals, messagingbugSomething isn't workingfork-onlyDeliberately not upstreamed to cluesmith/codev

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions