AntFleet two-model review found a data-loss race in events drain when a listener is concurrently appending to the same event file.
Summary
events listen --output appends events directly to the output path. events drain renames that file to a .lock path, processes the locked copy, and later writes remaining events back to the original path. If a listener appends a new event to the original path after the rename but before the drain writeback, the writeback can overwrite the newly appended event.
Evidence
- src/commands/events.ts: listener output writer appends to opts.output
- src/commands/events.ts: drain path renames file to file + .lock, then later calls writeFileSync(file, ...)
Impact
This violates the queue-like contract of draining existing events while preserving newly produced events. A concurrently appended event can be silently discarded by the drain writeback.
Reproduction shape
- Run acp events listen --output events.ndjson.
- Start acp events drain --file events.ndjson --limit 1.
- Have a new event appended after renameSync(file, lockFile) but before the drain command writes remaining locked-file events back to events.ndjson.
- The newly appended event can be overwritten by writeFileSync(file, ...).
Suggested fix
Make producer and drainer share a real lock protocol, or change the drain writeback so it never clobbers a recreated original file. For example, preserve and merge any current contents at the original path before writing remaining locked-file events, or append remaining lines instead of truncating where appropriate.
Suggested regression test
Add an integration or fs-mocked test that pauses drain after the rename, appends a new event to the original file path, resumes drain, and asserts the new event remains present afterward.
AntFleet two-model review found a data-loss race in events drain when a listener is concurrently appending to the same event file.
Summary
events listen --output appends events directly to the output path. events drain renames that file to a .lock path, processes the locked copy, and later writes remaining events back to the original path. If a listener appends a new event to the original path after the rename but before the drain writeback, the writeback can overwrite the newly appended event.
Evidence
Impact
This violates the queue-like contract of draining existing events while preserving newly produced events. A concurrently appended event can be silently discarded by the drain writeback.
Reproduction shape
Suggested fix
Make producer and drainer share a real lock protocol, or change the drain writeback so it never clobbers a recreated original file. For example, preserve and merge any current contents at the original path before writing remaining locked-file events, or append remaining lines instead of truncating where appropriate.
Suggested regression test
Add an integration or fs-mocked test that pauses drain after the rename, appends a new event to the original file path, resumes drain, and asserts the new event remains present afterward.