fork_session drops transcript entries containing U+2028/U+2029/U+0085 (str.splitlines), breaking the parentUuid chain
Maintainer thường phản hồi trong vòng 2 ngày
@mangeshraut712 đang làm issue này rồi.
Từ ngày 25/10/2026.
Đánh giá
- Độ khó
- 2/5
- Thời gian dự kiến
- 1-3 giờ
- Mức phù hợp với người mới
- 78/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- python
- Lĩnh vực
- backend, developer-experience
Hướng nghiên cứu
Bắt đầu tại src/claude_agent_sdk/_internal/session_mutations.py, ở _parse_fork_transcript quanh dòng 601, sau đó kiểm tra các bài kiểm thử bao quát việc phân tích cú pháp bản ghi hội thoại của fork_session. Tái hiện lỗi bằng một bản ghi JSONL chứa U+2028, U+2029 hoặc U+0085 và kiểm tra xem các mục tiếp theo có giữ nguyên parentUuid hay không. Công việc được xem là hoàn tất khi tất cả bản ghi đều được phân tích cú pháp và fork giữ nguyên chuỗi cha; hãy chạy các bài kiểm thử liên quan.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Summary
fork_session silently drops transcript entries whose JSON contains U+2028 (LINE SEPARATOR), U+2029 (PARAGRAPH SEPARATOR) or U+0085 (NEL). The entry after a dropped one loses its parent (parentUuid becomes None), so the forked session's chain breaks there, and resuming the fork loads only the part after the break.
Cause
_parse_fork_transcript splits the file with str.splitlines() (_internal/session_mutations.py line 601 on main):
for line in content.decode("utf-8", errors="replace").splitlines():
str.splitlines() treats U+2028, U+2029 and U+0085 as line boundaries. Claude Code writes the JSONL with JavaScript's JSON.stringify, which leaves these characters unescaped inside strings (that is valid JSON). So a single entry containing one of them is cut into pieces, each piece fails json.loads, and the except branch skips them without a trace. _build_fork_lines then cannot resolve the next entry's parent and writes parentUuid: None.
Splitting on "\n" only (the record separator Claude Code actually writes) avoids this. An equivalent fix is to escape the three characters to \uXXXX before parsing: inside JSON they can only occur in strings, so the meaning is unchanged.
Reproduction
import json, os, tempfile, uuid
from pathlib import Path
from claude_agent_sdk import fork_session
home = Path(tempfile.mkdtemp())
os.environ["CLAUDE_CONFIG_DIR"] = str(home)
project = home / "projects" / "-w"
project.mkdir(parents=True)
session, ids = str(uuid.uuid4()), [str(uuid.uuid4()) for _ in range(3)]
texts = ["first", "a\u2028b", "third"] # the middle message contains U+2028 LINE SEPARATOR
records = [
{"type": "user" if i % 2 == 0 else "assistant", "uuid": ids[i], "parentUuid": ids[i - 1] if i else None,
"sessionId": session, "cwd": "/w",
"message": {"role": "user" if i % 2 == 0 else "assistant", "content": [{"type": "text", "text": t}]}}
for i, t in enumerate(texts)
]
# Claude Code writes JSONL with JS JSON.stringify, which leaves U+2028 unescaped; ensure_ascii=False does the same here.
(project / f"{session}.jsonl").write_text("".join(json.dumps(r, ensure_ascii=False) + "\n" for r in records), encoding="utf-8")
forked = fork_session(session, directory="/w").session_id
copied = [json.loads(l) for l in (project / f"{forked}.jsonl").read_text(encoding="utf-8").split("\n") if l.strip()]
messages = [r for r in copied if r.get("type") in ("user", "assistant")]
print("source messages:", len(records))
print("forked messages:", len(messages), [r["message"]["content"][0]["text"] for r in messages])
print("parentUuid of the last one:", messages[-1]["parentUuid"])
Output with claude-agent-sdk 0.2.168 (main still has the same line; latest release is v0.2.174):
source messages: 3
forked messages: 2 ['first', 'third']
parentUuid of the last one: None
Impact
We fork live sessions with fork_session(..., up_to_message_id=...) and resume the fork through ClaudeSDKClient so that it continues with the full history. One source session contained a single tool call whose input had a raw U+2028. After forking, that entry was missing, the following tool result had parentUuid: None, and the resumed fork's first request carried 27 messages instead of the full conversation (cache read 0). Nothing reported an error.
Environment
- claude-agent-sdk 0.2.168 (Python 3.13), Claude Code CLI 2.1.310, Linux
- Ngôn ngữ chính
- Python
- Star
- 8.5k
- Fork
- 1.4k
- Merge trung bình
- 3 ngày 10 giờ
- Pull request đã merge (30 ngày)
- 13
Chuẩn bị môi trường
Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của anthropics/claude-agent-sdk-python
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
anthropics/claude-agent-sdk-python#1509 ·
Maintainer thường phản hồi trong vòng 2 ngày
-
`parse_message` raises raw `TypeError` instead of `MessageParseError` on non-dict nested payloadsĐang mởbug good first issue
Độ khó 2/5 Nửa ngày Mức phù hợp với người mới 76/100
anthropics/claude-agent-sdk-python#1508 ·
Maintainer thường phản hồi trong vòng 2 ngày
-
bug good first issue
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
anthropics/claude-agent-sdk-python#1507 ·
Maintainer thường phản hồi trong vòng 2 ngày
-
`fork_session` hangs forever on a cyclic `parentUuid` chain (no cycle guard in `_build_fork_lines`)Đang mởbug good first issue
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
anthropics/claude-agent-sdk-python#1506 ·
Maintainer thường phản hồi trong vòng 2 ngày
-
ClaudeSDKClient.connect() with an async iterable closes stdin, so follow-up query() failsCó thể đã có người làm @m4xkushnir đã nhận 19 ngày trước. Đang mởbug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
anthropics/claude-agent-sdk-python#1443 ·
Maintainer thường phản hồi trong vòng 2 ngày
Tất cả issue của anthropics/claude-agent-sdk-python
Issue tương tự
-
Contributing instructionsĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
dbt-labs/spark-utils#51 ·
-
bug pydanty:is-working
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
pydantic/pydantic-ai#13444 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
good first issue help wanted
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
-
good first issue help wanted
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 72/100
-
changelog investigate
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 68/100
ramnes/notion-sdk-py#502 ·