[BUG] saved_hook_context entries written with duplicate UUIDs, corrupting session graph and breaking resume
[BUG] saved_hook_context entries written with duplicate UUIDs, corrupting session graph and breaking resume
Summary
When plugins inject context via hooks (e.g., SessionStart with additionalContext), Claude Code writes saved_hook_context entries to the session JSONL file. All these entries share the same UUID (the original SessionStart hook's UUID) instead of unique UUIDs. This corrupts the conversation graph and can break session resume.
Environment
- Claude Code version: 2.1.27
- Platform: Linux (likely affects all platforms)
- Plugins: Any plugin using
SessionStarthooks withadditionalContext(e.g., superpowers)
Steps to Reproduce
- Enable a plugin that uses
SessionStarthooks withadditionalContext - Start a new Claude Code session
- Have a conversation with multiple tool uses
- Exit the session
- Examine the session JSONL file:
# Count UUID occurrences - duplicate UUIDs indicate the bug
jq -r 'select(.uuid) | .uuid' ~/.claude/projects/<project>/<session>.jsonl | sort | uniq -c | sort -rn | head -5
Expected output: All UUIDs should appear exactly once (count of 1)
Actual output: One UUID appears hundreds of times:
274 aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
1 11111111-2222-3333-4444-555555555555
1 66666666-7777-8888-9999-000000000000
...
- Verify all duplicates are
saved_hook_contexttype:
jq -c 'select(.uuid == "<duplicate-uuid>") | .type' <session>.jsonl | sort | uniq -c
# 274 "saved_hook_context"
- Try to resume the session - it may hang or fail to load properly
Expected Behavior
Each saved_hook_context entry should have a unique UUID, just like all other message types in the session file.
Actual Behavior
All saved_hook_context entries share the same UUID (appears to be derived from toolUseID: "SessionStart"). This creates:
- Corrupted conversation graph: The parent-child UUID relationships become ambiguous
- Session resume failures: Claude Code can't properly traverse the conversation chain
- Growing file corruption: The problem worsens with each tool use (more duplicate entries)
Analysis
Examining the saved_hook_context entries shows they all have:
- Same
uuid(one repeated value) - Same
hookName: "SessionStart" - Same
toolUseID: "SessionStart" - Different
parentUuidvalues (correctly linking to different conversation points)
{"type":"saved_hook_context","uuid":"<same-uuid>","parentUuid":"<parent-1>","hookName":"SessionStart","toolUseID":"SessionStart",...}
{"type":"saved_hook_context","uuid":"<same-uuid>","parentUuid":"<parent-2>","hookName":"SessionStart","toolUseID":"SessionStart",...}
{"type":"saved_hook_context","uuid":"<same-uuid>","parentUuid":"<parent-3>","hookName":"SessionStart","toolUseID":"SessionStart",...}
The UUID appears to be incorrectly derived from toolUseID ("SessionStart") rather than being generated uniquely for each entry.
Impact
- In my testing, 17 out of 20 recent sessions were affected
- Sessions with many tool uses accumulate hundreds of duplicate UUID entries
- Affected sessions may fail to resume or hang during resume
- The bug is triggered simply by using plugins with SessionStart hooks
Comparison
Sessions created without hooks enabled (or before the saved_hook_context feature) have no duplicate UUIDs and resume correctly. Sessions with hooks enabled accumulate duplicates proportional to the number of tool uses.
Workaround
A repair script can fix affected sessions by assigning unique UUIDs to duplicate saved_hook_context entries:
#!/usr/bin/env python3
"""Repair Claude Code sessions with duplicate saved_hook_context UUIDs"""
import json
import uuid
from pathlib import Path
from collections import Counter
def repair_session(filepath: Path):
entries = []
with open(filepath) as f:
for line in f:
if line.strip():
entries.append(json.loads(line))
# Find duplicate UUIDs
uuid_counts = Counter(e.get('uuid') for e in entries if e.get('uuid'))
duplicate_uuids = {k for k, v in uuid_counts.items() if v > 1}
if not duplicate_uuids:
print(f"No duplicates in {filepath}")
return
# Fix duplicates - keep first occurrence, assign new UUIDs to rest
seen_uuids = set()
fixed = 0
for entry in entries:
entry_uuid = entry.get('uuid')
if entry_uuid in duplicate_uuids and entry_uuid in seen_uuids:
if entry.get('type') == 'saved_hook_context':
entry['uuid'] = str(uuid.uuid4())
fixed += 1
elif entry_uuid:
seen_uuids.add(entry_uuid)
# Write back
with open(filepath, 'w') as f:
for entry in entries:
f.write(json.dumps(entry) + '\n')
print(f"Fixed {fixed} duplicate UUIDs in {filepath}")
if __name__ == '__main__':
import sys
repair_session(Path(sys.argv[1]))
Related Issues
- #14281 - Hook additionalContext injected multiple times (closed as fixed in v2.1, but this UUID issue persists in v2.1.27)
- #12235 - Session ID changes when resuming via --resume
- #2597 - Cross-session summary contamination (mentioned UUID reuse)
Suggested Fix
When writing saved_hook_context entries, generate a unique UUID for each entry rather than reusing the hook's toolUseID:
// Instead of:
uuid: hookContext.toolUseID // "SessionStart" - same for all
// Use:
uuid: crypto.randomUUID() // Unique for each entryThis issue has 4 comments on GitHub. Read the full discussion on GitHub ↗