| name | fix-stuck-session |
| description | Fix a Claude Code session stuck in an API 400 loop about thinking blocks that cannot be modified. Backs up the session JSONL, strips all thinking/redacted_thinking blocks, and preserves everything else. |
| license | MIT |
| metadata | {"author":"lifeodyssey","version":"1.0.0"} |
Fix Stuck Session — Thinking Block Remover
When a Claude Code session hits a 400 error loop about "thinking blocks cannot be modified", the cause is stale thinking/redacted_thinking blocks in the session JSONL file being replayed back to the API. This skill strips them.
Steps
1. Locate the stuck session
Session files live at ~/.claude/projects/<project-dir>/<session-id>.jsonl where <project-dir> is the working directory path with / replaced by -.
Run this to find the most recently modified session for the current project:
PROJECT_DIR=$(echo "$PWD" | sed 's|/|-|g')
SESSION_DIR=~/.claude/projects/"$PROJECT_DIR"
LATEST=$(ls -t "$SESSION_DIR"/*.jsonl 2>/dev/null | head -1)
echo "Latest session: $LATEST"
If the user specifies a session ID, use that instead. If they say "previous session" or "stuck session", use the most recent .jsonl that is NOT the current session (skip the newest, take the second).
2. Verify it has thinking blocks
python3 -c "
import json, sys
path = '$LATEST'
t = r = 0
with open(path) as f:
for line in f:
if not line.strip(): continue
obj = json.loads(line)
if obj.get('type') == 'assistant':
for b in obj.get('message',{}).get('content',[]):
if b.get('type') == 'thinking': t += 1
elif b.get('type') == 'redacted_thinking': r += 1
print(f'thinking: {t}, redacted_thinking: {r}')
if t + r == 0:
print('No thinking blocks found — this session may not be the stuck one.')
sys.exit(1)
"
If zero thinking blocks, ask the user to confirm the session or check a different one.
3. Backup and strip
cp "$LATEST" "${LATEST}.bak"
python3 -c "
import json
path = '${LATEST}'
lines = []
with open(path) as f:
for line in f:
line = line.rstrip('\n')
if not line:
lines.append(line)
continue
obj = json.loads(line)
if obj.get('type') == 'assistant':
msg = obj.get('message', {})
content = msg.get('content', [])
filtered = [b for b in content if b.get('type') not in ('thinking', 'redacted_thinking')]
if not filtered:
filtered = [{'type': 'text', 'text': '[thinking omitted]'}]
msg['content'] = filtered
obj['message'] = msg
lines.append(json.dumps(obj, ensure_ascii=False))
with open(path, 'w') as f:
for line in lines:
f.write(line + '\n')
"
4. Verify the fix
python3 -c "
import json
path = '${LATEST}'
total_lines = thinking = 0
with open(path) as f:
for line in f:
total_lines += 1
if not line.strip(): continue
obj = json.loads(line)
if obj.get('type') == 'assistant':
for b in obj.get('message',{}).get('content',[]):
if b.get('type') in ('thinking', 'redacted_thinking'):
thinking += 1
print(f'Lines: {total_lines}, Remaining thinking blocks: {thinking}')
assert thinking == 0, 'FAILED: thinking blocks still present'
print('Session cleaned successfully.')
"
echo "Backup saved at: ${LATEST}.bak"
5. Report to user
Tell the user:
- How many thinking blocks were removed
- The session file path
- The backup location
- They can resume the session with
claude --resume <session-id> (the UUID from the filename)