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

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.