Skip to content

fix(config): write config atomically via temp file + rename#291

Closed
sachiniyer wants to merge 1 commit into
mainfrom
siyer/fix-atomic-config-writes
Closed

fix(config): write config atomically via temp file + rename#291
sachiniyer wants to merge 1 commit into
mainfrom
siyer/fix-atomic-config-writes

Conversation

@sachiniyer
Copy link
Copy Markdown
Contributor

Summary

  • Fixes [Detail Bug] CLI config reads can fail due to non-atomic config file updates (truncated TOML race) #270 (Detail bug bug_2e491e1e-0d71-42df-9ca5-ee5a975ac314): concurrent CLI invocations could fail with Failed to parse config when one process was mid-rewrite of config.toml and another tried to read it.
  • update_config truncated the file in place (set_len(0) + write_all) and load_config read it without any lock, leaving a window where readers observed empty or partially-written TOML.
  • Now writes stage to a sibling temp file (in the same directory, so rename is atomic) and rename onto the target. Readers always see one complete version. A separate config.lock serializes writers — we can't lock the config file itself anymore, since rename swaps in a new inode and would leave concurrent writers locking unrelated dead inodes.
  • Unix mode is preserved (mirrored from any existing config; otherwise 0600 since the file holds an API token). On Windows std::fs::rename already replaces the target via MoveFileExW.

Test plan

  • cargo fmt --check, cargo clippy -- -D warnings, cargo test (all 291 lib tests + 17 integration tests green).
  • New concurrent_reads_during_writes_never_observe_partial_toml stress 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 through api_token) and passes with the fix in well under the 30 s safety cap.
  • Existing tests covering comment preservation, key removal, empty-file path, and writer-vs-writer concurrency still pass.

🤖 Generated with Claude Code

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>
@sachiniyer sachiniyer temporarily deployed to integration-tests May 9, 2026 03:18 — with GitHub Actions Inactive
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/config/storage.rs
)?;
}

fs::rename(&tmp_path, path)?;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 1 file

@sachiniyer
Copy link
Copy Markdown
Contributor Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Detail Bug] CLI config reads can fail due to non-atomic config file updates (truncated TOML race)

1 participant