Conversation
Host setTimeout coerces Infinity and delays above 2^31-1 ms to 1ms.
createRouter({ gcTime: Infinity }) and a 30-day gcTime therefore
scheduled a 1ms GC timer (TimeoutOverflowWarning) and rescheduled
it every millisecond.
Skip non-finite gcTime so those matches stay cached. Clamp finite
overflows to 2^31-1 ms so the existing reschedule path can expire
them later.
Signed-off-by: Sebastien Tardif <[email protected]>
|
🦞👀 Pull request received. I will update this pull request when review starts. ClawSweeper review completeClawSweeper finished reviewing this revision. The review result is being finalized. |
|
Codex review: blocked before merge. Reviewed September 5, 2026, 12:00 PM ET / 16:00 UTC. ClawSweeper reviewWhat this changesThe PR prevents infinite and oversized cache-retention periods from overflowing host timers, with documentation and three regression tests. Regression provenancePossible regression — suspected (reviewed change). No predecessor PR is attributed. Merge readiness⛔ Blocked before merge - 4 items remain The overflow fix remains necessary on main and has convincing Node proof, but both previously reported cache-lifetime defects remain in the unchanged head. Priority: P2 Review scores
Verification
How this fits togetherThe standalone UI router loads routes supplied by applications and caches their components and data. Its garbage-collection scheduler decides when unused or preloaded matches leave that cache. flowchart TD
A[Navigation or preload] --> B[Route loading]
B --> C[Cached route matches]
C --> D{Retention duration}
D -->|Infinite| E[Keep without timer]
D -->|Finite| F[Bounded timer]
F --> G[Recheck age or remove match]
Before merge
Findings
Agent review detailsSecurityNone. Review metrics
Merge-risk optionsMaintainer options:
Copy recommended automerge instructionTechnical reviewBest possible solution: Keep the bounded-delay scheduler, reserve permanent retention for positive Infinity, and cancel obsolete timers whenever retention changes. Do we have a high-confidence way to reproduce the issue? Yes: current-main source passes Infinity and 30-day durations directly to setTimeout, matching the supplied before/after Node traces. Both remaining defects also have concrete public-router sequences, but were not executed during this review. Is this the best way to solve the issue? Not yet: clamping each timer while retaining age-based rescheduling is appropriate, but the early return must preserve other expiry behavior and cancel an existing timer. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against ea06377b0e80. LabelsLabel justifications:
EvidenceAcceptance criteria:
What I checked:
Likely related people:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (14 earlier review cycles; latest 8 shown)
|
What Problem This Solves
Fixes an issue where consumers that call
createRouter({ gcTime: Infinity }), setpreloadGcTime: Infinity, or pass a retention longer than the host timer limit (for example30 * 24 * 60 * 60 * 1000) would schedule a 1ms garbage-collection timer. Node and browsers coercesetTimeoutdelays outside the signed 32-bit range to 1ms, so the unused match is re-checked every millisecond and Node printsTimeoutOverflowWarning.staleTime: Infinityis not affected because it is a comparison, not a timer.Why This Change Was Made
scheduleGcnow skips a timer whengcTimeorpreloadGcTimeis not finite, soInfinitykeeps the cached match until it is replaced orstop()runs. Finite values above2^31-1ms are clamped to that limit. The existing callback still reschedules when the match is younger thangcTime, so a 30-day retention can expire later instead of spinning 1ms timers. No public exports, types, history adapter shape, or loader signature changed.User Impact
gcTime: InfinityandpreloadGcTime: Infinitykeep unused cached matches without overflowing the host timer. A 30-day (or other overflow) retention no longer emitsTimeoutOverflowWarningor wake the event loop every millisecond. Default 30-minute GC is unchanged. Apps that never passInfinityor a delay above about 24.8 days behave as before.Evidence
Live
nodeagainst the builtdist/index.json this branch, macOS 26.6.2, Node v26.7.0. The same public sequence (createRouter({ gcTime }),navigate("chat"),navigate("fast"), then 20ms) was run against the unpatched bundle fromupstream/mainand the patched bundle.Unpatched
dist/index.js(gcTime: Infinity, 16 overflow warnings in 20ms):Unpatched
dist/index.js(gcTime: 30 * 24 * 60 * 60 * 1000):Patched
dist/index.js(gcTime: Infinity, no overflow warning):Patched
dist/index.js(gcTime: 2592000000, no overflow warning):pnpm run checkpassed locally (format, typecheck, lint, 23 tests, pack/import).This timer path has been in
scheduleGcsincef047b64a87a5(2026-06-20,refactor: finalize router match loading). Same class of host-timer overflow: TanStack Query #6287 (TimeoutOverflowWarninginQuery.scheduleGc). Adjacent but different: #20 (stale navigation cancellation), #21 (loader redirect hop cap), #24 (history listener context).Real behavior proof
Behavior or issue addressed:
createRouter({ gcTime: Infinity })and a 30-daygcTimeno longer overflowsetTimeoutinto a 1ms GC timer (TimeoutOverflowWarning). Unused cached matches stay cached without a 1ms reschedule loop.Real environment tested: macOS 26.6.2 arm64, Node v26.7.0,
@openclaw/uirouterbuilt fromfix/f003-gctime-overflowat/tmp/oc-pr-uirouter-F003.Exact steps or command run after this patch:
cd /tmp/oc-pr-uirouter-F003 node /tmp/proof-uirouter-f003.mjs /tmp/oc-pr-uirouter-F003/dist/index.js Infinity node /tmp/proof-uirouter-f003.mjs /tmp/oc-pr-uirouter-F003/dist/index.js 30dEvidence after fix: terminal output from the patched
dist/index.js:Observed result after fix: After
createRouter({ gcTime: Infinity })(and the 30-day value) plusnavigate("chat")thennavigate("fast"), Node emitted 0TimeoutOverflowWarningevents andcachedMatchesstill heldchatafter 20ms. The same commands against the unpatched bundle emitted 16 and 18 overflow warnings (Timeout duration was set to 1).What was not tested: A browser
setTimeoutin a running OpenClaw UI shell, and a per-routegcTime: Infinityoverride (samescheduleGchelper).