#92241: Gateway holds stale module import paths after update/rollback β inbound messages silently dropped (ERR_MODULE_NOT_FOUND)#92351
Conversation
β¦s after update/rollback After an in-place npm install -g update or rollback, dist chunk hashes rotate while the gateway process is still running. Dynamic import() of lazy reply modules that transitively reference old hashed chunks fails with ERR_MODULE_NOT_FOUND, and the unprotected call sites silently swallow the error so inbound messages disappear. - Add isDistRotationError() classifier to detect ERR_MODULE_NOT_FOUND / MODULE_NOT_FOUND errors that reference openclaw/dist paths. - Add guardedLoad() wrapper that catches dist-rotation errors, clears the stale loader cache, logs a visible operator warning, and re-throws so the inbound delivery fails visibly instead of silently. - Route all seven reply-path lazy loaders through guardedLoad(). This is defense-in-depth: the operator gets a clear "restart required" message instead of discovering the silent message drop hours later. Closes openclaw#92241 Co-Authored-By: Claude <[email protected]>
|
Codex review: needs real behavior proof before merge. Reviewed June 15, 2026, 4:15 PM ET / 20:15 UTC. Summary PR surface: Source +70, Tests +66, Other +188. Total +324 across 4 files. Reproducibility: yes. at source level: the linked rollback report plus current lazy import boundaries show a credible stale hashed chunk path after package replacement. No live current-main package-swap reproduction was established in this read-only review. Review metrics: 2 noteworthy metrics.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Security Review findings
Review detailsBest possible solution: Centralize stale-dist detection and recovery at a process or outer reply-dispatch boundary that covers the full inbound path, emits supervisor-neutral guidance, avoids repeated retries against deleted chunks, and is backed by live package-swap inbound-message proof. Do we have a high-confidence way to reproduce the issue? Yes at source level: the linked rollback report plus current lazy import boundaries show a credible stale hashed chunk path after package replacement. No live current-main package-swap reproduction was established in this read-only review. Is this the best way to solve the issue? No. The classifier is useful, but the proposed fix is too narrow; the maintainable solution belongs at a lifecycle or outer dispatch boundary that covers every stale-dist import path. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 32af1c069727. Label changesLabel justifications:
Evidence reviewedPR surface: Source +70, Tests +66, Other +188. Total +324 across 4 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
|
β¦otation guard - Add 6 isDistRotationError test cases in lazy-promise.test.ts (positive, negative, boundary: null, undefined, non-object) - Add repro-92241.ts proof script importing production modules - Demonstrates guardedLoad detects ERR_MODULE_NOT_FOUND in dist/, clears stale cache, logs operator warning, re-throws Closes openclaw#92241 Co-Authored-By: Claude <[email protected]>
|
@clawsweeper re-review |
|
π¦π§Ή I asked ClawSweeper to review this item again. Re-review progress:
|
- src/shared/lazy-promise.ts: add {} around early-return bodies in
isDistRotationError (curly rule)
- scripts/repro-92241.ts: remove redundant boolean-literal comparisons,
use empty catch binding
Co-Authored-By: Claude <[email protected]>
|
@clawsweeper re-review |
|
π¦π§Ή I asked ClawSweeper to review this item again. Re-review progress:
|
|
@clawsweeper re-review |
Co-Authored-By: Claude <[email protected]>
|
@clawsweeper re-review |
|
π¦π§Ή I asked ClawSweeper to review this item again. Re-review progress:
|
β¦ importer path
Previously the regex matched openclaw/dist/ anywhere in the error
message, which caused false positives when a third-party dependency
was missing but the importer happened to be under openclaw/dist/.
Now extract the missing module path from the Node.js ESM error format
("Cannot find module 'TARGET' imported from IMPORTER") and verify
that the target itself is under the dist tree.
- Add P1 regression test: third-party dep missing, importer in dist β false
- Update positive test cases to use realistic "imported from" format
Closes openclaw#92241
Co-Authored-By: Claude <[email protected]>
|
@clawsweeper re-review |
|
π¦π§Ή I asked ClawSweeper to review this item again. Re-review progress:
|
Summary
Guard lazy reply-module loaders against
ERR_MODULE_NOT_FOUNDcaused by staledist chunk hashes after an in-place
npm install -gupgrade or rollback.Previously, a running gateway process would silently drop all inbound messages
because the lazy
import()resolved to chunk names that no longer existed ondisk. Now
isDistRotationErrordetects the stale-dist condition, the guardemits an operator-visible warning pointing to
systemctl restart, and theerror propagates so the failure is no longer silent.
Root Cause
After
npm install -g openclaw@<version>, thedist/tree gets new chunkhashes (e.g.
cleanup-DlVQZQex.jsβcleanup-DbGY5-v-.js). The runninggateway process still holds the old Node.js module cache, so a lazy dynamic
import()for a module that transitively references the old hash hitsERR_MODULE_NOT_FOUND. The failure surfaces only when an inbound messagearrives and triggers the lazy load β health checks and channel connections
look fine, so the operator sees a healthy gateway silently dropping messages.
The fix adds a narrow detection function (
isDistRotationError) in the sharedlazy-promisemodule and wraps the seven lazy-load call sites inget-reply.tswith aguardedLoadhelper that clears the stale cache and logsthe restart instruction.
Real behavior proof
Behavior addressed:
isDistRotationErrordiscriminatesERR_MODULE_NOT_FOUND/MODULE_NOT_FOUNDerrors whose message referencesopenclaw/dist/(the tellfor a hashed chunk stale after update/rollback).
guardedLoadcatches thatcondition, clears the stale loader promise, emits an operator-visible
error+warnlog pair, and re-throws so the inbound delivery pipeline cansurface the failure instead of silently dropping messages.
Real environment tested: Linux 4.19.112-2.el8.x86_64, Node.js v22.22.0, tsx 4.x,
openclaw workspace import of production
lazy-promiseandinfra/errorsmodules.Exact steps or command run after this patch:
Evidence after fix:
lazy-promise.test.ts: 11 tests passed (5 existing + 6 newisDistRotationErrorcases).scripts/repro-92241.ts: 16 assertions passed, 0 failed, exercising positive/negativedetection, guardedLoad cache-clear, operator-warning log emission, error propagation,
and non-rotation pass-through.
Observed result after fix:
What was not tested: running an actual
npm install -gupgrade/rollback whilethe gateway is live with inflight Signal messages. That requires a full systemd
integration setup and is not feasible in this verification environment.
Regression Test Plan
pnpm test -- --run src/shared/lazy-promise.test.tspasses (11 tests)tsx scripts/repro-92241.tspasses (16 assertions, 0 failed)AI-assisted: built with Claude Code