anthropics / anthropics/claude-agent-sdk-python
fork_session() write is non-atomic; crash mid-write leaves orphan transcript
- Ngôn ngữ chính
- Python
- Star
- 8.1k
- Fork
- 1.3k
- Merge trung bình
- 2 ngày 31 phút
- Pull request đã merge (30 ngày)
- 1
Mô tả
## Summary
`fork_session()` writes the new transcript with a single `os.write()` call without a tmp+rename, so a crash between `os.open` and `os.close` leaves a partial or empty `.jsonl` in the project directory.
## Source
In `claude_agent_sdk/_internal/session_mutations.py` (verified against 0.1.72 on PyPI):
```python
fork_path = project_dir / f"{forked_session_id}.jsonl"
fd = os.open(fork_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
try:
os.write(fd, ("\n".join(lines) + "\n").encode("utf-8"))
finally:
os.close(fd)
```
There is no temp file + `os.replace()` step, so a process kill between `os.open` and the completed `os.write` produces a half-written file. `O_EXCL` guarantees the file did not exist beforehand, but does not provide atomicity for the contents.
## Impact
Low severity:
- Original session file is untouched (no data loss).
- The crash window is short — `os.write()` of typical transcript sizes (KB to ~100KB) completes in microseconds — but it is not zero.
- The orphan `.jsonl` will appear in any directory listing and may surface in tooling that enumerates sessions, until manually cleaned.
- Forked-session UUIDs are random v4, so collisions with a later successful fork are not a practical concern, but the orphan file occupies its UUID slot until removed.
## Suggested fix
Standard tmp + atomic rename:
```python
fork_path = project_dir / f"{forked_session_id}.jsonl"
tmp_path = fork_path.with_suffix(".jsonl.tmp")
fd = os.open(tmp_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
try:
os.write(fd, ("\n".join(lines) + "\n").encode("utf-8"))
finally:
os.close(fd)
os.replace(tmp_path, fork_path)
```
`os.replace()` is atomic on POSIX and on Windows (when source and destination are on the same volume, which they are here since both paths are inside `project_dir`).
## Reproduction
The behavior is visible from source inspection alone; a deterministic mid-write crash repro would require injecting a fault into `os.write`. The same code shape that makes it non-atomic also makes a fault-injection repro straightforward in a test (mock `os.write` to raise mid-call).
## Version
claude-agent-sdk 0.1.72 (latest on PyPI as of filing).
Hướng dẫn đóng góp
Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này
Đánh giá
Issue này chưa được đánh giá.