[BUG] Mods: `$.prompt.submit` on an idle session sometimes never enters the queue and never settles, then is dropped when the user sends a message

Status Open
Reported on v2.1.296
Maintainer reply None cached
Activity 0 comments · opened Oct 11, 2026

What happens

In the CLI, a plugin's function hook calls $.prompt.submit while the session is idle. Usually this works: the transcript gets a queue-operation enqueue/dequeue and the turn starts within milliseconds.

Sometimes, though, nothing happens at all:

  • no queue-operation row in the transcript
  • no turn starts (turn.start is not raised)
  • the promise returned by $.prompt.submit neither resolves nor rejects

It stays that way until the user sends their own message. When the user's turn starts, the held submit settles as a drop. The plugin's prompt is never run as a turn of its own.

For a plugin that wakes the agent on external events (ours watches pull requests and submits when CI fails or a PR is merged), the agent never wakes, and the user has to type something to "unstick" it.

This is different from #96336. There, the turn runs but the Desktop does not draw it. Here, in the CLI, the turn never starts and nothing is enqueued.

Environment

  • Claude Code 2.1.296 (CLI), macOS 26.7.1, running inside a terminal multiplexer (cmux)
  • Plugin with a function-hooks module (hooks/hooks.json → {"modules": ["./register.tsx"]})

How the plugin submits

// called from a $.process.spawn stream handler / $.clock.after callback, not from inside a hook chain
const box = await $.prompt.read()
if (!turnRunning && box.text.trim() === '') {
  void $.prompt.submit({ text }).then(
    r => (r.drop !== undefined ? onDropped(r.drop) : onEntered()),
    err => onDropped(String(err)),
  )
}

Its own turn.start / turn.complete hooks track whether a main-loop turn is running.

Timeline from one occurrence (UTC)

| Time | Event |
|---|---|
| 07:29:43 | The plugin submits while idle. The transcript has queue-operation enqueue + dequeue, then the user row "The <plugin> plugin sent a message". A turn runs normally |
| 07:29:59 | Turn ends. The session is idle from here |
| 07:33:01 | away_summary row |
| ~07:34–07:35 | The plugin submits again while idle (the box is empty and no turn is running) |
| 07:34 → 07:40 | The transcript has nothing: no queue-operation, no turn, no user row |
| 07:40:33 | The user sends a message |
| 07:40:34.5 | The held submit settles as a drop. The plugin then appends the same text to the running user turn with $.session.append (an isMeta user row) |
| after | No late turn from the held submit ever appears (checked for 1 hour) |

$.session.append is documented as "listed at once", so the 07:40:34 row means the text was still held at 07:40.

How often

We found 5 occurrences in 5 days, across 3 sessions in different repositories. All had the same shape:

  • an idle-time submit with no transcript record
  • delivery 0.8–3 s after the user's next prompt
  • the held submit never ran later

Many other idle-time submits in the same sessions worked normally. In 1 of the 5 there was an away_summary before the submit; in the others there was not, so it does not seem to be the only condition. We could not find what makes the engine hold the prompt.

The type docs only say a plugin's submit "runs once it is idle" (PromptSubmitInput.turnId, $.prompt.submit). They do not define idle or say that a submit can be held without settling.

Expected

Either:

  • the submit enters the queue and starts a turn (as it usually does), or
  • the promise settles ({ drop: reason } or a rejection) so the plugin can retry or tell the user.

At minimum, a submit that is being held should be visible somewhere, such as a queue-operation row.

Workaround we use

If the promise has not settled after 60 s and no turn has started, we give up on that submit, submit the same text again once, and ignore the late drop of the abandoned one. Because a submit cannot be cancelled, this delivers twice if the held submit ever does enter later. We have not seen that happen.

Questions

  1. Under which conditions does the engine hold a plugin's $.prompt.submit on an idle session without enqueueing it?
  2. Is a held submit guaranteed to settle (drop) when the next user prompt starts, or could it still enter later?
  3. Is there a supported way to cancel a pending $.prompt.submit?

View original on GitHub ↗