fix(config): write config atomically via temp file + rename#291
fix(config): write config atomically via temp file + rename#291sachiniyer wants to merge 1 commit into
Conversation
Concurrent CLI invocations could see "Failed to parse config" because update_config rewrote the file in place (set_len(0) + write_all) while load_config read it without any lock — readers occasionally observed the file mid-rewrite and got truncated TOML. Stage the new contents to a sibling temp file and rename onto the target so readers always see one complete version of the file. A separate config.lock now serializes concurrent writers, since rename invalidates a lock taken on the previous inode. Closes #270. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 799a99b422
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| )?; | ||
| } | ||
|
|
||
| fs::rename(&tmp_path, path)?; |
There was a problem hiding this comment.
Preserve symlinked config files on write
When config.toml is a symlink (common with dotfile managers), renaming the staged file over path replaces the symlink itself with a regular file instead of updating the symlink target. The previous File::create/OpenOptions writes followed the link, so after this change those users silently stop updating their managed config and leave the real target stale; resolve the symlink target or stage/rename at the target path before publishing.
Useful? React with 👍 / 👎.
|
Closing — bug #270 is real but the impact is rare (~0.005% per the report) and the regression test was hard to make reliable on CI. Not worth shipping right now; can revisit if it surfaces in practice. |
Summary
bug_2e491e1e-0d71-42df-9ca5-ee5a975ac314): concurrent CLI invocations could fail withFailed to parse configwhen one process was mid-rewrite ofconfig.tomland another tried to read it.update_configtruncated the file in place (set_len(0)+write_all) andload_configread it without any lock, leaving a window where readers observed empty or partially-written TOML.renameis atomic) and rename onto the target. Readers always see one complete version. A separateconfig.lockserializes writers — we can't lock the config file itself anymore, sincerenameswaps in a new inode and would leave concurrent writers locking unrelated dead inodes.std::fs::renamealready replaces the target viaMoveFileExW.Test plan
cargo fmt --check,cargo clippy -- -D warnings,cargo test(all 291 lib tests + 17 integration tests green).concurrent_reads_during_writes_never_observe_partial_tomlstress test (4 readers / 1 writer, 16 KiB rewrites). Verified it reliably fails on the pre-fix code with the truncated TOML errors from the bug report (e.g.key with no value, expected '='partway throughapi_token) and passes with the fix in well under the 30 s safety cap.🤖 Generated with Claude Code