Skip to content

Unclosed FileHandle on session JSONL lock crashes gateway on Node ≥24 under sustained session-store load #84820

Description

@cjalden

Summary

The gateway leaks unclosed fs.FileHandle references on session transcript .jsonl.lock files. On Node 22 (the supported LTS line per the 2026.5.18 release notes) this surfaces only as a deprecation warning, but on Node 24+ V8 promotes FileHandle GC to a fatal ERR_INVALID_STATE, which crashes the gateway process whenever the V8 GC happens to sweep one before OC closes it.

In our deployment this manifests as a chronic ~2-hour crash cadence on a single bot that accumulated a large session backlog (2,446 JSONL files in ~/.openclaw/agents/main/sessions/). Other bots on the same host / OC / Node version don't crash — they have 5–100 sessions on disk and don't generate enough open-handle pressure for V8 to ever GC a leaked one.

Environment

  • OC: 2026.5.18 (npm)
  • Node: 25.9.0 (three majors above the documented Node 22.19 minimum, but well within what users on rolling-release Homebrew get by default)
  • OS: macOS 26.4.1 arm64
  • Bot has been running ~30h under sustained activity; heartbeat interval is the gateway default 7,200,000ms (gateway/heartbeat: {"intervalMs":7200000} heartbeat: started)
  • Session count on disk: 2,446 .jsonl files; no stale .lock files at rest

Symptom

Every ~2h a stability bundle is written with reason: uncaught_exception and this error:

A FileHandle object was closed during garbage collection. This used to be allowed with a deprecation warning but is now considered an error. Please close FileHandle objects explicitly. File descriptor: <N> (/Users/<bot>/.openclaw/agents/main/sessions/<uuid>.jsonl.lock)

The FD number and session UUID rotate; the path pattern is invariant: always a .jsonl.lock file in the agent's `sessions/` directory.

On the next launchd-driven restart the gateway's `main-session-restart-recovery` logs:

```
warn marked 1 interrupted main session(s) from stale transcript locks
warn marked interrupted main session failed: agent:main:main:heartbeat (transcript tail is not resumable)
```

confirming the leaked handle was held by the heartbeat session.

After ~4 crashes in 8h, macOS launchd's anti-thrash kicks in (`Successfully spawned … because inefficient`) — downstream symptom, not the root cause.

Reproducer

  1. Run gateway on Node ≥24
  2. Accumulate ~2,000+ session JSONL files in the agent's sessions directory
  3. Let the default 2h heartbeat fire several times
  4. Crash typically occurs within 2–4 hours

Likely root cause

OC's session-store code opens FileHandles for transcript locks during heartbeat-session write / scan operations and doesn't always go through an explicit `.close()` on every return path (likely error paths or interrupted writes). On Node 22 this is harmless because GC-close was warn-only; on Node 24+ it's fatal.

The stability bundle includes the diagnostic-phase log up to seq=181 (load → normalize → plugins → channels) but does not include the throwing stack frame, since the crash is in V8's GC sweep rather than user code.

Suggested fix direction

Audit `FileHandle` open call sites in the session-store layer (anywhere transcript locks are taken) for non-explicit close paths, especially:

  • Error returns mid-write
  • Heartbeat-session interrupt handling on shutdown
  • Session metadata scans — the likely dominant pressure source given the 2,446-session correlation

Wrapping with `try { … } finally { await handle.close() }` or porting to `fs.promises.readFile`-style auto-closing helpers should close the leak.

Workaround for affected users

Either downgrade Node to the documented 22.19+ LTS line, or prune old session JSONL files until the on-disk count is in the low hundreds.

Stability bundle excerpt (sanitized)

```json
{
"version": 1,
"generatedAt": "2026-05-21T04:32:40.158Z",
"reason": "uncaught_exception",
"process": { "pid": 72273, "platform": "darwin", "arch": "arm64", "node": "25.9.0", "uptimeMs": 7208113 },
"error": {
"name": "Error",
"code": "ERR_INVALID_STATE",
"message": "A FileHandle object was closed during garbage collection. This used to be allowed with a deprecation warning but is now considered an error. Please close FileHandle objects explicitly. File descriptor: 38 (/Users//.openclaw/agents/main/sessions/.jsonl.lock)"
}
}
```

I have 10 stability bundles over a 31h window with the same error signature (different FD / UUID each time) — happy to share more if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.impact:crash-loopCrash, hang, restart loop, or process-level availability failure.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦐 gold shrimpDecent issue quality, but reproduction details are still incomplete.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions