Repository navigation
Claude: a blocked bare <hcom> wake must not leave the agent unwakeable - #165
Conversation
… the agent UserPromptSubmit set status active/prompt before blocking a bare `<hcom>` wake that found nothing pending. A blocked prompt runs no turn, so no Stop hook follows, and the row stayed active. The PTY gate then held every later message at `not_idle`. Stability recovery needs 10 s of quiet screen output, and that never came while a background agent was running. Observed in a live session: a background task's notification started a turn just as the PTY typed `<hcom>`. Claude queued the wake, and that turn's own UserPromptSubmit delivered the pending batch. Stop set listening, and 85 ms later Claude submitted the queued `<hcom>`. The hook found nothing pending, set active/prompt, and blocked the prompt. Every later message logged "Gate blocked: not_idle" until a manual wake 4.5 minutes later, by which time 10 messages were unread. In PTY mode a blocked bare wake now sets the status Stop sets (listening, ""), then checks for pending messages again. A message that arrived after the first check is delivered by this wake rather than blocked. Any later one finds the instance idle and gets its own wake. Without the PTY wrapper nothing injects wakes, so the status stays as Stop's poll left it rather than being marked active for a turn that never runs. Tests: after Stop and a blocked bare wake the row is listening, the Claude PTY gate passes for a new message, and the next wake delivers it (this fails on the old code with active/prompt). A message that lands between the first check and the block is delivered by that wake. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When Claude's UserPromptSubmit hook blocks a bare <hcom> wake (nothing pending), Claude puts the blocked prompt back into its input box. The PTY gate then read hcom's own trigger as user text and held every later message at prompt_has_text (observed: 73 blocked attempts over 90 s, nothing typed, until an outside monitor started a turn). The screen scrape now reports when Claude's box holds only <hcom>, typed on one row (ScreenTracker::is_claude_leftover_wake). With that, the gate opens with reason leftover_wake and the delivery loop presses Enter alone: the render phase re-checks ownership, approval and typing before the Enter, and the UserPromptSubmit hook delivers the pending batch as usual. Any other text stays protected, including "<hcom> plus more" and a multi-line draft whose first line is <hcom>. Every earlier gate (idle, approval, typing, overlays, submit settle) still applies. Tests: the scrape recognizes only a lone, typed, single-row <hcom> (not dim placeholder text, not a longer or multi-line draft); the gate submits it but still waits for idle and for the user; and an end-to-end hook test where the leftover is submitted with Enter alone and the batch is delivered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the
Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to
|
[Medium impact] The PR appears safe to merge; no new actionable issue was established. SummaryFixes Claude delivery after an empty bare
|
Two ways the leftover-wake gate could submit a user's unfinished draft: The first Enter and every WaitTextClear retry checked only that the prompt row reads <hcom>. That stays true after the user adds a second line, so a draft starting with the trigger could be submitted once typing paused. A leftover-wake submission now also needs the scrape's leftover_wake before each Enter; otherwise delivery goes back to Pending. The bottom border was taken to be the first row below the prompt containing a box-drawing dash, so a draft line such as " ─ draft notes" passed as the border and the prompt as a single row. The border row must now be a full dash rule, so such a draft fails closed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude also fires UserPromptSubmit for a prompt queued during a turn, so setting listening there marked a busy agent idle until its next tool hook. A blocked wake runs no turn: after Stop the row is already listening, and mid-turn it keeps the turn's own status. The re-check and notify are no longer needed, since an idle row lets any later message wake on its own.
…a failure Claude holds $.prompt.submit until the session is idle. When a turn delivers the batch first, the hook blocks the late wake as having nothing pending, and the mod reported it as submission-failed. That set submission_failed after attempted_cursor had already cleared, so nothing ever cleared it: no further wakes, and tui:mod-failed advising a switch to inject. A drop that arrives once the hook has acknowledged the wake is now logged as claude.mod_stale_wake.
Run inside an hcom-launched agent, HCOM_CLAUDE_DELIVERY=native put the delivery loop in plugin mode and failed the leftover-wake end-to-end test.
…e row is_dash_rule trims, so an indented `───` row in a draft passed as the composer's bottom border, and a pending message pressed Enter on the whole unfinished draft. The border must now be unindented and as wide as the top border; anything else reads as a draft and is left alone.
The callback cleared `submitting` before awaiting the report, so a poll in between could claim a wake for a newer message. hcom then judged the old drop against that newer claim and showed tui:mod-failed for a healthy wake.
# Conflicts: # src/pty/shared.rs
|
thanks |
Brings aannoo#164 (Codex approval latch clears on a negative scrape timer), aannoo#165 (Claude leftover <hcom> wake submitted with Enter), aannoo#163/aannoo#169 (Grok mail recovery and steer ack) and aannoo#171 (tmux panes closed on their owning server). Fork deltas in conflicted files: - src/pty/screen.rs: re-applied 362b479 Claude suggestion heuristic (is_claude_placeholder_or_suggestion) inside upstream's new ClaudeInputBox return; fork and upstream tests both kept. - src/pidtrack.rs: fork-only test_recovered_orphan_keeps_catalog_agent_name gets upstream's new tmux field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Hcom-Agent: hcom_dev
Two commits. Both fix what happens after Claude's UserPromptSubmit hook blocks a bare
<hcom>wake that found nothing pending. Each failure, on its own, leaves a PTY Claude agent idle with unread messages until something outside hcom starts a turn.Problem
When the PTY wrapper types
<hcom>and the hook finds nothing pending (another hook already delivered the batch), the hook blocks the prompt. After that:active. The hook marks the instanceactive/promptbefore blocking. A blocked prompt runs no turn, so no Stop hook follows to set it back tolistening. The PTY gate then holds every later message atnot_idle. Stability recovery needs 10 s of quiet screen output, which never comes while, for example, a background agent keeps the screen busy.<hcom>is guarded as a user draft. Claude puts a blocked prompt back into its input box. The gate then reads hcom's own trigger as user text and holds every later message atprompt_has_text.How it was observed
hcom 0.7.30 on macOS (arm64), Claude Code agents launched through hcom with PTY delivery.
<hcom>, so Claude queued the wake. That turn's own UserPromptSubmit delivered the pending batch. Stop setlistening, and 85 ms later Claude submitted the queued<hcom>. The hook found nothing pending, setactive/prompt, and blocked it. Every later message loggedGate blocked: not_idlefor 4.5 minutes (10 unread) until a manual wake.<hcom>reappeared in Claude's input box. The log showed 73Gate blocked: prompt_has_textattempts over 90 s with nothing typed, until an outside monitor started a turn.Fix
Commit 1: keep a blocked bare wake idle. In PTY mode, a bare wake that finds nothing pending now sets the status Stop sets (
listening, empty context) instead ofactive/prompt. It then checks for pending messages once more, so a message that arrived between the first check and the block is delivered by this wake rather than left behind. Any later message finds the instance idle and gets its own wake. Without the PTY wrapper nothing injects wakes, so the status is left as Stop's poll set it. The non-blocked path is unchanged (active/promptis still set for a real prompt).Commit 2: submit a leftover
<hcom>instead of guarding it.ScreenTracker::is_claude_leftover_wakereports when Claude's input box holds only<hcom>, typed (not dim placeholder text) and on a single row.leftover_wake, and the delivery loop presses Enter alone, without typing anything. The render phase re-checks ownership, approval and user typing before the Enter. The UserPromptSubmit hook then delivers the pending batch as usual.<hcom> plus moreand a multi-line draft whose first line is<hcom>.Tests
Commit 1 (
src/hooks/claude.rs):test_blocked_bare_wake_stays_idle_so_the_next_message_wakes_it: replays Stop, then a blocked bare wake. The row islistening, the Claude PTY gate passes for a new message, and the next wake delivers it.test_bare_wake_delivers_a_message_that_lands_before_it_blocks: a message that lands between the first check and the block is delivered by that wake.Commit 2:
src/pty/screen.rs):claude_leftover_wake_is_only_a_lone_typed_triggerandclaude_dim_trigger_is_placeholder_not_a_leftover_wake.src/delivery.rs):gate_submits_a_leftover_wake_but_never_a_draft,gate_leftover_wake_still_waits_for_idle_and_the_userandleftover_wake_is_rendered_text_for_the_submit_phase.src/pty/shared.rs):claude_gate_submits_only_a_lone_leftover_wake.src/hooks/claude.rs):test_leftover_wake_in_the_prompt_is_submitted_with_enter_alone.Results
Local run on this branch (on
main669a067), macOS arm64, rustc 1.99.0 (the repo pins 1.97.1),cargo test --bin hcomwith the test names as filters.cargo fmt --checkis clean. Clippy and the full suite were not run on this branch.active/promptagain, as before):test_blocked_bare_wake_stays_idle_so_the_next_message_wakes_itfails. The race test still passes, since it only checks delivery.gate_submits_a_leftover_wake_but_never_a_draft,claude_gate_submits_only_a_lone_leftover_wakeandtest_leftover_wake_in_the_prompt_is_submitted_with_enter_alonefail, each held atprompt_has_text.is_claude_leftover_wakealways false): the scrape test, the screen-to-gate test and the end-to-end test fail.Related
These may be related, but this PR does not claim to fix them:
<hcom>prompts #100 (open), "pty delivery retries flood a busy agent with empty<hcom>prompts".Both are in the same area: Claude, PTY delivery, and
<hcom>wakes that do not lead to delivery. Both already have maintainer comments pointing to earlier retry fixes in "0.7.2.4" and asking for logs. The failures fixed here are narrower (a blocked bare wake leaves the rowactive, or leaves<hcom>in the box). They might explain some reports of an agent that stops receiving, but that is not confirmed.Context only: #110 is a merged Codex placeholder-parsing fix. #114 is historical Antigravity composer/banner context; the maintainer reported that newer Antigravity changed its TUI banner and tested successfully on 1.2.11. Neither establishes this PR's failure or fix.
🤖 Generated with Claude Code