Make turn/steer retries idempotent by clientUserMessageId

Open 💬 1 comment Opened Jul 10, 2026 by jalehman

What variant of Codex are you using?

Codex app-server v2 through an external client.

What feature would you like to see?

Make clientUserMessageId a durable, thread-scoped idempotency key for turn/steer.

External app-server clients can receive input from durable ingress queues. A client may lose its connection or exit after Codex accepts a steer but before the client commits its own acknowledgement. Retrying the request must not deliver the same user message to the active turn twice.

The requested contract is:

  • The first turn/steer request for an ID and payload durably records the acceptance before returning success.
  • A concurrent or later request with the same thread, ID, and payload returns the original accepted result without enqueueing another user message.
  • A request with the same thread and ID but different input returns a machine-readable idempotency conflict.
  • The accepted ID survives app-server and client restarts through Codex thread or rollout persistence.
  • item/started, item/completed, thread/read, and resume history preserve the client ID so clients can reconcile an uncertain request.

The protocol already exposes clientUserMessageId on turn/start and turn/steer. Codex also persists the ID in user-message history and emits it in item notifications. PR #24653 describes that field as a correlation mechanism. This request extends that mechanism with retry semantics at the component that owns steer acceptance.

Suggested public integration coverage:

  1. Send two concurrent steers with the same ID and payload; assert one user item and equivalent successful responses.
  2. Restart app-server and replay the same request; assert no second user item.
  3. Reuse the ID with different input; assert a structured conflict.
  4. Interrupt the client after acceptance and reconcile the result through thread/read or resume history.

Additional information

This arose while integrating Codex steering with OpenClaw's durable Telegram ingress and SQLite transcript. OpenClaw must finalize an ingress entry only after the steered message is durable in its authoritative transcript. Codex emits a correlated item/started after persisting the user item, but a client crash between Codex persistence and client-side persistence leaves the request outcome uncertain. Retrying can duplicate model delivery because Codex does not deduplicate the client ID.

Adjacent work:

  • #24653 adds and persists user input client IDs for correlation.
  • #30341 preserves a late steer during turn finalization, but does not deduplicate repeated client requests.
  • #25268 discusses durable queued input and notes that stable client IDs and idempotent retry would support reconnect recovery.

An OpenClaw follow-up fallback prevents message loss when this guarantee is unavailable, but same-turn steering still requires Codex-owned idempotent acceptance.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗