Skip to content

[Bug]: Codex runtime delays inbound user transcript writes until turn end, so WebChat/Control UI cannot see external messages immediately #83528

@sgh6688

Description

@sgh6688

Summary

When a Feishu DM is routed to a session that runs on Runtime: OpenAI Codex, the inbound user message is not mirrored into the session transcript immediately. As a result, WebChat / Control UI usually does not show the new inbound message until the Codex turn finishes and the assistant reply is mirrored.

This makes external-channel sessions feel delayed or invisible from WebChat even though the gateway has already received and dispatched the inbound message.

Environment

  • OpenClaw version: 2026.5.12
  • Runtime: OpenAI Codex
  • Channel: Feishu DM
  • session.dmScope: per-channel-peer
  • Observed session key shape: agent:main:feishu:direct:<open_id>

Reproduction

  1. Configure Feishu and make sure the DM routes to a session using Runtime: OpenAI Codex.
  2. Open the corresponding session in local WebChat / Control UI.
  3. Send a new Feishu DM to the bot.
  4. Before the assistant replies, watch the session UI and the session JSONL transcript.

Actual behavior

  • gateway.log shows the inbound message is received and dispatched immediately:
    • received message from ...
    • dispatching to agent (session=agent:main:feishu:direct:...)
  • But the session transcript does not get the new role=user entry at inbound time.
  • WebChat / Control UI therefore keeps showing the previous completed turn until the Codex turn ends.
  • After the turn completes, the inbound user message and the assistant reply appear together.

Expected behavior

The inbound role=user message should be appended to the session transcript as soon as the external message is accepted for the target session, so WebChat / Control UI can show it immediately. Assistant / tool results can continue to append later as the turn progresses or finishes.

Root cause

The channel-turn record path updates session metadata / lastRoute, but does not append the actual inbound user transcript entry:

  • dist/kernel-5-rDHkvC.js
  • dist/session-BGECYHCy.js
  • dist/store-3qAZ3Zl6.js

For Codex sessions, the real transcript write happens later through the Codex app-server transcript mirror:

  • mirrorTranscriptBestEffort(...) is called after the turn ends in dist/run-attempt-DValQTsj.js
  • buildResult() constructs messagesSnapshot only at that point
  • the user message in that snapshot is synthesized there as:
const messagesSnapshot = [attachCodexMirrorIdentity({
  role: "user",
  content: this.params.prompt,
  timestamp: Date.now()
}, `${turnId}:prompt`)];

So the inbound user message is effectively mirrored at turn-finalization time, not at inbound-dispatch time.

Why this looks wrong in UI

WebChat / Control UI reads the session transcript projection (chat.history rebuilt from durable transcript), not the raw external-channel inbound webhook stream. If the transcript does not receive the user message immediately, the UI has nothing durable to show for that inbound turn.

Suggested fix

For Codex app-server turns:

  1. As soon as the turn has a stable turnId, append the inbound prompt to the transcript immediately with the same logical mirror identity used later: ${turnId}:prompt.
  2. Keep the existing final mirrorTranscriptBestEffort(...) call.
  3. Let existing idempotency-key / mirror-identity dedupe skip the prompt when the final snapshot mirror runs.

This preserves the current end-of-turn mirror flow while making inbound messages visible immediately in WebChat / Control UI.

Related note

Using Date.now() inside buildResult() for the synthesized user message also means the mirrored user timestamp reflects turn-finalization time rather than true inbound time. Even if the delayed mirror is intentional, the timestamp is still misleading.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions