Skip to content

fix(gateway): expose idempotencyKey on chat.history __openclaw envelope (#79844)#80044

Closed
Sanjays2402 wants to merge 1 commit into
openclaw:mainfrom
Sanjays2402:fix/79844-idempotency-key-history
Closed

fix(gateway): expose idempotencyKey on chat.history __openclaw envelope (#79844)#80044
Sanjays2402 wants to merge 1 commit into
openclaw:mainfrom
Sanjays2402:fix/79844-idempotency-key-history

Conversation

@Sanjays2402

Copy link
Copy Markdown
Contributor

Fixes #79844.

Problem

When a user message is sent with an idempotencyKey, that key is persisted on the transcript message but only surfaces as a top-level message field on hydration. Clients consuming chat.history (and the SSE inline stream) expect identity-style metadata to live inside the __openclaw envelope alongside seq, id, importedFrom, etc., so they can correlate echoed messages without sniffing top-level fields.

Change

Hoist the persisted idempotencyKey into the __openclaw envelope at hydration time:

  • parsedSessionEntryToMessage in src/gateway/session-utils.fs.ts — covers chat.history full reads and recent-tail reads.
  • SessionHistorySseState.appendInlineMessage in src/gateway/session-history-state.ts — covers the live inline SSE append.
  • broadcastSessionMessage in src/gateway/server-session-events.ts — covers the inline broadcast path.

The original top-level idempotencyKey field is preserved on the message object for back-compat, so existing readers do not regress.

Tests

  • src/gateway/session-utils.fs.test.ts — new test verifies idempotencyKey surfaces on both full and recent-tail reads, and is absent when the persisted message has no key.
  • src/gateway/session-history-state.test.ts — new test verifies inline-append surfaces idempotencyKey in __openclaw alongside id/seq.

Verification

  • pnpm tsgo:core — clean
  • pnpm tsgo:core:test — clean
  • node scripts/run-vitest.mjs run --config test/vitest/vitest.gateway.config.ts src/gateway/session-utils.fs.test.ts src/gateway/session-history-state.test.ts — 92/92 pass
  • node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.core.json over the touched files — clean

…pe (openclaw#79844)

When a user message is sent with an idempotencyKey, that key is persisted on
the transcript message but was only available as a top-level message field on
hydration. Clients consuming chat.history (and the SSE inline message stream)
expect identity-style metadata to live inside the __openclaw envelope alongside
seq, id, importedFrom, etc.

Hoist the persisted idempotencyKey into __openclaw at hydration time:
- parsedSessionEntryToMessage (chat.history reads + recent-tail reads)
- SessionHistorySseState.appendInlineMessage (live SSE inline append)
- broadcastSessionMessage in server-session-events (inline broadcast path)

The original top-level field is preserved for back-compat, so no existing
reader breaks.

Tests:
- session-utils.fs.test.ts: idempotencyKey surfaces on full + recent reads
- session-history-state.test.ts: idempotencyKey surfaces on inline append
@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: S triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 10, 2026
@clawsweeper

clawsweeper Bot commented May 10, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge.

Summary
The PR adds idempotencyKey to Gateway session-history __openclaw metadata during transcript hydration, inline SSE append projection, and session message broadcasts, with focused tests.

Reproducibility: yes. by source inspection. A transcript row with message.idempotencyKey reaches the current-main hydration helper, which only attaches id and seq, and the PR’s live SSE path still depends on a real chat.send update shape that does not include the key.

Real behavior proof
Needs real behavior proof before merge: Needs real behavior proof before merge: the PR lists tests/lint/typecheck only, not redacted live output, logs, a recording, or an artifact showing chat.send plus chat.history or SSE after the patch. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, ask a maintainer to comment @clawsweeper re-review.

Next step before merge
External PR needs contributor real-behavior proof, a branch refresh, and a focused live SSE-path fix before maintainers can merge it; automation should not repair while the contributor proof gate is unsatisfied.

Security
Cleared: No concrete security or supply-chain concern found; the diff only echoes already-authorized client metadata and adds tests, with no dependency, workflow, secret, or install-script changes.

Review findings

  • [P2] Propagate the key into real chat.send SSE updates — src/gateway/session-history-state.ts:232-236
Review details

Best possible solution:

Carry the client idempotency key at the canonical chat.send transcript message/update point, then expose it as __openclaw.idempotencyKey in full history, recent history, session-message broadcasts, and live session-history SSE with targeted runtime coverage.

Do we have a high-confidence way to reproduce the issue?

Yes, by source inspection. A transcript row with message.idempotencyKey reaches the current-main hydration helper, which only attaches id and seq, and the PR’s live SSE path still depends on a real chat.send update shape that does not include the key.

Is this the best way to solve the issue?

No. The history hydration change is the right direction, but the live SSE fix should pass the client key into the canonical chat.send transcript update instead of reading a field that real updates do not populate.

Full review comments:

  • [P2] Propagate the key into real chat.send SSE updates — src/gateway/session-history-state.ts:232-236
    The new inline SSE code copies from update.message.idempotencyKey, but the real chat.send update is built by buildChatSendTranscriptMessage, which returns only role/content/timestamp/media fields. Live chat.send session-history events will still miss __openclaw.idempotencyKey; pass the client key into the emitted transcript update and cover that real path.
    Confidence: 0.82

Overall correctness: patch is incorrect
Overall confidence: 0.82

What I checked:

  • Current main hydration omits envelope idempotencyKey: parsedSessionEntryToMessage on current main attaches only transcript id and seq to __openclaw for normal message records, so a persisted top-level message.idempotencyKey is not copied into the envelope. (src/gateway/session-utils.fs.ts:720, 862be9fb3d6c)
  • chat.history uses the omitted hydration path: The chat.history handler reads local transcript rows through readRecentSessionMessagesAsync before projecting them into the response, so the missing metadata remains missing in the RPC result. (src/gateway/server-methods/chat.ts:1782, 862be9fb3d6c)
  • PR SSE code reads from a synthetic field: The PR adds idempotencyKey extraction from update.message in SessionHistorySseState.appendInlineMessage, and its new test supplies a synthetic inline message that already contains the key. (src/gateway/session-history-state.ts:232, d0de65b289a7)
  • Real chat.send inline updates do not carry the key: On current main, buildChatSendTranscriptMessage returns only role/content/timestamp/media fields, and emitSessionTranscriptUpdate passes that object as update.message; update.message.idempotencyKey is therefore undefined for real chat.send SSE updates. (src/gateway/server-methods/chat.ts:867, 862be9fb3d6c)
  • Existing contract supports a narrow projection fix: The Gateway schema requires chat.send callers to provide a non-empty idempotencyKey, and transcript append code already preserves explicit idempotency keys on persisted message records. (src/gateway/protocol/schema/logs-chat.ts:35, 862be9fb3d6c)
  • Latest shipped release still has the omission: The latest release tag v2026.5.12 points at f066dd2f31c231f38fbcaacd6f6dfce0801143b3; its parsedSessionEntryToMessage also attaches only id and seq, so this is not already shipped fixed behavior. (src/gateway/session-utils.fs.ts:720, f066dd2f31c2)

Likely related people:

  • steipete: GitHub file history shows repeated recent work on session transcript reads, session-history state, server-session-events, and the Gateway chat handler around the affected history/SSE paths. (role: recent area contributor; confidence: high; commits: 3fa9658b392e, 4d9c658f4058, 6147e1b91d3e; files: src/gateway/session-utils.fs.ts, src/gateway/session-history-state.ts, src/gateway/server-session-events.ts)
  • samzong: Recent commits touched transcript update sequencing, agent event fanout, and incremental chat delta payloads adjacent to the live session-history path. (role: recent event/SSE contributor; confidence: medium; commits: c20224688c99, bb8aa0cfe2b0, 10315ce21593; files: src/gateway/session-history-state.ts, src/gateway/server-session-events.ts, src/gateway/server-methods/chat.ts)
  • vincentkoc: Recent history shows transcript hot-path and bounded transcript read work in the files that hydrate and count session transcript records. (role: adjacent transcript hot-path contributor; confidence: medium; commits: d122a3492d17, a32fe82bd21b, 23e0be355ac3; files: src/gateway/session-utils.fs.ts, src/config/sessions/transcript.ts)

Remaining risk / open question:

  • The PR body still has no after-fix real Gateway proof for chat.send plus chat.history or the SSE path.
  • The PR head is currently merge-conflicting against main.
  • The proposed SSE test covers a synthetic inline update that already contains idempotencyKey, not the real chat.send update path.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 862be9fb3d6c.

@Sanjays2402

Copy link
Copy Markdown
Contributor Author

Closing — stale (15+ days). Will reopen / resubmit if still relevant.

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

Labels

gateway Gateway runtime size: S triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chat.history: surface idempotencyKey on hydrated user messages for client-side correlation

1 participant