anthropics / anthropics/claude-agent-sdk-python

Hook/progress messages corrupt parentUuid chain causing one-message delay

Open
#566 0 comments 1 reaction 0 assignees View on GitHub
Dominant language
Python
Stars
8.1k
Forks
1.3k
Avg merge
2d 31m
Merged PRs (30d)
1

Description

## Bug Description

When using `ClaudeSDKClient` for multi-turn conversations, the SDK's JSONL transcript writer assigns `parentUuid` based on a rolling "last UUID" cursor that advances for **every** message type, including `hook_progress` and `stop_hook_summary` system events. This causes the next user message to be parented to a hook/progress entry instead of the assistant's response, breaking the conversation chain.

## Observed Behavior

User messages get parented to `hook_progress` system messages instead of assistant responses, causing Claude to effectively "miss" a message. The user must send an additional message to get the response they expected from the previous one — a "one-message delay" effect.

## JSONL Evidence

From a real session transcript:

```
21:59:47.801Z | assistant (uuid=5a5caa75, parent=01df9ff2) ← Normal response
22:00:32.409Z | progress/hook_progress (uuid=187ee328, parent=5a5caa75) ← SDK hook runs AFTER turn
22:00:37.631Z | user (uuid=a6d699bb, parent=187ee328) ← WRONG: parented to hook, not assistant
```

The user message at `22:00:37` should have `parentUuid=5a5caa75` (the assistant response), but instead gets `parentUuid=187ee328` (a `SessionStart:resume` hook_progress event that the SDK wrote between turns).

## Root Cause

Investigated with both Claude and OpenAI Codex independently — both reached the same conclusion:

The bundled CLI's `insertMessageChain(...)` assigns `parentUuid` from a rolling prior UUID and advances that cursor for every message, including hook/progress events. When hooks run after `ResultMessage` (stop hooks, session start hooks on resume, etc.), they become the "last message" in the chain, and the next user query gets parented to the hook instead of the assistant response.

This is an SDK/CLI persistence issue, **not** a consumer-side issue — the bridge cannot fix this by draining hook messages from the stream, because `parentUuid` is already assigned when the entry is written to JSONL.

## Expected Behavior

Hook/progress/system messages should not advance the conversational parent cursor. The SDK should track a `last_conversation_uuid` (assistant/user messages only) separately from the raw event ordering, and use that for parenting new user messages.

## Reproduction

1. Use `ClaudeSDKClient` for a multi-turn conversation
2. Send a query that triggers hooks (e.g., any query on a session with `SessionStart` hooks)
3. After the response completes and hooks run, send another query
4. Inspect the JSONL — the second user message will be parented to the hook instead of the assistant response

## Environment

- `claude-agent-sdk` 0.1.27 (also confirmed on 0.1.35)
- Python 3.14.2 (Homebrew)
- macOS

Contributor guide

No contributing guide indexed for this repository

Research direction

Start at ClaudeSDKClient's JSONL transcript writer and the bundled CLI's insertMessageChain(...) entry point. Reproduce a multi-turn conversation with SessionStart or stop hooks, then inspect the JSONL parentUuid values. Done means hook/progress/system events no longer become the parent of the next user message.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
api
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.