Skip to content

Make turn/steer retries idempotent by clientUserMessageId #32254

Open

Description

@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:

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

Activity

  1. FayAndXan commented on Jul 20, 2026

    @FayAndXan

    Downstream production reproduction reinforcing the need for durable, idempotent turn/steer:

    On 2026-07-20, OpenClaw 2026.7.1-beta.5 sent a Telegram message into an active native Codex turn. The message was echoed externally 748 ms after ingress, while the real assistant answer arrived separately later. The retained Codex transcript shows the text only as a userMessage, so the echo appears to be an OpenClaw delivery/provenance defect rather than Codex generating that answer.

    However, recovery analysis still reaches the gap described here: after turn/steer acceptance, the downstream client cannot safely distinguish “persisted but acknowledgement/notification was lost” from “not persisted.” Retrying the same clientUserMessageId may duplicate the user item; not retrying may lose it.

    Is durable, thread-scoped idempotency for clientUserMessageId still planned, and will app-server advertise it as a protocol capability rather than requiring version detection? The interoperability tests we need are:

    • same ID and same input, including after restart → one user item
    • same ID with different input → structured conflict
    • accepted request followed by disconnect/crash → reconcilable through thread/read or resume history
    • correlated userMessage.clientId emitted only after durable adoption

    We can provide further sanitized downstream evidence if helpful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    app-serverIssues involving app server protocol or interfacesenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions