fix(settings): retry section writes against latest disk state (R-LOGIC-01)#429
fix(settings): retry section writes against latest disk state (R-LOGIC-01)#429
Conversation
…C-01) Addresses R-LOGIC-01 / R-TEST-01 from the deep runtime audit. The async unified-settings save paths read the current settings record, mutate one section, and write it back without checking whether another process wrote a newer settings.json in the meantime. That creates a stale-overwrite race. Fix: capture the observed settings mtime, check it immediately before rename inside writeSettingsRecordAsync, and on ESTALE re-read the latest settings record and re-apply the section-scoped mutation up to 3 times. Added a regression test that injects a concurrent external write between the read and write and verifies the unrelated sections from the external write are preserved while the intended pluginConfig mutation still lands.
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 47 minutes and 51 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…429) Responds to the PR #429 review comment about the optimistic mtime check. The previous patch captured the expected mtime in a separate fs.stat() after the read, leaving a TOCTOU window between reading the file contents and sampling its metadata. readSettingsRecordAsyncInternal now uses the mtime from the same open file handle used for the async read and threads that value through the ESTALE retry loop. Regression test remains green.
…429) Responds to the PR #429 review comment. The optimistic mtime check was taking expectedMtimeMs after the async read, leaving a window where an external write could update the file between the read and the mtime snapshot. Swap the order so the expected mtime is sampled first, then the record is read, and mirror that order on ESTALE retry. This preserves the existing settings contract and test behavior while closing the specific stale-overwrite hole called out in review.
Merge the reviewed rollup fix branch after deep audit, full verification battery, and real pack/install smoke test. This lands the integrated fix set from PRs #414-#429 and #431 via one reviewed branch, including the two rollup-only integration commits: - 7f50455 fix(rollup): add missing numeric tracker cleanup helpers from PR #419 - 056ad18 test(rollup): restore auth-list empty-state expectation to current behavior Rationale: the rollup branch was the only branch deep-audited end-to-end and pack/install smoke-tested as a complete artifact. Baseline failures in codex-bin-wrapper / benchmark-runtime-path-script already exist on main and are not regressions from this merge.
Addresses R-LOGIC-01 and R-TEST-01 from the deep runtime audit.
The async unified-settings save paths (
saveUnifiedPluginConfig,saveUnifiedDashboardSettings) used a read-modify-write flow with only an in-process queue. If another process wrote a newersettings.jsonafter the read but before the rename, the stale in-memory record would overwrite unrelated sections.This PR adds an optimistic mtime check:
ESTALE), re-read the latest record and re-apply the section-scoped mutationIncludes a regression test that injects an external write between temp-file creation and rename, and verifies the external writer's unrelated sections are preserved.
note: greptile review for oc-chatgpt-multi-auth. cite files like
lib/foo.ts:123. confirm regression tests + windows concurrency/token redaction coverage.Greptile Summary
this pr patches the async read-modify-write paths in
saveUnifiedPluginConfigandsaveUnifiedDashboardSettingswith an optimistic mtime check: capture mtime before the read, re-stat immediately before rename, and retry up to 3 times on mismatch (ESTALE). the mtime-before-read ordering is correct in both the initial path and the retry path, addressing the toctou concern raised previously. all remaining findings are p2.Confidence Score: 5/5
safe to merge; prior toctou concern is resolved, remaining findings are p2 style/coverage gaps
the mtime-before-read ordering is correct in both initial and retry paths; estale sentinel is synthetic so no windows compat issue; only open items are a missing dashboard test and a fat32 mtime granularity edge case, both p2
test/unified-settings.test.ts — missing symmetric regression test for saveUnifiedDashboardSettings
Important Files Changed
Sequence Diagram
sequenceDiagram participant Caller participant saveUnifiedPluginConfig participant fs Caller->>saveUnifiedPluginConfig: saveUnifiedPluginConfig(pluginConfig) saveUnifiedPluginConfig->>fs: stat(settings.json) → T1 saveUnifiedPluginConfig->>fs: readFile(settings.json) → record loop attempt 0..2 saveUnifiedPluginConfig->>saveUnifiedPluginConfig: merge pluginConfig into record saveUnifiedPluginConfig->>fs: writeFile(temp.tmp, merged) Note over fs: external process may write settings.json here (mtime → T2) saveUnifiedPluginConfig->>fs: stat(settings.json) → currentMtime alt currentMtime ≠ expectedMtime (ESTALE) saveUnifiedPluginConfig->>fs: unlink(temp.tmp) saveUnifiedPluginConfig->>fs: stat(settings.json) → T2 saveUnifiedPluginConfig->>fs: readFile(settings.json) → fresh record else mtime matches saveUnifiedPluginConfig->>fs: rename(temp.tmp → settings.json) saveUnifiedPluginConfig-->>Caller: done end end saveUnifiedPluginConfig-->>Caller: throw ESTALE (after 3 failed attempts)Prompt To Fix All With AI
Reviews (3): Last reviewed commit: "fix(settings): snapshot mtime before asy..." | Re-trigger Greptile