#21693: feat(gateway): expose session entry id on chat history messages
gateway
size: S
## Summary
- `readSessionMessages()` now copies the Pi session entry `id` onto each message object returned to clients
- Enables the WebChat UI to use a stable, server-assigned identifier for exact message dedup during history reconciliation — replacing the current fuzzy heuristic (role + content + timestamp ±30s)
- Entry id is only added when the message does not already carry its own `id` field, so existing messages are never clobbered
## Test plan
- [x] Added test: entry IDs are preserved on messages when present in transcript
- [x] Added test: messages without entry IDs are returned unchanged
- [x] Added test: a message's own `id` field is never overwritten by the entry ID
- [x] All 38 existing tests in `session-utils.fs.test.ts` continue to pass
- [x] Lint and format hooks pass with 0 warnings
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR adds session entry IDs to chat history messages returned by `readSessionMessages()`, enabling the WebChat UI to use stable server-assigned identifiers for exact message deduplication instead of the current fuzzy heuristic based on role + content + timestamp.
**Changes:**
- Modified `readSessionMessages()` in `session-utils.fs.ts` to copy the Pi session entry `id` field onto message objects when the entry has an ID and the message doesn't already have its own `id` field
- Added two comprehensive tests validating that entry IDs are preserved when present and that existing message `id` fields are never overwritten
- All 38 existing tests continue to pass
**Implementation quality:**
- The logic correctly checks both that `parsed.id` is a string and that the message doesn't already have an `id` field using the `in` operator
- Safe spread operator usage creates a shallow copy with the new field
- Proper handling of edge cases (non-string IDs, missing IDs, existing message IDs)
- Follows existing code patterns in the file
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk
- The implementation is straightforward, defensive, and well-tested. It adds a non-breaking feature (copying entry IDs to messages) with proper guards to prevent overwriting existing IDs. The change is backward-compatible, has comprehensive test coverage including edge cases, and all existing tests pass. The code follows repository conventions and handles all identified edge cases correctly.
- No files require special attention
<sub>Last reviewed commit: 3f68622</sub>
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#22798: feat(webchat): ChatGPT-style multi-chat threads with generated titles
by opnsec · 2026-02-21
78.3%
#19722: feat(chat): add search functionality for chat history
by aleiby · 2026-02-18
78.2%
#14966: fix(webchat): preserve user message visibility after chat.send
by BenediktSchackenberg · 2026-02-12
77.9%
#19343: Refactor chat state management: reset chat messages and queue on se...
by saurav470 · 2026-02-17
77.8%
#14309: fix(ui): resolve chat event session key mismatch
by justonlyforyou · 2026-02-11
77.6%
#17527: fix(gateway): allow WebChat to attach to main session regardless of...
by Glucksberg · 2026-02-15
76.5%
#18232: fix: webchat rapid messages create orphan sessions
by MisterGuy420 · 2026-02-16
76.2%
#18694: fix(ui): reset session key to main after /new in webchat
by Phineas1500 · 2026-02-17
75.6%
#15564: fix: webchat messages disappear during concurrent session activity
by Automatedcapitalist · 2026-02-13
75.4%
#16006: fix(gateway): reduce chat.history byte cap from 6 MB to 2 MB
by fagemx · 2026-02-14
75.2%