writeTask (project_task.go), writeFileAtomic (fs_extra.go), writeAgentSession (agent.go), and writeRecord (jobs.go) all build the temp path as path + ".tmp" with os.WriteFile (O_CREATE|O_TRUNC, no O_EXCL, no unique suffix) then os.Rename. write-to-tmp-then-rename is only atomic if a single writer owns that tmp path at a time. writeRecord is serialized by JobManager.mu and writeAgentSession (existing sessions) by sessionMu, but writeTask and writeFileAtomic have no such guard. Two concurrent HTTP requests writing the same record id (or the same workspace file via file.edit_apply) can interleave writes to the shared .tmp and rename a torn/mixed file into the canonical path — silent corruption, not last-write-wins.
Fix (0.3.0 R0): unique temp suffix (e.g. .tmp.<rand>) + O_EXCL, and/or a per-path/per-record write lock. Pairs with #24. Part of the write-serialization foundation.
writeTask(project_task.go),writeFileAtomic(fs_extra.go),writeAgentSession(agent.go), andwriteRecord(jobs.go) all build the temp path aspath + ".tmp"withos.WriteFile(O_CREATE|O_TRUNC, no O_EXCL, no unique suffix) thenos.Rename. write-to-tmp-then-rename is only atomic if a single writer owns that tmp path at a time.writeRecordis serialized byJobManager.muandwriteAgentSession(existing sessions) bysessionMu, butwriteTaskandwriteFileAtomichave no such guard. Two concurrent HTTP requests writing the same record id (or the same workspace file viafile.edit_apply) can interleave writes to the shared.tmpand rename a torn/mixed file into the canonical path — silent corruption, not last-write-wins.Fix (0.3.0 R0): unique temp suffix (e.g.
.tmp.<rand>) + O_EXCL, and/or a per-path/per-record write lock. Pairs with #24. Part of the write-serialization foundation.