Summary
Clicking Send now for a queued message can leave a Codex chat permanently marked in-flight even though the native Codex session later emits task_complete. The chat then stops ingesting messages, queue pause requests time out, and recovery requires restarting Garcon.
Observed on deployed main commit 12b166f888ac2c6427ec226711f81ab48392a8f9.
Public incident snapshot: https://garcon-backtest.chiayong.com/shared/LfVl0HTGmFQrsgchgPAa-T0UvzHDdCEF
Reproduction / incident sequence
- Run a Codex turn.
- Queue another message while the turn is active.
- Click Send now (
agent-interrupt-and-send).
- In this incident, Garcon logged an in-flight metadata overwrite while Codex continued running.
- Native Codex eventually completed, but Garcon did not ingest the remaining messages or clear the active state.
- A later queue pause failed with
Failed to pause queue: signal timed out.
Evidence
Chat: 1784772965538199
Codex session: 019f8cc9-6984-7301-ac75-bca297be7691
UTC timeline:
02:36:27.111 — native Codex task_started
02:37:15.683 — last activity persisted by Garcon
02:37:25 — Garcon log: [agents:event-bus] agents: overwriting in-flight turn metadata for chat 1784772965538199
02:39:27.470 — native Codex emitted final agent_message
02:39:27.513 — native Codex emitted task_complete
- Garcon shared snapshot created later omitted everything after
02:37:15
- Queue pause subsequently timed out
Garcon itself remained healthy throughout:
garcon.service active, NRestarts=0
/api/v1/auth/status returned HTTP 200 in 1–7 ms
- no ENOSPC/OOM
This isolates the failure to per-chat execution/queue ownership, not service or disk health.
Suspected race
Relevant paths:
server/chat-execution/chat-execution-coordinator.ts
#interruptActiveTurn() calls #abortStop(...), then always calls #requestDrain(...) in finally
#performStop() coordinates attempt settlement and provider abort
server/agents/event-bus.ts
trackTurn() warns but still overwrites existing active metadata
- subsequent terminal events only dispatch when identity matches current metadata
server-agents/codex/src/agents/codex/app-server/runtime.ts
abort() interrupts and removes the active runtime session
Likely invariant violation: successor drain/launch can reach trackTurn() before prior turn ownership/event metadata is fully settled. trackTurn() then overwrites the old identity. A late completion from either side can no longer reconcile the correct turn, leaving Garcon stale even though native history has terminal proof.
PR #362 fixed stale client processing events from earlier turns, but this incident appears to be a server-side ownership/transcript reconciliation gap.
Expected behavior
- Send now atomically stops/settles the active attempt before launching the queued successor.
- Exactly one active turn identity exists per chat.
- Native terminal evidence (
turn/completed / task_complete) clears matching ownership and is imported even across WebSocket disconnects or interrupt races.
- Queue pause remains responsive.
Acceptance criteria
- Add deterministic regression test: active Codex turn + queued successor + Send now, with delayed/late terminal events from interrupted turn.
- Assert
trackTurn() never silently overwrites a live identity.
- Assert successor launches exactly once only after predecessor settlement.
- Assert late predecessor events cannot clear successor state.
- Assert successor final message is persisted and chat becomes idle.
- Assert queue pause returns promptly after completion.
- Add restart/native-history reconciliation test for a terminal native turn missing from Garcon state.
Summary
Clicking Send now for a queued message can leave a Codex chat permanently marked in-flight even though the native Codex session later emits
task_complete. The chat then stops ingesting messages, queue pause requests time out, and recovery requires restarting Garcon.Observed on deployed
maincommit12b166f888ac2c6427ec226711f81ab48392a8f9.Public incident snapshot: https://garcon-backtest.chiayong.com/shared/LfVl0HTGmFQrsgchgPAa-T0UvzHDdCEF
Reproduction / incident sequence
agent-interrupt-and-send).Failed to pause queue: signal timed out.Evidence
Chat:
1784772965538199Codex session:
019f8cc9-6984-7301-ac75-bca297be7691UTC timeline:
02:36:27.111— native Codextask_started02:37:15.683— last activity persisted by Garcon02:37:25— Garcon log:[agents:event-bus] agents: overwriting in-flight turn metadata for chat 178477296553819902:39:27.470— native Codex emitted finalagent_message02:39:27.513— native Codex emittedtask_complete02:37:15Garcon itself remained healthy throughout:
garcon.serviceactive,NRestarts=0/api/v1/auth/statusreturned HTTP 200 in 1–7 msThis isolates the failure to per-chat execution/queue ownership, not service or disk health.
Suspected race
Relevant paths:
server/chat-execution/chat-execution-coordinator.ts#interruptActiveTurn()calls#abortStop(...), then always calls#requestDrain(...)infinally#performStop()coordinates attempt settlement and provider abortserver/agents/event-bus.tstrackTurn()warns but still overwrites existing active metadataserver-agents/codex/src/agents/codex/app-server/runtime.tsabort()interrupts and removes the active runtime sessionLikely invariant violation: successor drain/launch can reach
trackTurn()before prior turn ownership/event metadata is fully settled.trackTurn()then overwrites the old identity. A late completion from either side can no longer reconcile the correct turn, leaving Garcon stale even though native history has terminal proof.PR #362 fixed stale client processing events from earlier turns, but this incident appears to be a server-side ownership/transcript reconciliation gap.
Expected behavior
turn/completed/task_complete) clears matching ownership and is imported even across WebSocket disconnects or interrupt races.Acceptance criteria
trackTurn()never silently overwrites a live identity.