fix(statedb): preserve detected session-id keys on stale full-table saves#1526
fix(statedb): preserve detected session-id keys on stale full-table saves#1526lazyoft wants to merge 1 commit into
Conversation
…aves
A freshly-launched session's claude_session_id (and the gemini/opencode/codex
equivalents) could be silently wiped from state.db, surfacing as
"no session ID detected" on a session whose transcript is actively being
written.
Root cause: the per-tool conversation ids are detected asynchronously and
persisted shortly after launch, but any concurrent full-table save
(SaveWithGroups -> SaveInstances) whose in-memory snapshot has not yet
observed the id rebuilds tool_data with the id omitted (omitempty
zero-value). MergeToolDataExtras treated every typed/known key as
authoritative "including absence", so the omitted id cleared the
previously-persisted value instead of being preserved. The rename hook
(hook_name_sync) is the hottest such writer; it fires on every Claude hook.
Fix: treat the session-id keys ({claude,gemini,opencode,codex}_session_id
and their *_detected_at) as sticky in MergeToolDataExtras. When the new
blob OMITS a sticky key but the old row has it, carry the old value
forward. A non-empty new value still wins (a real resume/fork that changes
the id), and an explicit empty present in the new blob is honored as an
intentional clear -- only outright omission preserves.
Extends tool_data_extras_test.go: repoints the typed-absence test to a
non-sticky key (latest_prompt) and adds sticky coverage for preserve-on-
omit, non-empty-new-wins, and explicit-empty-clears.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wm3xPWp2Wm5B5QjSHkEnXD
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughMergeToolDataExtras now preserves selected session-id and detected-at JSON fields when new tool data omits them, while still allowing explicit new values and explicit empty values to replace existing ones. Tests were updated for omission, override, and explicit-clear cases. ChangesSticky session-id merge semantics
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 5 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
🤖 autopilot: CI green + Claude review clean; FLAGGED for local Codex dual-review before merge (data-sensitive area). The change modifies Generated by Claude Code |
|
🤖 autopilot: CI green + Claude review clean; FLAGGED for local Codex dual-review before merge (data-sensitive area). Review summary: The fix correctly reorders the sticky-key preservation logic in Generated by Claude Code |
|
🤖 autopilot: CI green + Claude review clean; FLAGGED for local Codex dual-review before merge (data-sensitive area: Generated by Claude Code |
|
🤖 autopilot 2026-06-29: CI still green; still FLAGGED for local Codex dual-review before merge (data-sensitive area — Generated by Claude Code |
|
🤖 autopilot: CI green + Claude review clean; FLAGGED for local Codex dual-review before merge (data-sensitive area: Generated by Claude Code |
|
🤖 autopilot: CI green + Claude review clean; FLAGGED for local Codex dual-review before merge (data-sensitive area). This PR modifies Generated by Claude Code |
Problem
A freshly-launched session's
claude_session_id(and thegemini/opencode/codexequivalents) can be silently wiped fromstate.db, surfacing as "no session ID detected" on a session whose Claude transcript is actively being written. Callers that resolve a session's transcript by its persisted id (e.g. a coordinator mapping its workers to~/.claude/projects/<uuid>.jsonl) intermittently lose the mapping within seconds of launch.Root cause
The per-tool conversation ids are detected asynchronously and persisted shortly after launch. But any concurrent full-table save —
SaveWithGroups→SaveInstances→saveInstancesOnce— rebuilds each row'stool_datafrom its in-memoryInstance. If that writer's snapshot has not yet observed the id, the rebuilt blob omits it (omitemptyzero-value).MergeToolDataExtraspreserved only keys unknown to the typed schema; for a known key it let the new blob win "including absence-by-omitempty". So an omitted id cleared the previously-persisted value instead of being preserved.The hottest such writer is the rename hook (
hook_name_sync.go): on every Claude hook that renames a session it doesLoadWithGroupsthenSaveWithGroupsover the whole snapshot. Other full-rewrite writers (internal/ui/home.go,web_mutator.go,group_cmd.go,session_move.go) hit the same path less frequently. The launch path itself is already hardened (InsertSessionAndVerify, #1031), so the clobberer is always a different writer.Fix
Treat the detected session-id keys as sticky in
MergeToolDataExtras:{claude,gemini,opencode,codex}_session_idand their*_detected_at. When the new blob omits a sticky key but the old row has it, carry the old value forward. Semantics:"claude_session_id":"") is honored as an intentional clear;This is a one-line change to the merge guard (
if known[k] && !sticky[k]) plus a small key set; it does not touch the save/sweep contract.Design note
Deliberate clears must now use an explicit empty (or a single-row clear) rather than relying on an omit.
UpdateClaudeSessionsWithDedupclears in-memory only; with sticky keys a cleared id is restored from disk on reload and re-cleared in memory each load. Since duplicates "shouldn't occur in normal use" and tmux env is authoritative, that is a harmless redundant in-memory re-dedup, not a functional regression.Tests
internal/statedb/tool_data_extras_test.go:latest_prompt), sinceclaude_session_idabsence behavior intentionally changed;*_detected_at), non-empty-new-wins, and explicit-empty-clears.go test ./internal/statedb/passes;gofmt/go vetclean.🤖 Generated with Claude Code
Summary by CodeRabbit