fix(session-fork): skip fork when parent size is unresolved instead of forking (#101718)#101767
fix(session-fork): skip fork when parent size is unresolved instead of forking (#101718)#101767LZY3538 wants to merge 3 commits into
Conversation
…penclaw#101718) resolveParentForkTokenCount reads the parent session transcript file to estimate token count, but had no timeout. On large files or slow filesystems this could block session creation indefinitely. Race the runtime call against a 2-second deadline. On timeout return undefined, which resolveParentForkDecision already handles correctly by proceeding with fork without a token estimate. Co-Authored-By: Claude <[email protected]>
610462c to
153d111
Compare
|
Thanks for the context here. I swept through the related work, and this is now duplicate or superseded. Close as superseded: the revised branch addresses the prior unsafe timeout-to-fork concern, but it is now conflicting against current main and the open proof-sufficient #101932 carries the same fix on the current accessor-based fork path. Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Canonical path: Review and land or reject #101932 as the canonical current-base fix for #101718; reopen or rebase this PR only if that replacement path stops being viable. So I’m closing this here and keeping the remaining discussion on #101932 and #101718. Review detailsBest possible solution: Review and land or reject #101932 as the canonical current-base fix for #101718; reopen or rebase this PR only if that replacement path stops being viable. Do we have a high-confidence way to reproduce the issue? Yes. Source inspection on current main shows parent-fork decisions still await token-count resolution before returning, and the linked issue plus PR proof describe the slow-transcript path. Is this the best way to solve the issue? No for this branch as the landing path. The revised fix shape is useful, but #101932 applies the same current behavior to the post-accessor code and is the safer canonical path. Security review: Security review cleared: The diff changes session-fork logic, tests, and a local proof script; no dependency, workflow, secret, package, or supply-chain surface changed. AGENTS.md: found and applied where relevant. What I checked:
Likely related people:
Codex review notes: model internal, reasoning high; reviewed against 534ace4d8a49. |
…f forking (openclaw#101718) The prior 2s token-resolution timeout returned `undefined`, which made `resolveParentForkDecision` fork the parent. The fork runtime then reads the whole parent transcript unbounded (`readForkSourceTranscript` → `readRegularFile`), so a slow/huge parent still stalled session creation *after* the timeout fired and bypassed the documented oversized-parent skip (review P1). Never fork after an unresolved size check: on timeout, fall back to a fast, stat-only byte estimate (`estimateParentForkTokensFromSizeRuntime`) so an oversized parent still skips to isolated context; if even the stat probe times out, skip conservatively via a new `parent-size-unresolved` decision. The fork read is now only entered for a parent whose size was confirmed small. Adds unit coverage for the fallback/skip paths and a self-contained real-behavior proof (`scripts/proof-session-fork-timeout.ts`) that drives the real modules against real transcripts with injected filesystem latency. Co-Authored-By: Claude <[email protected]>
…lpers Wrap the setTimeout calls in the deadline/sleep Promise executors in block bodies so the executor no longer returns the timer handle (oxlint no-promise-executor-return). Co-Authored-By: Claude <[email protected]>
|
Addressed the P1 ("Do not fork after a timed-out size check"). The revised fix no longer forks a parent whose size is unresolved. What changed (
Real behavior proof (was the P1 proof ask): added @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
What Problem This Solves
resolveParentForkTokenCountreads the parent session transcript to estimate token count when forking a session, but had no timeout. On large transcripts (hundreds of MB) or slow filesystems (NFS, FUSE, saturated SSD), this blocks session creation indefinitely, accumulating blocked async contexts that exhaust gateway connection slots (#101718).Why This Change Was Made (revised after review)
The first revision added a 2-second
Promise.racetimeout that returnedundefinedon timeout. Review correctly flagged this as unsafe (P1):undefinedmakesresolveParentForkDecisionreturn{status: "fork"}, and the fork runtime (forkSessionFromParentRuntime→readForkSourceTranscript→readRegularFile) then reads the entire parent transcript with no bound. So a slow/huge parent could still stall session creation after the timeout fired, and an oversized parent that should start with isolated context was forked instead — bypassing the documented 100K-token skip.This revision fixes the root cause: never fork after an unresolved size check.
estimateParentForkTokensFromSizeRuntime). A singlefs.statis O(1) regardless of file size, so a multi-hundred-MB parent still yields an estimate → skip (isolated context), never a whole-file read.parent-size-unresolveddecision), rather than forked.Net effect: the fork read is only ever entered for a parent whose size was confirmed small, so it is inherently bounded. Oversized or unresolvable parents skip to isolated context — the safe, documented behavior.
Evidence
Layer 1 — Real behavior proof (slow filesystem, real modules, no mocks)
scripts/proof-session-fork-timeout.tsdrives the realresolveParentForkDecision/forkSessionEntryFromParentagainst real on-disk transcripts, injectingfslatency to simulate a slow mount (the exact issue scenario). Elapsed times prove the deadline is enforced and the fork read is never entered for an oversized/unresolvable parent:The Baseline case shows the unguarded runtime blocking 3s on a slow read (what the deadline must bound); Case 1c proves the storage-boundary entry point returns
skippedin ~2s for an oversized parent — it never enters the unbounded fork read that review flagged.Layer 2 — Focused unit tests
The prior revision's test asserted the buggy behavior (
forkwith noparentTokenson timeout); it is replaced with the fallback/skip coverage above.Layer 3 — Static checks
Type checks (
tsgo:core) run in CI on this branch.Changes
src/auto-reply/reply/session-fork.tssrc/auto-reply/reply/session-fork.runtime.tssrc/auto-reply/reply/session-parent-fork-prepare.tssrc/auto-reply/reply/session-fork.test.tsscripts/proof-session-fork-timeout.ts(proof only)Closes #101718.
🤖 Generated with Claude Code