fix(session-fork): add 2s timeout to parent-fork token count resolution#102218
fix(session-fork): add 2s timeout to parent-fork token count resolution#102218hugenshen wants to merge 1 commit into
Conversation
Wrap the transcript read + byte-stat path in a withTimeout so a slow or very large parent transcript cannot stall session creation indefinitely. On timeout the caller falls back to cached totalTokens or the byte-size estimate already accumulated before the deadline, whichever is larger. Fixes openclaw#101718
|
Thanks for the context here. I swept through the related work, and this is now duplicate or superseded. Close as superseded: this branch is a useful focused attempt, but its timeout fallback can still proceed into parent transcript copying, while the sibling PR is open, mergeable, proof-positive, and handles unresolved parent sizing without inheriting unsafe parent context. Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Canonical path: Continue review on #101932, or an equivalent fail-closed timeout shape, and land one canonical fix after maintainer acceptance of unknown-size parent-fork semantics. So I’m closing this here and keeping the remaining discussion on #101932. Review detailsBest possible solution: Continue review on #101932, or an equivalent fail-closed timeout shape, and land one canonical fix after maintainer acceptance of unknown-size parent-fork semantics. Do we have a high-confidence way to reproduce the issue? Yes from source inspection: current main awaits parent-token resolution with byte/line bounds but no wall-clock timeout, and this PR's fallback can still flow into the parent transcript copy path. I did not run a live slow-filesystem reproduction in this read-only review. Is this the best way to solve the issue? No. This branch is a plausible partial mitigation, but the safer solution is the sibling fail-closed path that prevents unresolved parent sizing from entering transcript copy and has production-like terminal proof. Security review: Security review cleared: The diff changes session-fork runtime logic and adjacent tests only; no dependency, workflow, secret, package, permission, or supply-chain surface is changed. AGENTS.md: found and applied where relevant. What I checked:
Likely related people:
Codex review notes: model internal, reasoning high; reviewed against c69724ef3b50. |
Summary
resolveParentForkTokenCountRuntimereads the parent transcript file and parses recent usage with no timeout or abort boundary. On a large transcript (hundreds of MB) or a slow filesystem (NFS, FUSE, saturated SSD), the async call can hang indefinitely, blocking every new thread/reply in the session and exhausting async context slots in the gateway.withTimeout(2 000 ms). Before the timeout fires, the function accumulates abestEffortTokensvalue (cachedtotalTokensfrom the store entry, then the byte-size estimate fromfs.stat). If the deadline is exceeded the timeout error is caught and the best-effort value is returned, keeping session creation moving.bestEffortTokensmutation inside thewithTimeoutclosure and whether the catch path correctly returns the last accumulated value.Linked context
Closes #101718
Real behavior proof (required for external PRs)
pnpm devnot running — unit-test-only proof; no live gateway needed for this path).{ status: "fork", maxTokens: 100_000 }when runtime returnsundefined.vi.useFakeTimers+vi.advanceTimersByTimeAsync.vi.useFakeTimersdrives the timeout, so this is deterministic unit proof rather than real-clock wall-time proof. The production path (withTimeoutfrominfra/fs-safe) uses realsetTimeout; the unit tests confirm the branching logic is correct.Tests and validation
node scripts/run-vitest.mjs src/auto-reply/reply/session-fork.runtime.test.ts --run --reporter=verbose— 8/8 passnode scripts/run-vitest.mjs src/auto-reply/reply/session-fork.test.ts --run --reporter=verbose— 2/2 passsession-fork.runtime.test.ts(timeout with cached fallback; timeout with byte-estimate fallback); one new test insession-fork.test.ts(fork decision when runtime returnsundefined)Risk checklist
bestEffortTokensvariable is mutated inside the async closure beforewithTimeoutresolves; ifestimateParentTranscriptTokensFromBytesthrows synchronously that value staysundefinedand the catch returnsundefined— same as the old behavior.estimateParentTranscriptTokensFromBytesalready has its owntry/catchand returnsundefinedon error; the only new failure mode is the timeout path, which is tested.Current review state
codex reviewnot run (no local Codex binary available in this checkout).