Fix/issue 98958 gateway lock fd leak#99291
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 3, 2026, 5:17 AM ET / 09:17 UTC. Summary PR surface: Source +8, Tests +30. Total +38 across 2 files. Reproducibility: yes. Source-level reproduction is high confidence: make Review metrics: 1 noteworthy metric.
Stored data model Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land this PR or an equivalent narrow Do we have a high-confidence way to reproduce the issue? Yes. Source-level reproduction is high confidence: make Is this the best way to solve the issue? Yes. Cleanup belongs inside AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 68e06b9ea148. Label changesLabel justifications:
Evidence reviewedPR surface: Source +8, Tests +30. Total +38 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
4dfc714 to
5f2ce17
Compare
5f2ce17 to
1f05ad1
Compare
|
Land-ready verification for exact head
No screenshot attached: this is a daemon resource-lifecycle fix with no visual surface. Thanks @chenyangjun-xy for the fix. |
…law#98958) When fs.open(lockPath, "wx") succeeds but handle.writeFile() fails (e.g. disk full / ENOSPC), close the file handle and remove the partially-written lock file before re-throwing to avoid a file descriptor leak and stale lock artifact. Changes: - src/infra/gateway-lock.ts: nested try-catch around writeFile - src/infra/gateway-lock.test.ts: test for fd close + lock cleanup - scripts/verify-gateway-lock-fd-leak*.mjs: fault-injection proof Co-Authored-By: Claude <[email protected]>
9228eb2 to
cdacea6
Compare
|
Merged via squash.
|
* fix(infra): close fd and remove lock file on writeFile failure (openclaw#98958) When fs.open(lockPath, "wx") succeeds but handle.writeFile() fails (e.g. disk full / ENOSPC), close the file handle and remove the partially-written lock file before re-throwing to avoid a file descriptor leak and stale lock artifact. Changes: - src/infra/gateway-lock.ts: nested try-catch around writeFile - src/infra/gateway-lock.test.ts: test for fd close + lock cleanup - scripts/verify-gateway-lock-fd-leak*.mjs: fault-injection proof Co-Authored-By: Claude <[email protected]> * test(infra): strengthen gateway lock cleanup proof --------- Co-authored-by: Claude <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
* fix(infra): close fd and remove lock file on writeFile failure (openclaw#98958) When fs.open(lockPath, "wx") succeeds but handle.writeFile() fails (e.g. disk full / ENOSPC), close the file handle and remove the partially-written lock file before re-throwing to avoid a file descriptor leak and stale lock artifact. Changes: - src/infra/gateway-lock.ts: nested try-catch around writeFile - src/infra/gateway-lock.test.ts: test for fd close + lock cleanup - scripts/verify-gateway-lock-fd-leak*.mjs: fault-injection proof Co-Authored-By: Claude <[email protected]> * test(infra): strengthen gateway lock cleanup proof --------- Co-authored-by: Claude <[email protected]> Co-authored-by: Peter Steinberger <[email protected]> (cherry picked from commit 36dd9ee)
What Problem This Solves
Fixes #98958: In
src/infra/gateway-lock.ts, whenfs.open(lockPath, "wx")succeeds but the subsequenthandle.writeFile()fails (e.g. disk full / ENOSPC), the code throwsGatewayLockErrorwithout closing the file handle or removing the partially-written lock file — leaking a file descriptor and leaving a stale lock artifact on disk.Root Cause
acquireGatewayLock()opens a lock file withfs.open(lockPath, "wx"), then writes the payload withhandle.writeFile(). The outertry-catchonly handled theEEXISTcase (lock already held). Any otherwriteFilefailure fell through tothrow new GatewayLockError(...)without cleanup — the fd fromfs.openwas never closed and the partially-written lock file was never removed.Why This Change Was Made
Wrap the payload-write block in a nested
try-catchinside the existingtryblock. WhenwriteFilefails:await handle.close()) to release the fdawait fs.rm(lockPath, { force: true })) to clean up the stale artifactcatchwraps it inGatewayLockErroras beforeThe success path is unchanged: the handle stays open and is closed later in the
release()callback.Changes
src/infra/gateway-lock.ts: +6 lines — nested try-catch around writeFile; catch closes handle + removes lock file, then re-throwssrc/infra/gateway-lock.test.ts: +31 lines — new test"closes handle and removes lock file when writeFile fails after open succeeds"Evidence
Verification environment
Regression tests (17/17, 1 new)
The new test:
fs.opento return a handle with a failingwriteFile(ENOSPC)handle.close()is called exactly once after the failurefs.rm(lockPath, { force: true })is called to remove the partial lock fileReal-process fault injection: open succeeds, writeFile fails
Standalone script (
scripts/verify-gateway-lock-fd-leak-fault.mjs) imports the productionacquireGatewayLockmodule viatsx. It wrapsfs.opento return a handle with a patchedwriteFilethat throwsENOSPC— simulating what the kernel does when the disk is genuinely full. The patchedcloserecords whether the production cleanup path executed.This is NOT a Vitest mock. The only mutation is
handle.writeFile(to inject the fault) andhandle.close(to record the call). All other logic — the inner try-catch, thefs.rm, theGatewayLockErrorwrapping — is the actual production code running in a real Node.js process.Measurement methodology:
What each assertion proves:
handle.close()called after writeFile failurefs.rm()User Impact
🤖 Generated with Claude Code