anthropics / anthropics/claude-agent-sdk-python

fork_session() write is non-atomic; crash mid-write leaves orphan transcript

Aberta
#906 0 comentários 0 reações 0 responsáveis Ver no GitHub
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.

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.