New request
#2097: Follow-up: turn-journal hardening — double-terminal detection + multi-process append safety
We'll provision a sandbox, run an agent against the issue, and open a draft PR. You can pull the branch and iterate from there.
Background
Two cleanup items from the v0.51.46 / Release V Opus advisor review. Filing as a single batch so they can be picked up by a contributor (or rolled into the next contributor PR that touches these areas).
1. Turn-journal completed × interrupted lifecycle double-write
PR #2062 places completed inside an outer try block and interrupted inside the outer except. Mutually exclusive in the normal case. Edge case: if anything between the completed event write and the start of the outer except raises an unhandled exception (e.g. a logger crash, an OOM in the middle of state cleanup), both events fire for the same turn.
Currently derive_turn_journal_states resolves the collision by latest created_at — data is non-crashing but lossy.
Fix shape
Add a terminal: bool field on completed and interrupted events. Have the audit detect double-terminal turns explicitly (e.g. report turn_journal_double_terminal for the audit consumer) rather than silently collapsing.
~10-15 LOC + 1 regression test.
2. Multi-process append safety for long messages
api/turn_journal.py uses O_CREAT|O_APPEND|O_WRONLY + single write() per event. POSIX guarantees atomic appends only up to PIPE_BUF (4096 bytes on Linux). The submitted event line contains the full user message, so a >4KB paste crosses the atomicity boundary.
For the current single-process deployment this is fine. If a future deployment runs multiple webui workers on the same state dir (e.g. behind a load balancer with sticky sessions, or a multi-worker uvicorn), large submitted events from different workers could interleave at the byte level — corrupting the JSONL.
Fix shape
Two options:
(a) Per-process journal files. Append pid-{os.getpid()} to the journal filename, and iter_turn_journal_session_ids globs across the variants. Simple, zero coordination needed.
(b) flock-based writer. fcntl.flock(LOCK_EX) around the write block. Slightly slower (lock contention), but keeps the on-disk filename stable.
(a) is simpler. (b) is more standard.
Either is ~20-30 LOC + a multi-process regression test (spawn N writer subprocesses, hammer the same session, verify no interleaved lines).
Why both items together
Both are forward-looking — neither is a current-deployment bug. Filing as a single hardening ticket for whichever contributor wants to harden the turn-journal subsystem for multi-process or robustness scenarios.
References
- PR #2059 / #2062 (turn-journal stack)
- Release V notes (v0.51.46)
- Opus advisor stage-339 review (verbatim from items 3 and 4)
Localized turn-journal hardening adding explicit double-terminal detection and append-safety guards.
- api/turn_journal.py
- tests/test_turn_journal.py