Nudge doorbells that land in the input line without submitting #3

Merged
forgejo-admin merged 1 commit from fix-mail-doorbell-stall into main 2026-07-31 20:15:20 +00:00

The symptom

A spawned lunacycle agent sat idle with its doorbell visible in the input line but never submitted:

❯ 📬 mail from w1D:p1: Prod smoke checks smoke_spa + smoke_ws fail on their
  first-ever real run. run '/Users/pbanka/.../bin/mail read 84'▏

Meanwhile everything reported healthy — event-bus status healthy, list-pending empty, message 84 intact in the DB. Delivery had "succeeded" as far as mail was concerned. The agent looked idle for no reason and the mail went unread.

Why it was invisible

_deliver_one fired and forgot:

herdr("agent", "prompt", pane, text, check=False)   # outcome discarded

A swallowed Enter was indistinguishable from a delivered doorbell. Nothing logged, nothing retried.

The fix

Capture the recipient's status before prompting. If it was not working and is still not working ~2s later, the submit didn't take — send one enter to flush the buffer.

  • A busy recipient is skipped entirely: Claude Code queues text typed during a turn and submits it afterward, so there's nothing to fix and nothing to race.
  • A false positive presses Enter on an empty prompt, which is a no-op. That's what makes it safe to do unconditionally rather than gate on fragile heuristics.

Placed in _deliver_one because every doorbell routes through it — spawn kickoffs and ordinary mail alike.

Verification

Both primitives tested against a live agent:

step result
pane send-text (no Enter) text sits in input line, status idlereproduces the reported state exactly
pane send-keys enter flushes it, status → done
real mail through the dispatcher, into a deliberately jammed input line doorbell landed, agent read the mail, replied NUDGE-OK

Also agent_status() returns None cleanly for a nonexistent pane.

Honest limitation

The original stall did not reproduce on demand. I tried zero-delay prompting immediately after agent start (submitted cleanly, PONG) and the exact 156-char emoji/quoted doorbell string (submitted cleanly, READ-OK). So this fixes the failure class — a prompt that lands without submitting — rather than a confirmed mechanism.

That's deliberately why it's a cheap idempotent nudge rather than a redesign: it costs one status read per delivery to a non-busy recipient, and it can't make anything worse. If stalls persist after this, the next step is logging agent prompt failures to ~/.herdr-mail/ so there's evidence to work from instead of a screenshot.

Docs updated in herdr-mail/README.md (v1 limits) and skill/SKILL.md (manual flush: herdr pane send-keys <pane> enter, plus a reminder that the message is durable and mail inbox shows it regardless).

🤖 Generated with Claude Code

## The symptom A spawned lunacycle agent sat idle with its doorbell **visible in the input line but never submitted**: ``` ❯ 📬 mail from w1D:p1: Prod smoke checks smoke_spa + smoke_ws fail on their first-ever real run. run '/Users/pbanka/.../bin/mail read 84'▏ ``` Meanwhile everything reported healthy — `event-bus status` healthy, `list-pending` empty, message 84 intact in the DB. Delivery had "succeeded" as far as mail was concerned. The agent looked idle for no reason and the mail went unread. ## Why it was invisible `_deliver_one` fired and forgot: ```python herdr("agent", "prompt", pane, text, check=False) # outcome discarded ``` A swallowed Enter was indistinguishable from a delivered doorbell. Nothing logged, nothing retried. ## The fix Capture the recipient's status before prompting. If it was **not** working and is **still** not working ~2s later, the submit didn't take — send one `enter` to flush the buffer. - A **busy** recipient is skipped entirely: Claude Code queues text typed during a turn and submits it afterward, so there's nothing to fix and nothing to race. - A **false positive** presses Enter on an empty prompt, which is a no-op. That's what makes it safe to do unconditionally rather than gate on fragile heuristics. Placed in `_deliver_one` because every doorbell routes through it — spawn kickoffs and ordinary mail alike. ## Verification Both primitives tested against a live agent: | step | result | |---|---| | `pane send-text` (no Enter) | text sits in input line, status `idle` — **reproduces the reported state exactly** | | `pane send-keys enter` | flushes it, status → `done` | | real mail through the dispatcher, into a deliberately jammed input line | doorbell landed, agent read the mail, replied `NUDGE-OK` | Also `agent_status()` returns `None` cleanly for a nonexistent pane. ## Honest limitation **The original stall did not reproduce on demand.** I tried zero-delay prompting immediately after `agent start` (submitted cleanly, `PONG`) and the exact 156-char emoji/quoted doorbell string (submitted cleanly, `READ-OK`). So this fixes the *failure class* — a prompt that lands without submitting — rather than a confirmed mechanism. That's deliberately why it's a cheap idempotent nudge rather than a redesign: it costs one status read per delivery to a non-busy recipient, and it can't make anything worse. If stalls persist after this, the next step is logging `agent prompt` failures to `~/.herdr-mail/` so there's evidence to work from instead of a screenshot. Docs updated in `herdr-mail/README.md` (v1 limits) and `skill/SKILL.md` (manual flush: `herdr pane send-keys <pane> enter`, plus a reminder that the message is durable and `mail inbox` shows it regardless). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Observed on a spawned lunacycle agent: the doorbell text was typed into the
recipient's prompt but never submitted. The agent sat idle with the mail
visible in its input line and unread, while the bus reported healthy and
nothing pending — because delivery had "succeeded" as far as mail knew.

`_deliver_one` fired `herdr agent prompt` and ignored the outcome entirely
(check=False), so a swallowed Enter was indistinguishable from a delivered
doorbell.

Now: capture the recipient's status before prompting. If it was not working and
is still not working ~2s later, the submit did not take — send one `enter` to
flush the buffer. A busy recipient is skipped, since it queues typed text and
submits after its turn. A false positive presses Enter on an empty prompt,
which is a no-op.

Verified the two primitives against a live agent: `pane send-text` leaves text
in the input line with status idle (reproducing the reported state exactly),
and `pane send-keys enter` flushes it to done. Also confirmed normal delivery
still works end to end through a deliberately jammed input line — doorbell
landed, agent read the mail and replied.

Note: the original stall did not reproduce on demand (zero-delay prompting
after agent start, and the exact 156-char emoji/quoted doorbell text, both
submitted cleanly). This fixes the failure class rather than a confirmed
mechanism, which is why it is a cheap idempotent nudge rather than a redesign.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo-admin/herdrmastr!3
No description provided.