Repository navigation
fix(api): make sidecar config writes durable - #6945
Conversation
houko
left a comment
There was a problem hiding this comment.
Automated review: two findings left as inline comments (missing changelog.d fragment; possible duplication with the #6942 shared atomic_write helper and #6944's secrets_env writer). No commits pushed to this branch.
Generated by Claude Code
| @@ -150,8 +151,33 @@ static TMP_SEQ: AtomicU64 = AtomicU64::new(0); | |||
| fn atomic_write(path: &Path, contents: &str) -> Result<(), String> { | |||
There was a problem hiding this comment.
Missing changelog fragment: CLAUDE.md requires a new file under changelog.d/ per PR (not an edit to CHANGELOG.md), and this PR's diff has no fragment, unlike sibling PRs #6942 and #6944 which each added one under changelog.d/fixed/. Suggest adding changelog.d/fixed/6945-sidecar-config-durable-write.md, ending with (#6945) (@houko).
Generated by Claude Code
| @@ -150,8 +151,33 @@ static TMP_SEQ: AtomicU64 = AtomicU64::new(0); | |||
| fn atomic_write(path: &Path, contents: &str) -> Result<(), String> { | |||
There was a problem hiding this comment.
This atomic_write (open+create_new, write, sync_all, rename, fsync parent dir, cleanup on failure) duplicates the pattern that #6942 is extracting into a shared pub(crate) fn atomic_write in crates/librefang-api/src/lib.rs ("This shared helper backs config, provider, webhook, budget, user, and upload-metadata writes"), and is structurally identical to the inline copy in #6944's secrets_env::upsert_secret — this file's own doc comment even says "Same defect class as secrets_env::upsert_secret (T3.1)". Flagging rather than changing myself because: (1) #6942 is unmerged and its PR body explicitly scopes out "independent atomic-write helpers in other modules/crates", so it's unclear whether unifying sidecar's writer is in scope yet; (2) this module's atomic_write returns Result<(), String> and shares TMP_SEQ across both upsert_sidecar_block and remove_sidecar_block (see the comment on TMP_SEQ), whereas the lib.rs helper is io::Result-typed with its own private counter — reconciling those isn't a pure call-site swap. Worth a maintainer call on whether to converge onto the shared helper now or track it as a follow-up once #6942 lands.
Generated by Claude Code
houko
left a comment
There was a problem hiding this comment.
One additional finding on top of the two threads already open on this PR (missing changelog fragment — since resolved by a follow-up push; atomic_write duplication with #6942/#6944). Everything else in the diff checks out: fsync-before-rename and fsync-parent-dir-after-rename is the correct durability ordering, cleanup on both write and rename failure paths is correct, the new unit tests are sound, rustfmt is clean, and cargo check -p librefang-api --lib compiles without errors.
Generated by Claude Code
| #[cfg(unix)] | ||
| fs::File::open(parent) | ||
| .and_then(|dir| dir.sync_all()) | ||
| .map_err(|e| format!("sync parent directory {parent:?}: {e}"))?; |
There was a problem hiding this comment.
Once fs::rename on line 171 succeeds, the on-disk config.toml already reflects the new sidecar block — that part of the write is done and visible to any subsequent read. If the parent-directory sync_all() here then fails (e.g. an unusual filesystem, a permissions hiccup, or an FS that rejects directory fsync), atomic_write still returns Err, and both callers (configure_channel / delete_sidecar_channel in routes/channels.rs) turn that into a 500 via ApiErrorResponse::internal_scrub. The operator sees "internal server error" and will likely retry, even though the config change already landed — only the crash-durability confirmation failed, not the write itself.
For upsert_sidecar_block this is low-risk since retries are idempotent by name, but for remove_sidecar_block a caller could see a false failure on a removal that already took effect, which is more confusing (dashboard says "failed to remove", GET shows it gone). Might be worth distinguishing "durability unconfirmed" from "write failed" in the error, or at minimum logging the directory-fsync failure as a WARN and still returning Ok(()) since the rename — the operation that matters for correctness — already succeeded. Flagging for a maintainer call rather than changing the behavior myself, since picking between "fail loud" and "warn and proceed" is a product/ops decision, not just a bug fix.
Generated by Claude Code
There was a problem hiding this comment.
Commit 346cd4b ("docs: add missing changelog fragment for #6945") has author and committer identity Claude <noreply@anthropic.com> (git log --format='%an|%ae|%cn|%ce' confirms both). That's the exact pattern the commit-msg hook's author-identity check exists to reject (CLAUDE.md: "separately rejects a commit whose author identity ... resolves to Claude / Anthropic even when the message itself is clean"), and the project rule is no AI/Claude attribution in commit messages, PR bodies, or comments.
Fixing this requires rewriting the commit's author/committer identity, which needs a history rewrite and a force-push to this branch — flagging here rather than doing that unilaterally. Worth squashing/rebasing with a corrected identity before merge.
Durability implementation itself looks correct and consistent with the sibling PRs (#6942, #6944, #6947, #6948): temp file in the same directory via create_new, sync_all before rename, cleanup of the staging file on write or rename failure, and an fsync of the parent directory on Unix after the rename. No config-struct or route changes, so no Default-impl or TestServer gap. Changelog fragment format and attribution line are correct.
Generated by Claude Code
|
Added the missing Same note as on #6948/#6947: that fixup commit was pushed from an automated review session whose local git identity was misconfigured to Generated by Claude Code |
* fix(api): durably persist registry content * docs(changelog): add fragment for durable registry content write fix PR #6984 fixes a real TOCTOU race and adds rollback-on-reject behavior for registry content writes but was missing the changelog.d fragment other PRs in this durable-write series (#6942, #6944, #6945, #6947, #6948) already carry. --------- Co-authored-by: Evan <tonymo2048@gmail.com>
Summary
Testing
cargo test -p librefang-api sidecar_toml::tests --libcargo clippy -p librefang-api --lib -- -D warningscargo fmt --all -- --checkgit diff --check