Skip to content

fix: close FileHandles explicitly to prevent Node 26 GC crash#99461

Closed
999wwx wants to merge 1 commit into
openclaw:mainfrom
999wwx:fix/node26-filehandle-gc-crash
Closed

fix: close FileHandles explicitly to prevent Node 26 GC crash#99461
999wwx wants to merge 1 commit into
openclaw:mainfrom
999wwx:fix/node26-filehandle-gc-crash

Conversation

@999wwx

@999wwx 999wwx commented Jul 3, 2026

Copy link
Copy Markdown

Summary

Node 26 promotes GC-finalized FileHandle from a deprecation warning to a hard ERR_INVALID_STATE error. Three locations in @openclaw/fs-safe 0.3.0 skip explicit handle.close() when a stream close event fires (handleClosedByStream / sourceClosedByStream / tempClosedByStream), relying on the stream auto-close to release the fd. On Node 26 the FileHandle object still needs an explicit .close() call — otherwise the GC finalizer throws.

The leak affects the inbound-media path: copyFileFallback pipelines a bounded read stream (opened on media/inbound/<uuid>.png) into a temp write handle. After pipeline() resolves, both handles are skipped for close because their stream-close flags are true.

Changes

Patch: @openclaw/[email protected]

Removes the if (!handleClosedByStream) / if (!sourceClosedByStream) / if (!tempClosedByStream) guards before handle.close() calls in three functions:

  1. writeStreamToTempSource (file-store-boundary.js) — always close handle after pipeline, in both success and catch paths
  2. copyFileFallback (root-impl.js) — always close source.handle and tempHandle in finally, unconditionally
  3. writeZipFileEntry (archive.js) — always close tempHandle after pipeline and in finally

Double-close is safe (all calls are wrapped in .catch()).

Fix: src/infra/gateway-lock.ts

acquireGatewayLock opened a FileHandle with fs.open(lockPath, "wx") but if handle.writeFile() threw, the handle was never closed (no finally block). Fixed by moving handle to a block-scoped variable that is cleared on success and closed in the catch block on error.

Tests: src/infra/fs-safe.test.ts

Added regression tests verifying that copyIn properly closes source FileHandles, both on success and on too-large error.

Test plan

  • Manual test: copyIn succeeds and file is readable
  • Manual test: copyIn with maxBytes rejects too-large and source is intact
  • CI: pnpm test passes
  • CI: check-package-patches guard passes with new allowlist entry

Fixes #99263

…aw#99263)

Node 26 promotes GC-finalized FileHandle from a deprecation warning to
ERR_INVALID_STATE. Three locations in @openclaw/fs-safe 0.3.0 skip
explicit handle.close() when a stream's 'close' event fires
(handleClosedByStream / sourceClosedByStream / tempClosedByStream),
relying on the stream's autoClose to release the fd. On Node 26 the
FileHandle object still needs an explicit .close() call to signal
disposal — otherwise the GC finalizer throws ERR_INVALID_STATE.

The leak affects the inbound-media path: copyFileFallback pipelines
sourceStream (opened on media/inbound/<uuid>.png) through a bounded
read stream into a temp write handle. After pipeline() resolves, both
the source handle and temp handle are skipped for close because their
stream-close flags are true.

Fix:
- Patch @openclaw/[email protected] to always call handle.close() in
  finally blocks regardless of stream-close flags, in three affected
  functions: writeStreamToTempSource, copyFileFallback, and
  writeZipFileEntry. Double-close is safe (wrapped in .catch()).
- Fix gateway-lock.ts: if handle.writeFile() throws, the FileHandle
  was leaked (no finally block). Move to try/finally with a
  handle-local variable that is cleared on success and closed in
  the catch block on error.
- Add regression tests verifying copyIn closes source handles.

Refs openclaw#99263
@openclaw-barnacle openclaw-barnacle Bot added scripts Repository scripts size: S labels Jul 3, 2026
@github-actions github-actions Bot added the dependencies-changed PR changes dependency-related files label Jul 3, 2026
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Dependency Guard

This PR changes dependency-related files. Maintainers should confirm these changes are intentional.

Changed files:

Maintainer follow-up:

  • Review whether the dependency changes are intentional.
  • Inspect resolved package deltas when lockfile, shrinkwrap, or workspace dependency policy changes are present.
  • Treat package-lock.json and npm-shrinkwrap.json diffs as security-review surfaces.
  • Run pnpm deps:changes:report -- --base-ref origin/main --markdown /tmp/dependency-changes.md --json /tmp/dependency-changes.json locally for detailed release-style evidence.

@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jul 3, 2026
@clawsweeper

clawsweeper Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Thanks for the contribution. This PR still needs the requested real-behavior proof, and the branch has been idle since that ask.

Close this external PR as stalled and unproven: it has had no head/check activity since July 3, 2026 despite a dated real-proof request, remains merge-conflicted, and retains blocking dependency and test defects.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #99263
Summary: This PR is a stalled candidate fix for the canonical Node 26 inbound-media FileHandle crash report; it does not establish a merge-ready resolution.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

So I’m closing this for now to keep the review queue honest. Please reopen or open a fresh PR with real-behavior proof (a live run, logs, or a reproducible validation transcript) and it will be reviewed again.

Review details

Best possible solution:

If the inbound-media crash still reproduces, open a fresh, narrowly rebased fix that excludes the already-merged gateway-lock work, uses an approved upstream fs-safe release or a synchronized temporary patch, and includes observable Node 26/forced-GC proof.

Do we have a high-confidence way to reproduce the issue?

No high-confidence live reproduction was supplied for this PR. The linked issue has production Node 26 crash evidence and the patched dependency path plausibly skips explicit closes, but no after-fix runtime proof was posted.

Is this the best way to solve the issue?

No. Explicit cleanup is plausible, but this branch is not the best mergeable solution because its dependency patch is unsynchronized, its gateway-lock change is superseded, and its tests do not observe the claimed close behavior.

Security review:

Security review needs attention: The filesystem dependency patch is a supply-chain-sensitive change whose exact patched install state is not locked or independently proven.

  • [low] Lock the patched dependency reproducibly — pnpm-workspace.yaml:155
    The new package patch is allowlisted but has no corresponding lockfile patch hash, preventing a frozen install from resolving a reviewable dependency state.
    Confidence: 0.98

AGENTS.md: found and applied where relevant.

What I checked:

  • Expired proof request: The PR received the status: 📣 needs proof label on July 3, 2026; its body still contains only checklist claims and no after-fix Node 26, forced-GC, or inbound-media runtime evidence. (dacfcf9d195d)
  • Unresolved frozen-install defect: The branch adds a patchedDependencies entry but does not include the corresponding pnpm-lock.yaml patch hash. The prior completed review recorded ERR_PNPM_LOCKFILE_CONFIG_MISMATCH, and the current branch has no follow-up commit. (pnpm-workspace.yaml:155, dacfcf9d195d)
  • Gateway-lock work is already merged elsewhere: The PR repeats gateway-lock cleanup on a path changed by merged PR Fix/issue 98958 gateway lock fd leak #99291. That PR merged as commit 36dd9ee3c3c188cc0619f0a3277c64c4b96a9615; this branch is now reported mergeableState dirty and needs a rebase rather than carrying a competing implementation. (src/infra/gateway-lock.ts:273, 36dd9ee3c3c1)
  • Canonical crash report remains open: The linked Node 26 inbound-media crash report remains the canonical user-impact record and has production crash evidence; this PR is only a candidate fix until a proof-backed, mergeable implementation lands. (dacfcf9d195d)

Likely related people:

  • steipete: Merged the already-landed gateway-lock cleanup and prior review history links this person to fs-safe wrapper and package-patch guard maintenance. (role: merger and recent area contributor; confidence: high; commits: 36dd9ee3c3c1; files: src/infra/gateway-lock.ts, src/infra/fs-safe.ts, scripts/check-package-patches.mjs)
  • chenyangjun-xy: Authored the merged gateway-lock repair that now overlaps this branch's acquireGatewayLock change. (role: gateway lock feature-history contributor; confidence: high; commits: 36dd9ee3c3c1; files: src/infra/gateway-lock.ts)

Codex review notes: model internal, reasoning high; reviewed against 99046179a8fe.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 automation 🚨 May affect CI, automerge, proof capture, label sync, or maintainer automation. labels Jul 3, 2026
@openclaw-barnacle

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Jul 18, 2026
@clawsweeper

clawsweeper Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper applied the proposed close for this PR.

@clawsweeper clawsweeper Bot closed this Jul 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies-changed PR changes dependency-related files merge-risk: 🚨 automation 🚨 May affect CI, automerge, proof capture, label sync, or maintainer automation. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P1 High-priority user-facing bug, regression, or broken workflow. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. scripts Repository scripts size: S stale Marked as stale due to inactivity status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Gateway crashes with ERR_INVALID_STATE (FileHandle closed during GC) on Node 26 when handling inbound image media

1 participant