feat(tasks): add generic task-route lease module for cross-crash delivery origin recovery#95481
Conversation
…enclaw#95352 P1) ClawSweeper review on the cron integration PR (openclaw#95362) flagged P1: the current schema uses raw runId as the lease primary key, but current main deliberately supports multiple task records sharing a runId when their runtime/scopeKind/ownerKey/childSessionKey differ. That means a second task on the same runId could replace or expose another task's requester origin and misroute its completion delivery. Mirror the task-registry scope facts into the lease key so the (runId, scope) tuple is the actual primary key: - schema: add runtime, scope_kind, owner_key, child_session_key columns; PK becomes (run_id, runtime, scope_kind, owner_key, child_session_key); regen openclaw-state-schema.generated.{ts,d.ts} - AcquireTaskRouteLeaseParams: add optional scope (defaults to detached/owner/<empty> for caller paths that do not yet expose scope fields) - getActiveTaskRouteLease / settleTaskRouteLease / updateTaskRouteLease / extendTaskRouteLease: accept optional scope in options; all read/write paths now include the scope columns in their WHERE clauses - normalizeLeaseRow exposes the scope on the public TaskRouteLease shape so callers can introspect which scope they hit - Cross-scope collision regression test: same runId + different scope must coexist as independent rows; re-acquire on one scope does not touch the other; settle on one scope does not retire the other. Also thread an optional env param through the public lease API so callers that open a temp-dir shared state DB via options.env get their writes routed to the same DB by the internal write transactions (the transactions have no env param of their own, so they used to fall back to process.env and write to ~/.openclaw/state instead of the temp dir). Repro script updated to thread env through every lease call. Regression repro all 9 sections pass. Co-Authored-By: Claude <[email protected]>
|
Codex review: needs changes before merge. Reviewed June 21, 2026, 12:36 AM ET / 04:36 UTC. Summary PR surface: Source +608, Tests +720, Docs +50, Generated +63, Other +233. Total +1674 across 12 files. Reproducibility: yes. source-level for the reviewable surfaces: current main lacks the route-lease implementation, the PR head adds scoped lease storage/hooks/tests, and the docs mismatch is visible from grep because the documented production triggers are not present. I did not run live transport proof in this read-only pass. Review metrics: 2 noteworthy metrics.
Stored data model Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Copy recommended automerge instructionNext step before merge
Security Review findings
Review detailsBest possible solution: Land the scoped storage foundation only after lifecycle docs match the shipped hooks and maintainers accept the persistent route-state contract; keep the user-visible cron repair in the companion PR. Do we have a high-confidence way to reproduce the issue? Yes, source-level for the reviewable surfaces: current main lacks the route-lease implementation, the PR head adds scoped lease storage/hooks/tests, and the docs mismatch is visible from grep because the documented production triggers are not present. I did not run live transport proof in this read-only pass. Is this the best way to solve the issue? Mostly yes as a storage foundation: the scoped 5-tuple mirrors task-registry identity and avoids cross-scope route collisions. The safer pre-merge state is to fix the lifecycle docs and have maintainers explicitly accept the persistent route-state contract while reviewing the companion cron wiring separately. Full review comments:
Overall correctness: patch is correct AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 8d2e6d7686b0. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +608, Tests +720, Docs +50, Generated +63, Other +233. Total +1674 across 12 files. View PR surface stats
Acceptance criteria:
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
|
|
@clawsweeper re-review All four findings from the 04:03 UTC review are addressed at the current head P1 —
|
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
@vincentkoc — ClawSweeper pointed to you as the recent contributor to The cluster (all open, all
|
|
Closing PR #95481. Why: Issue #92460 carries
Both are blocked on the same product decision. Without maintainer direction, neither can merge regardless of proof or patch quality. Current cluster state:
What happens next: Code preserved on branch Re-focus: Shifting to bound-read PRs where the fix shape is already established and ClawSweeper acceptance is the only gate (#96776, #96786, #96782, #96790). |
What Problem This Solves
The detached-task completion delivery path (cron, subagent, ACP, codex) resolves its outbound origin at completion time. The originating session entry can be evicted, the shared main session bucket can be retargeted by another conversation, and the explicit delivery config may not survive all the way to the announce step. The task-route lease module (
src/tasks/task-route-lease.ts) captures the original outbound origin at job start and exposes it as a session-identity fallback for the delivery-target resolver.This is the storage foundation. The user-visible fix lands in the companion PR #95362 (isolated cron completion delivery wiring) once this is in.
Why This Change Was Made
A prior PR (#95352) was opened with the same code but ran into a ClawSweeper SHA-caching issue across five re-reviews (15:23, 16:31, 17:45, 18:19, 03:39 UTC) — the durable marker comments kept citing the pre-P1-fix SHA
7213007156even after the P1 fix landed in1269a4a7a9, driving the rating from 🐚 platinum hermit down to 🧂 unranked krab despite all findings being closed at the head. Closing it and reopening with the same code under a clean conversation lets ClawSweeper evaluate the diff at the canonical head on its first pass. The pre-close comment on #95352 explains this and points reviewers to the P1 commit, the 4 cross-scope tests, and the composite-PK schema on the actual headdcd9129a08of this branch.User Impact
requesterOriginfrom the lease.expireStaleTaskRouteLeases; task deletion cascades to the lease row via the newdeleteTaskRouteLeasesByTaskIdInDbhelper wired intodeleteTaskRowsWithDeliveryState.run_id, runtime, scope_kind, owner_key, child_session_key) that the task registry uses to disambiguate shared-runId task records, so the storage identity matches the runtime identity — no cross-scope row collision possible.Changes
src/state/openclaw-state-schema.sql—task_route_leasestable with composite PK and per-row scope columns defaulting todetached/owner/<empty>for existing callers.src/state/openclaw-state-schema.generated.{ts,d.ts}— regenerated from the SQL viapnpm db:kysely:gen.src/tasks/task-route-lease.ts— public API:acquireTaskRouteLease,getActiveTaskRouteLease,settleTaskRouteLease,updateTaskRouteLease,extendTaskRouteLease,expireStaleTaskRouteLeases,deleteTaskRouteLeasesByTaskIdInDb,resetTaskRouteLeasesForTests,mapDeliveryStatusToLeaseRetirement. All accept optionalscope?: TaskRouteLeaseScopeandenv?: NodeJS.ProcessEnv.src/tasks/task-executor.ts—createRunningTaskRunauto-acquires a lease for eligible detached tasks (skipped on the test-only paths that don't need one).src/tasks/task-registry.store.sqlite.ts—deleteTaskRowsWithDeliveryStatecascades totask_route_leasesin the same write transaction.src/tasks/task-route-lease.test.ts— 22 unit tests, including thetask-route lease scoped key (PR #95352 P1 review)describe block with 4 cross-scope collision cases.src/tasks/task-registry.store.test.ts— adds a cascade-cleanup integration test exercising the real delete path.docs/concepts/task-completion.md— new page describing the storage invariant, lifecycle phases, and ownership boundary. Registered indocs/docs.json.scripts/repro/issue-92460-task-route-lease-lifecycle.mts— 9-step real on-disk SQLite lifecycle repro.Cross-scope regression coverage (the P1 fix)
src/tasks/task-route-lease.test.ts:426-580, newdescribe("task-route lease scoped key (PR #95352 P1 review)"):(detached, owner, "", "")tuple and matches only that scope.Schema shape
Real behavior proof
requester_origin_json; the lease module reads/writes/deletes only the scope tuple the caller names.node --import tsx scripts/repro/issue-92460-task-route-lease-lifecycle.mts.node --import tsx scripts/repro/issue-92460-task-route-lease-lifecycle.mts— 9/9 PASS on real temp-dir SQLite, including the cross-scope collision steps that the prior raw-runId PK would have failed.node scripts/run-vitest.mjs src/tasks/task-route-lease.test.ts— 22 passed (was 13, +9 across the original 5 tests and the P1 4 cross-scope cases).node scripts/run-vitest.mjs src/tasks/task-registry.store.test.ts— 28 passed.pnpm tsgo:core— exit 0.npx oxlinton touched files — clean.pnpm docs:list—concepts/task-completion.mdlisted.=== Reproduction for issue #92460 ===block in the repro output above, plus the 22 lease-test passing assertions, including the fourtask-route lease scoped key (PR #95352 P1 review)cases that explicitly acquire two leases with the same runId and assert they are independent rows.getActivewith the matching scope returns the second row while the first row remains untouched;tsgo:corepasses, no lint errors.Companion PR
fix(cron): wire task-route lease into isolated cron delivery-target (closes #92460, stacked on #95352). Will be rebased onto this branch once it lands and add live transport proof.Related PR
fix(sdk): type-narrow manifest.files in pack staging root helper(independent test typecheck fix that was hiding behind this branch's check-test-types noise; already 🐚 platinum hermit withstatus: 👀 ready for maintainer look).