[BUG] Infinite CPU loop on startup when session .jsonl contains duplicate saved_hook_context UUIDs

Resolved 💬 5 comments Opened Jan 31, 2026 by gbottari Closed Mar 1, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Claude Code freezes permanently (100% CPU, completely unresponsive) when launching in a directory whose project session cache (~/.claude/projects/<project>/) contains a .jsonl file with duplicate saved_hook_context entries that share the same UUID but have different parentUuid values.

This creates a cycle in the message parent chain that the session loader follows indefinitely:

hook_ctx(uuid=X, parent=A) → user(uuid=B, parent=X) → hook_ctx(uuid=X, parent=B) → user(uuid=B, parent=X) → ...

In practice, this happens when a plugin (e.g., "superpowers") writes saved_hook_context entries reusing the same UUID across conversation turns. Once a session with this pattern exists, the directory becomes permanently unusable — and every frozen session creates another orphaned .jsonl with the same problem, making it self-perpetuating.

What Should Happen?

  1. The session loader should detect cycles in the message parent chain (e.g., visited-set or max-depth limit) and gracefully skip or discard malformed sessions instead of looping forever.
  2. The hook context writer should assign unique UUIDs per saved_hook_context entry rather than reusing them across turns.

Error Messages/Logs

No error messages are produced. The process silently spins at 100% CPU with zero I/O. In debug logs, the API request is made but "Stream started" is never logged — the process hangs before receiving any response.

Steps to Reproduce

# 1. Create a minimal git repo
mkdir /tmp/claude-bug-repro && cd /tmp/claude-bug-repro && git init

# 2. Create the project session directory
mkdir -p ~/.claude/projects/-tmp-claude-bug-repro

# 3. Write a minimal .jsonl with a UUID cycle
python3 << 'PYEOF' > ~/.claude/projects/-tmp-claude-bug-repro/deadbeef-0000-0000-0000-000000000000.jsonl
import json

hook_uuid = "aaaa0000-0000-0000-0000-000000000003"
user2_uuid = "aaaa0000-0000-0000-0000-000000000005"
session_id = "deadbeef-0000-0000-0000-000000000000"

lines = [
    {"type": "file-history-snapshot", "messageId": "aaaa0000-0000-0000-0000-000000000001",
     "snapshot": {"messageId": "aaaa0000-0000-0000-0000-000000000001",
     "trackedFileBackups": {}, "timestamp": "2026-01-31T12:00:00.000Z"}, "isSnapshotUpdate": False},
    {"parentUuid": None, "isSidechain": False, "userType": "external",
     "cwd": "/tmp/claude-bug-repro", "sessionId": session_id, "version": "2.1.27",
     "type": "user", "message": {"role": "user", "content": "hi"},
     "uuid": "aaaa0000-0000-0000-0000-000000000002", "timestamp": "2026-01-31T12:00:01.000Z"},
    {"type": "saved_hook_context", "uuid": hook_uuid, "content": ["hook context"],
     "hookName": "UserPromptSubmit", "parentUuid": "aaaa0000-0000-0000-0000-000000000004",
     "sessionId": session_id, "isSidechain": False},
    {"parentUuid": hook_uuid, "isSidechain": False, "userType": "external",
     "cwd": "/tmp/claude-bug-repro", "sessionId": session_id, "version": "2.1.27",
     "type": "user", "message": {"role": "user", "content": "Implement this plan:\n" + "x" * 18000},
     "uuid": user2_uuid, "timestamp": "2026-01-31T12:00:02.000Z"},
    {"type": "saved_hook_context", "uuid": hook_uuid, "content": ["hook context"],
     "hookName": "UserPromptSubmit", "parentUuid": user2_uuid,
     "sessionId": session_id, "isSidechain": False},
    {"parentUuid": hook_uuid, "isSidechain": False, "userType": "external",
     "cwd": "/tmp/claude-bug-repro", "sessionId": session_id, "version": "2.1.27",
     "type": "assistant", "message": {"role": "assistant",
     "content": [{"type": "text", "text": "I'll help with that."}]},
     "uuid": "aaaa0000-0000-0000-0000-000000000006", "timestamp": "2026-01-31T12:00:03.000Z"},
]

for line in lines:
    print(json.dumps(line))
PYEOF

# 4. Launch Claude Code and type "hi"
cd /tmp/claude-bug-repro && claude
# → Process spins at 100% CPU, never shows a response

# Cleanup:
# rm -rf ~/.claude/projects/-tmp-claude-bug-repro /tmp/claude-bug-repro

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.27

Platform

Anthropic API

Operating System

Other Linux

Terminal/Shell

Other

Additional Information

Workaround: Delete orphaned .jsonl files in ~/.claude/projects/<project>/ that are not referenced in sessions-index.json. Note that each frozen session creates a new orphaned .jsonl, so the problem is self-perpetuating until cleaned up.

How this occurs naturally: The "superpowers" plugin (and potentially other plugins) writes saved_hook_context entries that reuse the same UUID across multiple conversation turns with different parentUuid values. After a multi-turn session with such a plugin, the resulting .jsonl contains the cycle pattern described above. The next time Claude Code starts in that directory, it freezes.

Environment: Linux (Manjaro 6.18.4), Node.js v25.2.1

View original on GitHub ↗

This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗