Skip to content

ACP "redirect" fabricates empty ~1ms turn_completes when the agent advertises steering=false, wedging the conversation (cancelled/pending_review) and swallowing the send #590

Description

@lumberjackchef

Summary

When a user sends a prompt to an ACP agent that does not support mid-turn steering, Codeg's "Redirected the active turn with your correction" flow fabricates an empty turn_complete ~1ms later, moves the conversation to cancelled or pending_review (the status is not deterministic), and leaves the row unable to send. The prompt is either lost entirely, or — worse — it does land in the agent's store and gets answered, but the reply never streams back because the turn was already closed client-side.

Observed with: codeg-server v0.28.1 (native binary), agent = Hermes 0.20.5 via hermes acp, which advertises steering: advertised=false. Four incidents on our fleet in 48 hours (JAR-1103, JAR-1133, JAR-990, and another today), all on the same production server.

Wire evidence (server log, connection d7f4cd4d-…, conversation "Tackle App Launch review batch")

Real turns take seconds to minutes:

2026-08-28T00:01:41.494228Z  status_changed session=...0278c6f6... Connected -> Prompting
2026-08-28T00:05:18.239865Z  turn_complete session=...0278c6f6... stop_reason=end_turn   # 3m37s — real

Two sends while the agent had a live turn that was invisible to Codeg's session state (an out-of-band background-process notification had just been injected into the session at 00:30:00Z):

2026-08-28T00:31:26.905952Z  status_changed session=...0278c6f6... Connected -> Prompting
2026-08-28T00:31:26.907866Z  turn_complete session=...0278c6f6... stop_reason=end_turn   # 1.9 ms — synthetic, empty

2026-08-28T00:33:29.680022Z  status_changed session=...0278c6f6... Connected -> Prompting
2026-08-28T00:33:29.680733Z  turn_complete session=...0278c6f6... stop_reason=end_turn   # 0.7 ms — synthetic, empty

After that: the conversation row stayed pending_review (an earlier incident flipped to cancelled instead — non-deterministic), every further send failed with no surfaced error, and the agent's real turn_complete never arrived (row still stuck 10+ minutes later; auto-reconcile is not guaranteed).

The insidious part — the first send (00:31:26) was lost for good (never reached the agent store), but the second (00:33:29.680Z, same instant as the synthetic completion) landed and was answered: the agent persisted its reply at 00:34:10 (finish_reason=stop), and it never streamed to the tab because the turn had already been closed client-side.

Diagnostic signature for anyone reproducing: grep the server log for the session uuid and look for Prompting → turn_complete pairs with sub-2ms deltas (an earlier incident had three swallowed sends within 8s).

Trigger hypothesis

The redirect path appears to be attempted unconditionally when a send races an agent turn, even when the agent advertised steering: advertised=false. Two candidate triggers, both observed today:

  1. Invisible live turn — a background-process notification was injected out-of-band into the session (user-role message, 00:30:00Z), so the agent was mid-turn while Codeg's session state said idle. The send became a "redirect" that could not be delivered.
  2. Stale session identity after compression rotation — Hermes rotated its session id mid-conversation (compression, 11:48Z); Codeg's row still maps the old id. Lineage resolution makes load_session work, but the conversational redirect/status logic may key on stale state.

Either way the agent cannot accept the correction, so an empty completion is synthesized and the row's status is flipped — which is the destructive part.

Suggested fixes (any one of these materially helps)

  1. When steering: advertised=false, never attempt the redirect flow for mid-turn sends. Queue the prompt for the next turn, or surface a clear "agent is busy" state, instead of fabricating a completion.
  2. Don't flip a regular conversation's status (cancelled/pending_review) from an empty synthetic completion — keep the row sendable and the client-stream in sync.
  3. If the prompt was passed to the agent anyway (as observed), match its eventual real turn_complete to the conversation instead of discarding it (same direction as Complete grok turns when the session/prompt response is lost #553 for grok).
  4. #539 (journal sent prompts) would at least make recovery lossless.

Environment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions