fix: guard auth-profiles.json writes with gateway-compatible file lock (#758)#759
fix: guard auth-profiles.json writes with gateway-compatible file lock (#758)#759octo-patch wants to merge 1 commit intoValueCell-ai:mainfrom
Conversation
ValueCell-ai#758) ClawX's writeAuthProfiles() was doing a plain fs.writeFile() while the OpenClaw gateway uses a .lock sidecar file (acquireFileLock / withFileLock from file-lock-B4wypLkV.js) for exclusive access to the same file. This created a race where the gateway's locked READ-MODIFY-WRITE could overwrite a provider key that ClawX had just saved (or vice-versa), causing the second and subsequent providers to silently disappear from auth-profiles.json. Add withAuthProfilesLock() that mirrors the gateway's protocol: - Lock path: auth-profiles.json.lock (sidecar file) - Acquire: fs.open(lockPath, 'wx') -- atomic, throws EEXIST on collision - Lock body: JSON { pid, createdAt } for stale detection - Stale: PID no longer alive OR lock older than 30 s -> remove and retry - Release: fs.rm(lockPath, { force: true }) in a finally block Wrap the read-modify-write loops in saveProviderKeyToOpenClaw, saveOAuthTokenToOpenClaw, removeProviderKeyFromOpenClaw, and removeProviderFromOpenClaw with this lock so every Electron-side auth-profiles write participates in the same mutual-exclusion protocol as the gateway. Add three tests: lock sidecar is cleaned up on success, stale lock (dead PID) is auto-cleared, and concurrent saves within the same process are serialised so neither key is lost.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 96dae9f3e0
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| } catch { | ||
| return true; // unreadable / malformed lock → treat as stale |
There was a problem hiding this comment.
Don't delete lock files on transient parse/read failures
When open(lockPath, 'wx') succeeds, the lock file exists before its JSON body is fully written; a concurrent writer that gets EEXIST can read an empty/partial file, hit this catch, mark it stale, and rm a still-live lock. On POSIX this unlink can succeed even while the first process still holds the fd, allowing both processes into the critical section and reintroducing lost updates to auth-profiles.json under contention.
Useful? React with 👍 / 👎.
Problem
Resolves #758.
When a user saves a second (or later) provider, the API key intermittently disappears and an error toast appears.
Root cause: The OpenClaw gateway guards every write to
auth-profiles.jsonwith a.locksidecar file viawithFileLock()/acquireFileLock()(innode_modules/openclaw/dist/file-lock-B4wypLkV.js). Inside that lock it does a fresh READ → MODIFY → WRITE. ClawX'swriteAuthProfiles()was doing a plainfs.writeFile()without acquiring the same lock. This created a race:The first provider always works because the gateway is not yet running when it is saved. Subsequent providers race with a live gateway process.
Solution
Add
withAuthProfilesLock(agentId, fn)that implements the same.locksidecar file protocol as the gateway:auth-profiles.json.lockfs.open(lockPath, 'wx')— atomic, throwsEEXISTon collisionJSON { pid, createdAt }fs.rm(lockPath, { force: true })infinallyAll four functions that perform a read-modify-write on auth-profiles —
saveProviderKeyToOpenClaw,saveOAuthTokenToOpenClaw,removeProviderKeyFromOpenClaw,removeProviderFromOpenClaw— now wrap their per-agent loop body with this lock.Tests
Three new tests in
tests/unit/openclaw-auth.test.ts:.locksidecar file is removed after a successful writesaveProviderKeyToOpenClawcalls on the same agent both commit their keys (neither overwrites the other)