-
Notifications
You must be signed in to change notification settings - Fork 2.6k
fix: use safeWriteJson for all JSON file writes with race condition fix #4733
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: use safeWriteJson for all JSON file writes with race condition fix #4733
Conversation
|
@daniel-lxs, here is my rework that fixes #3772 with regard to #4468 |
64403b7 to
328da41
Compare
|
@cte, do you understand this test error? I am not able to reproduce it on my desk. A full clean followed by roo-cline:test: roo-cline:bundle: [extension] Cleaning dist directory: /home/runner/work/Roo-Code/Roo-Code/src/dist
roo-cline:test: roo-cline:bundle: Error: ENOTEMPTY: directory not empty, rmdir '/home/runner/work/Roo-Code/Roo-Code/src/dist'
roo-cline:test: roo-cline:bundle: at Object.rmdirSync (node:fs:1215:11)
roo-cline:test: roo-cline:bundle: at _rmdirSync (node:internal/fs/rimraf:262:21)
roo-cline:test: roo-cline:bundle: at rimrafSync (node:internal/fs/rimraf:195:7)
roo-cline:test: roo-cline:bundle: at Module.rmSync (node:fs:1264:10)
roo-cline:test: roo-cline:bundle: at main (file:///home/runner/work/Roo-Code/Roo-Code/src/esbuild.mjs:39:6)
roo-cline:test: roo-cline:bundle: at file:///home/runner/work/Roo-Code/Roo-Code/src/esbuild.mjs:130:1
roo-cline:test: roo-cline:bundle: at ModuleJob.run (node:internal/modules/esm/module_job:263:25)
roo-cline:test: roo-cline:bundle: at async ModuleLoader.import (node:internal/modules/esm/loader:540:24)
roo-cline:test: roo-cline:bundle: at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:117:5) {
roo-cline:test: roo-cline:bundle: errno: -39,
roo-cline:test: roo-cline:bundle: code: 'ENOTEMPTY',
roo-cline:test: roo-cline:bundle: syscall: 'rmdir',
roo-cline:test: roo-cline:bundle: path: '/home/runner/work/Roo-Code/Roo-Code/src/dist'
roo-cline:test: roo-cline:bundle: }
roo-cline:test: roo-cline:bundle: ELIFECYCLE Command failed with exit code 1.according to Gemini-2.5:
I am not sure what to do about that, and fixing the CI race is out of scope for this PR, so FYI because others will probably hit this too. |
|
@daniel-lxs: Ignoring the unrelated CI test failure that I asked @cte about above, this is ready for review: |
|
Hey @KJ7LNW, the implementation looks good, I threw everything I had at it and it seems to be working. I tried solving the conflicts but I couldn't write to this branch, can you take a look? |
sure I can do that |
how far did you get in your fixes? I am having a very difficult time converting jest to vitest. any words of wisdom there? |
|
@KJ7LNW I honestly thought it would take you a few minutes at most, my bad. To fix them I just rebased and modified the actual conflicts (your new tests) to use vitest, had a bit of help from Opus 4. |
|
I gave you push access and sent an invite for your user. do you still have the task available to tell the model to redo the changes ? |
|
the tests that are in conflict are probably fine but the most recent release removed jest completely, and I am having trouble with converting https://github.com/RooCodeInc/Roo-Code/blob/104b645e3c29ce329923c6aac01f0d6c383112e9/src/utils/__tests__/safeWriteJson.test.ts to use vitest |
|
@daniel-lxs standby , @cte something something that might help ... |
104b645 to
f2b457f
Compare
To reduce memory pressure and increase performance,
@mrubens, to answer your question: Currently in Roo, this behavior varies throughout the code base: for example, existing large objects in # pwd
~/.config/Code/User/globalStorage/rooveterinaryinc.roo-cline/tasks
# ls -l 37b0417d-65c3-4a48-8c9c-77d7ee556f3c/
total 188
-rw-------. 1 ewheeler ewheeler 95890 Jun 21 12:00 api_conversation_history.json
drwx------. 3 ewheeler ewheeler 4096 Jun 21 11:57 checkpoints
-rw-------. 1 ewheeler ewheeler 1438 Jun 21 11:58 task_metadata.json
-rw-------. 1 ewheeler ewheeler 82283 Jun 21 11:59 ui_messages.jsonStream-json does not appear to support pretty-printing. Here are some options, in order of increasing complexity:
|
|
This PR likely solves at least the following issues: #722: Task can't be opened Here is a test build for anyone who wishes to try this PR: If you try it out please report back. |
|
@mrubens this is a summary of all .json files that I could find in the project, and the MCP configuration in critical for consistency
less critical
Non-critical
Unless you can think of other user-serviceable files, I think the last (bold) one is the only one we should pretty-print. let me know what you think |
That sounds like a good idea! |
|
Sounds good! |
Support user-serviceable .roo/mcp.json file with pretty-printing - Remove safeWriteJson in favor of direct JSON.stringify to pretty-print user serviceable MCP configuration files Cc: @mrubens Signed-off-by: Eric Wheeler <[email protected]>
Applied in 0f0432c Was that the last issue to solve before merging? |
…ix (RooCodeInc#4733) Co-authored-by: Eric Wheeler <[email protected]> Co-authored-by: Daniel Riccio <[email protected]>
…ix (#4733) Co-authored-by: Eric Wheeler <[email protected]> Co-authored-by: Daniel Riccio <[email protected]>

Context
Closes #4311
This PR reintroduces the
safeWriteJsonutility from PR #3772 with critical improvements to fix the race condition identified in #4468 that caused the original implementation to be reverted in #4471.The
safeWriteJsonutility addresses issue #722 by preventing task data corruption through atomic writes, proper file locking, and error handling. This ensures task files remain consistent even during crashes or concurrent access, making tasks reliably accessible.Problem
The original implementation of
safeWriteJsonin PR #3772 had a race condition where it would fail with ENOENT errors during lock acquisition when a parent directory was just created. This happened because:saveApiMessagesfunction would callgetTaskDirectoryPathto create the task directory with{ recursive: true }safeWriteJsonwith the file pathproper-lockfilelibrary would perform anlstatoperation on the target file path during lock acquisitionSolution
This PR includes the fix from commit 3880518e4e128020327c7df74af4ee401e1400c6 which ensures directories exist and are fully synchronized before lock acquisition by:
fs.mkdir({ recursive: true })withinsafeWriteJsonitselfrealpath: falsein lock options to handle non-existent filesThese changes prevent the race condition that was causing issues in #4468 and ensure reliable operation even on slower file systems or under high load conditions.
Testing
Added comprehensive tests for directory creation capabilities including:
All tests pass successfully.
Fixes: #4468
See-also: #4471, #3772, #722
Important
Reintroduces
safeWriteJsonutility for atomic JSON writes, fixing race conditions and integrating it across the codebase with comprehensive tests.safeWriteJsonutility to handle JSON file writes atomically, fixing race conditions from previous implementation.fs.writeFilewithsafeWriteJsoninmodelCache.ts,modelEndpointCache.ts, andimportExport.ts.FileContextTracker.ts,apiMessages.ts, andtaskMessages.tsto usesafeWriteJson.webviewMessageHandler.tsto createmcp.jsonusingsafeWriteJson.cache-manager.tsandMcpHub.tsto usesafeWriteJsonfor cache and server config writes.safeWriteJson.test.tsto cover directory creation, error handling, and rollback scenarios.safeWriteJsoninMcpHub.test.tsto verify integration without actual file writes.This description was created by
for 64403b74ae71c6275a2dae76ec1df32e64964d90. You can customize this summary. It will automatically update as commits are pushed.