anthropics / anthropics/claude-agent-sdk-python
fork_session() write is non-atomic; crash mid-write leaves orphan transcript
- Linguagem predominante
- Python
- Estrelas
- 8.1k
- Forks
- 1.3k
- Merge médio
- 2d 31min
- PRs com merge (30d)
- 1
Descrição
## 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).
Guia de contribuição
Nenhum guia de contribuição indexado para este repositório
Avaliação
Esta issue ainda não foi avaliada.