Problem
The TUI persists several critical files that can be corrupted by an interrupted write (Ctrl+C mid-save, kernel panic, OOM kill):
A partial write to any of these results in a corrupt JSON file on disk that fails to parse on next startup. We saw a related fix recently (persistence actor in #310 / commit f1f601c) for the blocking problem; the atomicity problem is separate.
Fix
All persisted writes go through a write_atomic(path, contents) helper:
pub fn write_atomic(path: &Path, contents: &[u8]) -> io::Result<()> {
let parent = path.parent().ok_or_else(...)?;
let tmp = NamedTempFile::new_in(parent)?;
fs::write(tmp.path(), contents)?;
tmp.as_file().sync_all()?; // fsync the temp
tmp.persist(path)?; // atomic rename
// optionally fsync the parent directory on Linux for full durability
Ok(())
}
Use tempfile::NamedTempFile::new_in(parent) (already in workspace via reqwest's deps probably; verify) so the rename is on the same filesystem.
For append-only files (audit.log):
- Open with
OpenOptions::append(true).
- Buffer writes; flush +
fsync after each batch.
- For tamper-resistance (companion idea, separate issue): hash-chain entries so a missing/altered line is detectable.
Apply to
Audit every fs::write\|File::create\|.write_all site in crates/tui/src/ that touches ~/.deepseek/. Convert to write_atomic (or for append-only, use the buffered-flush pattern).
Acceptance criteria
Companion to: #310 (persistence actor — non-blocking) + this (atomic).
Problem
The TUI persists several critical files that can be corrupted by an interrupted write (Ctrl+C mid-save, kernel panic, OOM kill):
~/.deepseek/config.toml~/.deepseek/sessions/<id>.json~/.deepseek/sessions/checkpoints/latest.json~/.deepseek/sessions/checkpoints/offline_queue.json~/.deepseek/audit.log~/.deepseek/tasks/*.json~/.deepseek/trust.json(when security: skill + MCP supply-chain trust — TOFU consent, manifest hashing, audit hookup #349 lands)~/.deepseek/mcp.jsonA partial write to any of these results in a corrupt JSON file on disk that fails to parse on next startup. We saw a related fix recently (persistence actor in #310 / commit f1f601c) for the blocking problem; the atomicity problem is separate.
Fix
All persisted writes go through a
write_atomic(path, contents)helper:Use
tempfile::NamedTempFile::new_in(parent)(already in workspace via reqwest's deps probably; verify) so the rename is on the same filesystem.For append-only files (
audit.log):OpenOptions::append(true).fsyncafter each batch.Apply to
Audit every
fs::write\|File::create\|.write_allsite incrates/tui/src/that touches~/.deepseek/. Convert towrite_atomic(or for append-only, use the buffered-flush pattern).Acceptance criteria
write_atomichelper exists incrates/tui/src/utils.rs.audit.loguses buffered flush + fsync.Companion to: #310 (persistence actor — non-blocking) + this (atomic).