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
- Configure Feishu and make sure the DM routes to a session using
Runtime: OpenAI Codex.
- Open the corresponding session in local WebChat / Control UI.
- Send a new Feishu DM to the bot.
- 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:
- 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.
- Keep the existing final
mirrorTranscriptBestEffort(...) call.
- 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.
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
2026.5.12OpenAI Codexsession.dmScope:per-channel-peeragent:main:feishu:direct:<open_id>Reproduction
Runtime: OpenAI Codex.Actual behavior
gateway.logshows the inbound message is received and dispatched immediately:received message from ...dispatching to agent (session=agent:main:feishu:direct:...)role=userentry at inbound time.Expected behavior
The inbound
role=usermessage 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.jsdist/session-BGECYHCy.jsdist/store-3qAZ3Zl6.jsFor Codex sessions, the real transcript write happens later through the Codex app-server transcript mirror:
mirrorTranscriptBestEffort(...)is called after the turn ends indist/run-attempt-DValQTsj.jsbuildResult()constructsmessagesSnapshotonly at that pointSo 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.historyrebuilt 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:
turnId, append the inbound prompt to the transcript immediately with the same logical mirror identity used later:${turnId}:prompt.mirrorTranscriptBestEffort(...)call.This preserves the current end-of-turn mirror flow while making inbound messages visible immediately in WebChat / Control UI.
Related note
Using
Date.now()insidebuildResult()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.