Skip to content

fix(cron): wire task-route lease into isolated cron delivery-target (closes #92460, stacked on #95352)#95012

Closed
wangmiao0668000666 wants to merge 9 commits into
openclaw:mainfrom
wangmiao0668000666:fix/92460-task-route-lease
Closed

fix(cron): wire task-route lease into isolated cron delivery-target (closes #92460, stacked on #95352)#95012
wangmiao0668000666 wants to merge 9 commits into
openclaw:mainfrom
wangmiao0668000666:fix/92460-task-route-lease

Conversation

@wangmiao0668000666

@wangmiao0668000666 wangmiao0668000666 commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Isolated cron completion delivery drops explicit delivery.channel because the originator session entry is evicted, or the shared main session bucket was retargeted by another conversation, between job start and completion. Today:

  • The originating session entry can be evicted from the session store before completion fires.
  • The shared agent-main session bucket is last-writer-wins across conversations, so lastChannel/lastTo can point to a different conversation's room when the cron completes.
  • The explicit job.delivery config may not survive to the announce step because resolveDeliveryTarget resolves the target from the session entry, not from job.delivery directly.

The reported case is the worst combination: a manual cron with delivery.channel: "webchat" and no explicit to, an isolated session that has not routed any message yet (no deliveryContext / lastTo), and a shared main bucket retargeted by another conversation.

Closes #92460.

Stacking

This PR is the cron-side wiring of a 2-PR split that addresses ClawSweeper's triage: needs-pr-context finding and the persistent XL-size + 3× 🚨 merge-risk signal. The infrastructure half (the generic task_route_leases SQLite module) is in #95352, which should merge first.

PR Branch What Files Why first
#95352 feat/task-route-lease-module task_route_leases SQLite table + generic lease module + tests + lifecycle repro 7 / +966 Independent, reusable by subagent (#92076) / ACP / codex. No cron coupling.
#95012 (this) fix/92460-task-route-lease Cron acquire / settle / routability / manual runId / GC owner + 4 cron repro scripts 16 / +1721 Depends on #95352's lease API. Closes #92460.

Until #95352 merges, this PR's diff-vs-main shows the union (lease module + cron wiring = 22 files / +2685 lines), because GitHub doesn't allow a fork branch as a PR base. After #95352 lands, this branch will be rebased onto #95352's merge commit, dropping the 3 lease-module commits, and the diff-vs-main will shrink to the 16 cron files (+1721 lines).

What changed since the last ClawSweeper review

ClawSweeper reviewed this PR five times in the last 24 hours (c1b49395787fea99fdb89d81b0481538733d1b71, plus the current head). Each round found a real follow-up; this head addresses all of them:

  • P1 fix: add @lid format support and allowFrom wildcard handling #1 (manual runId alignment)finishPreparedManualRun now passes runId: runId (i.e. prepared.runId, the manual: id when present) to executeJobCoreWithTimeout. Task ledger row stays on cron: id; only the resolver-bound run id changes. New end-to-end test pins the alignment.
  • P1 fix: add @lid format support and allowFrom wildcard handling #1 (manual cron path not wired) — already addressed in 7fea99fdb8; carried forward.
  • P2 (stale-lease GC had no production owner) — already addressed in 7fea99fdb8; carried forward.
  • P3 (test isolation + lint + mock fixes) — already addressed in c1b4939578; carried forward.
  • P1 (proof gap, 4th round) — current-head after-fix proof added in the Evidence section below.

The full commit-by-commit history is preserved in this branch.

Evidence

The full structured after-fix proof is in the Real behavior proof section below (Behavior addressed, Real environment tested, Exact steps run after this patch, Evidence after fix, Observed result after fix, What was not tested). Quick summary:

  • 51 unit tests passing across 4 test files (32 cron-related + 19 ops.test.ts regression)
  • 24 PASS steps across 4 repro scripts (lifecycle + cron completion + channel-only + manual-cron-lease)
  • Lint exit 0 on touched files
  • Type check (pnpm tsgo:core) exit 0
  • Live behavior: the manual: runId flows through executeJobCoreWithTimeoutrunIsolatedAgentJobresolveDeliveryTarget so the resolver-side getActiveTaskRouteLease(runId) recovers the lease even when the session entry is evicted; the resolver is a routability-aware fallback so the lease is consulted only when higher-precedence sources are unroutable.

Real behavior proof

Behavior addressed: an isolated manual cron with delivery.channel: "webchat" and no explicit to, whose originating session entry was evicted (or whose shared main session bucket was retargeted) before completion fires, still delivers to the resolved target via the task-route lease. The lease is keyed by the manual: run id, and the execution pipeline (executeJobCoreWithTimeoutrunIsolatedAgentJobresolveDeliveryTarget) forwards the same manual: runId to the resolver. Stale leases from process crashes are GC'd within an hour by leaseGcTimer.

Real environment tested: Linux, Node 22.19, real on-disk SQLite via openOpenClawStateDatabase({ env: { OPENCLAW_STATE_DIR: tempDir } }) against fs.mkdtempSync temp dirs. Human ran all repros and tests on a clean checkout.

Exact steps or command run after this patch:

  • pnpm exec tsx scripts/repro/issue-92460-channel-only-empty-session.mts — 7 PASS
  • pnpm exec tsx scripts/repro/issue-92460-cron-completion-leases.mts — 6 PASS
  • pnpm exec tsx scripts/repro/issue-92460-task-route-lease-lifecycle.mts — 7 PASS (covered by feat(tasks): add generic task-route lease module for cross-crash delivery origin recovery #95352; reproduced here as smoke test)
  • pnpm exec tsx scripts/repro/issue-92460-manual-cron-lease.mts — 4 PASS (the reported [Bug]: Isolated cron completion announcer drops explicit delivery.channel on final controller return #92460 shape)
  • node scripts/run-vitest.mjs src/tasks/task-route-lease.test.ts — 13 passed
  • node scripts/run-vitest.mjs src/cron/isolated-agent/delivery-target.issue-92460.test.ts src/cron/isolated-agent/run.issue-91613-preflight.test.ts — 13 passed
  • node scripts/run-vitest.mjs src/cron/service/ops.issue-92460-manual-lease.test.ts — 6 passed
  • node scripts/run-vitest.mjs src/cron/service/ops.test.ts — 19 passed (no regression)
  • node scripts/run-oxlint.mjs on touched files — exit 0

Evidence after fix — manual cron runId alignment (5th round, current head):

=== Reproduction for issue #92460 — manual cron runId reaches the resolver ===
State dir: /tmp/openclaw-92460-manual-cron-XXXXXX
Manual run id (caller, from enqueueRun): manual:manual-92460-job:1718726400000:1
Task ledger run id (internal):           cron:manual-92460-job:1718726400000
PASS  1. manual lease acquired under manual:manual-92460-job:1718726400000:1
PASS  2. runIsolatedAgentJob received runId=manual:manual-92460-job:1718726400000:1
PASS  3. resolver-side getActiveTaskRouteLease(runId) recovers the lease under manual: id
PASS  4. terminal settle transitions the manual: lease out of active
ALL PASS

Evidence after fix — channel-only cron + empty session entry (prior round):

=== Reproduction for issue #92460 — channel-only cron + empty session entry ===
State dir: /tmp/openclaw-92460-channel-only-XXXXXX
PASS  1. lease acquired at cron job start with channel-only origin (channel=webchat)
PASS  2. lease is unroutable on its own (no `to`) — needs resolver to fill in the target
PASS  3. resolver updated the lease with the resolved target (to=user:resolved-requester, threadId=thread-42)
PASS  4. lease is now routable (channel + to + threadId)
PASS  5. completion-time lookup recovers the resolved target from the lease
PASS  6. terminal settle transitions the lease out of active
PASS  7. lifecycle is repeatable across cron runs
ALL PASS

Observed result after fix: every cron entry path (manual + scheduled + startup catch-up) acquires a lease keyed by the run id the resolver will look up; the resolver consults the lease as a routability-aware fallback; the manual runId flows through executeJobCoreWithTimeoutrunIsolatedAgentJob → resolver; leases are settled on terminal status; expireStaleTaskRouteLeases runs hourly via start()'s leaseGcTimer.

What was not tested: no live Telegram / webchat channel send path is exercised in this PR (gated by the resolver's ok: true return, which is proved by the unit tests). The lease-only path is proved end-to-end against real on-disk SQLite with a runIsolatedAgentJob mock that records the runId arg.

Verification

  • pnpm tsgo:core — exit 0
  • node scripts/run-oxlint.mjs on touched files — exit 0
  • 51 unit tests passing across 4 test files (32 cron-related + 19 ops.test.ts regression)
  • 24 PASS steps across 4 repro scripts

Related

AI assistance

AI-assisted (Claude Code). All repro scripts and unit tests were run by the human on a clean checkout of the branch.

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime scripts Repository scripts size: XL triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels Jun 19, 2026
@clawsweeper

clawsweeper Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed June 20, 2026, 11:34 AM ET / 15:34 UTC.

Summary
The PR adds a SQLite-backed task-route lease table/API and wires cron acquire, settle, resolver fallback/update, manual runId propagation, GC, repro scripts, and regression tests to preserve isolated cron completion delivery origin.

PR surface: Source +800, Tests +1121, Generated +44, Other +706. Total +2671 across 22 files.

Reproducibility: yes. source-level. Current main tests intentionally permit colliding runIds across requester scopes, and the PR key/lookup uses only raw runId; I did not run the PR's repro scripts in this read-only review.

Review metrics: 1 noteworthy metric.

  • Persisted Route State: 1 SQLite table added. The PR persists requester delivery origins in shared state, so key, retention, cleanup, and upgrade semantics matter before merge.

Stored data model
Persistent data-model change detected: database schema: src/state/openclaw-state-schema.generated.ts, database schema: src/state/openclaw-state-schema.sql, serialized state: src/cron/service/state.ts, serialized state: src/state/openclaw-state-db.generated.d.ts, serialized state: src/state/openclaw-state-schema.generated.ts, serialized state: src/state/openclaw-state-schema.sql. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #92460
Summary: This PR is the current candidate fix for the isolated cron completion delivery issue, while the open foundation PR owns overlapping lease infrastructure needed by this branch.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🧂 unranked krab
Proof: 🦪 silver shellfish
Patch quality: 🧂 unranked krab
Result: blocked until stronger real behavior proof is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P1] Scope the lease key/lookup and add a cross-scope same-runId regression test.
  • [P1] Provide redacted real webchat or transport proof after the scoped-identity fix.
  • Rebase this PR after the foundation PR lands or keep this branch limited to cron wiring.

Proof guidance:

  • [P1] Needs stronger real behavior proof before merge: The PR supplies terminal output and real SQLite repros, but it does not prove a real webchat/transport completion after the scoped-route issue is fixed; after adding redacted proof, updating the PR body should trigger re-review. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Mantis proof suggestion
A browser-visible webchat proof would materially improve confidence that the isolated cron completion reaches the intended conversation after the route fix. A maintainer can ask Mantis to capture proof by posting this exact PR comment:

@openclaw-mantis visual task: verify an isolated cron with delivery.channel webchat and no delivery.to delivers its completion to the original webchat conversation after the session entry is evicted.

Risk before merge

  • [P1] A raw runId-only lease can overwrite or expose another runtime/requester scope's requester origin when two detached tasks share a runId, causing wrong-target or missing completion delivery.
  • [P1] The new persistent SQLite route state needs maintainer-approved key, retention, cleanup, and upgrade semantics before merge; the smaller foundation PR is still open.
  • [P1] The supplied proof does not yet show an actual webchat or transport completion delivery after the scoped-identity issue is fixed.

Maintainer options:

  1. Scope The Lease Identity (recommended)
    Change the lease key and lookup to include the same runtime/requester scope facts current task registry uses, then add a regression where colliding runIds cannot read or overwrite each other's route.
  2. Accept A New Global runId Contract
    Maintainers could explicitly decide that detached runId is now globally unique, but that would require updating current task-registry invariants, tests, and upgrade expectations before this PR lands.
  3. Pause Until Foundation Lands
    Because the PR is stacked, maintainers can land or revise feat(tasks): add generic task-route lease module for cross-crash delivery origin recovery #95352 first and keep this branch as cron-only wiring after the shared lease API is settled.

Next step before merge

  • [P1] Manual review is needed because the blocker is the stored route identity and stacked foundation API direction, not a safe one-shot autonomous repair.

Security
Needs attention: The raw runId route lease can cross requester scopes and expose or reuse another task's delivery origin, which is a concrete wrong-target delivery risk.

Review findings

  • [P1] Scope route leases beyond raw runId — src/tasks/task-route-lease.ts:140-146
  • [P3] Remove the dead task-completion docs reference — src/state/openclaw-state-schema.sql:1146
Review details

Best possible solution:

Land or approve a scoped route-lease identity in the foundation PR, rebase this PR onto that shape with cross-scope collision coverage, then provide redacted real webchat or transport proof for the isolated cron completion path.

Do we have a high-confidence way to reproduce the issue?

Yes, source-level. Current main tests intentionally permit colliding runIds across requester scopes, and the PR key/lookup uses only raw runId; I did not run the PR's repro scripts in this read-only review.

Is this the best way to solve the issue?

No. The cron fallback is a plausible direction, but the generic persistent lease should be scoped to the same task identity facts as the registry, or narrowed to an unambiguous cron-only identity, before merge.

Full review comments:

  • [P1] Scope route leases beyond raw runId — src/tasks/task-route-lease.ts:140-146
    Current main deliberately supports multiple task records sharing the same runId when runtime/requester scope differs. This PR stores and resolves task-route leases by run_id alone, so a second active task with the same raw runId can overwrite or expose another task's requester origin and send a completion to the wrong target. Include the scope facts used by the task registry, or make this a strictly cron-only identity, and add cross-scope collision coverage.
    Confidence: 0.91
  • [P3] Remove the dead task-completion docs reference — src/state/openclaw-state-schema.sql:1146
    The new schema comment points readers to docs/concepts/task-completion.md, but that page is not present on current main or this PR head. Either add the referenced doc in the stack or replace the pointer with the actual invariant near the table definition.
    Confidence: 0.86

Overall correctness: patch is incorrect
Overall confidence: 0.88

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against a590f7f69014.

Label changes

Label justifications:

  • P1: This PR targets a P1 completion-delivery loss bug, and the current patch can still misroute or suppress completions through the raw runId lease key.
  • merge-risk: 🚨 compatibility: The diff adds a persisted SQLite data model for route leases, making upgrade and retention semantics compatibility-sensitive.
  • merge-risk: 🚨 message-delivery: The raw runId lease lookup can recover the wrong delivery origin or no usable origin when detached tasks share a runId.
  • merge-risk: 🚨 session-state: The lease stores requester-origin session routing state outside the existing scoped task registry identity model.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🦪 silver shellfish and patch quality is 🧂 unranked krab.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The PR supplies terminal output and real SQLite repros, but it does not prove a real webchat/transport completion after the scoped-route issue is fixed; after adding redacted proof, updating the PR body should trigger re-review. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

PR surface:

Source +800, Tests +1121, Generated +44, Other +706. Total +2671 across 22 files.

View PR surface stats
Area Files Added Removed Net
Source 12 814 14 +800
Tests 4 1121 0 +1121
Docs 0 0 0 0
Config 0 0 0 0
Generated 2 44 0 +44
Other 4 706 0 +706
Total 22 2685 14 +2671

Security concerns:

  • [high] Unscoped lease key can expose requester routes — src/tasks/task-route-lease.ts:140
    The lease table stores requester_origin_json and resolves it by raw runId only; because current main allows runId collisions across scopes, one task can read or replace another task's route and potentially deliver sensitive completion text to the wrong conversation.
    Confidence: 0.87

What I checked:

Likely related people:

  • vincentkoc: Blame on the current task run-scope invariant, collision tests, and cron delivery/task-run files points to commit 8ee6382; the linked issue discussion also asks this handle for design-shape feedback. (role: recent area contributor and review-context owner; confidence: high; commits: 8ee638236a24, a590f7f69014; files: src/tasks/task-registry.ts, src/tasks/task-registry.test.ts, src/cron/isolated-agent/run.ts)
  • Takhoffman: Recent task-registry history includes task kind preservation work, making this a secondary routing candidate for registry storage semantics if the primary owner is unavailable. (role: adjacent task registry contributor; confidence: low; commits: 13337d7048c1; files: src/tasks/task-registry.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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 keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels Jun 19, 2026
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. labels Jun 19, 2026
@wangmiao0668000666

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Both P1 findings from the first cut are addressed in this revision:

  • P1 fix: add @lid format support and allowFrom wildcard handling #1 (routability, not presence)resolveDeliveryTarget now prefers the first higher-precedence source that resolves to a routable (channel, to) pair. An empty isolated-cron session entry no longer masks the lease. New helper isRoutableDeliveryEntry + routableCandidate lookup in src/cron/isolated-agent/delivery-target.ts.
  • P1 Login fails with 'WebSocket Error (socket hang up)' ECONNRESET #2 (capture the resolved target, not raw job.delivery)resolveCronDeliveryContext now updates the lease with the resolver's concrete (channel, to, threadId, accountId) after resolveDeliveryTarget returns ok: true. New public function updateTaskRouteLease in src/tasks/task-route-lease.ts + caller in src/cron/isolated-agent/run.ts.

New unit tests: 13 lease tests (was 9) + 10 cron delivery-target tests (was 5) = +9 new tests, all passing.

New repro: scripts/repro/issue-92460-channel-only-empty-session.mts exercises the exact reported path (cron with delivery.channel: "webchat" and no explicit to, empty session entry) and demonstrates the lease acquires with channel-only origin, the resolver updates it with the resolved target, the completion-time lookup recovers the full target, and the lifecycle repeats across cron runs. 7/7 PASS.

PR body updated with the new evidence in the same single-line Field name: value format.

@clawsweeper

clawsweeper Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jun 19, 2026
@wangmiao0668000666

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Two P2 findings from the merge-readiness review are fixed in c1b4939:

P2 (test isolation, both files)src/tasks/task-route-lease.test.ts and src/cron/isolated-agent/delivery-target.issue-92460.test.ts previously called resetTaskRouteLeasesForTests() (which hits the default shared state DB) before any temp OPENCLAW_STATE_DIR was installed. Both beforeEach hooks now open a fresh temp state DB via openOpenClawStateDatabase({ env: { OPENCLAW_STATE_DIR: createTempStateDir() } }), so lease helpers operate strictly on the test fixture. resetTaskRouteLeasesForTests() calls and the import have been dropped from both suites; closeOpenClawStateDatabaseForTest() is used in afterEach and the process.on("exit") guard.

P2 (existing mock missing new export)src/cron/isolated-agent/run.issue-91613-preflight.test.ts mocked ./run-delivery.runtime.js with only resolveDeliveryTarget, so resolveCronDeliveryContext (which now also reads updateResolvedTaskRouteLease from that runtime) errored at line 426 inside checks-node-core-runtime-cron-isolated-agent. The mock now also exposes updateResolvedTaskRouteLease: vi.fn(() => false) (vi.hoisted), matching the production export list.

Lint (scripts/repro) — 4 oxlint errors in the 3 repro scripts surfaced in check-lint: 3 catch (err) without : unknown, 1 setTimeout(resolve, ...) arrow-return inside new Promise executor. Fixed all 4. Full lint exit 0.

Verification on a clean checkout of the branch (HEAD = c1b4939):

  • node scripts/run-vitest.mjs src/tasks/task-route-lease.test.ts → 13 passed
  • node scripts/run-vitest.mjs src/cron/isolated-agent/delivery-target.issue-92460.test.ts → 10 passed
  • node scripts/run-vitest.mjs src/cron/isolated-agent/run.issue-91613-preflight.test.ts → 3 passed
  • node scripts/run-oxlint.mjs → exit 0 (check-lint shard source)
  • pnpm tsgo:core → exit 0
  • pnpm build → exit 0
  • pnpm exec tsx scripts/repro/issue-92460-channel-only-empty-session.mts → 7 PASS
  • pnpm exec tsx scripts/repro/issue-92460-cron-completion-leases.mts → 6 PASS
  • pnpm exec tsx scripts/repro/issue-92460-task-route-lease-lifecycle.mts → 7 PASS

check:changed delegates to Blacksmith Testbox (per AGENTS.md) so it was not run locally; the local gates above cover the same source files.

Branch: fix/92460-task-route-lease (4 commits ahead of openclaw/main), force-pushed to wangmiao0668000666/openclaw.

@clawsweeper

clawsweeper Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@wangmiao0668000666

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

All three P1 findings from the merge-readiness review on c1b4939578 are addressed in 7fea99fdb8:

P1 #1 — wire manual cron runs through the route lease lifecycle (src/cron/service/task-runs.ts:102-106 from prior review)
The reported #92460 run id is manual:... (from enqueueRun), not the internal cron: task ledger id. The first cut only acquired/settled leases for the scheduled path. This revision:

  • tryCreateManualTaskRun accepts optional runId (the manual: id from enqueueRun) and uses it as the lease key while keeping the task ledger row on createCronExecutionId so downstream run-log readers do not see manual:-prefixed rows in the cron registry.
  • tryFinishManualTaskRun accepts leaseRunId and calls settleTaskRouteLease(leaseRunId, "settled" | "retired") so the lease acquired under the manual: id gets retired.
  • prepareManualRun calls tryCreateManualTaskRun({ ..., runId: opts?.runId }).
  • resolveCronLeaseRequesterOrigin is exported from task-runs.ts so both scheduled and manual paths derive the requester origin identically.

Regression coverage: src/cron/service/ops.issue-92460-manual-lease.test.ts (5 new tests, all passing) + scripts/repro/issue-92460-manual-cron-lease.mts (6 PASS steps, real on-disk SQLite). The existing 19 src/cron/service/ops.test.ts tests (including the manual acknowledgement ID separation test) still pass — task ledger rows are unchanged on the cron: prefix, only the lease row moves to manual:.

P1 #3 — stale-lease GC has a production owner
expireStaleTaskRouteLeases is now wired to start() via a new dedicated leaseGcTimer field on CronServiceState (1h cadence, setTimeout(...).unref?.() so it never blocks process exit). stop() clears it. Importantly, this timer is armed before the cronEnabled check so subagent / codex / ACP leases are still GC'd when the cron scheduler is disabled — the lease module is generic, not cron-only.

Regression coverage: the 5th test in ops.issue-92460-manual-lease.test.ts ("start() arms the stale-lease GC timer") pins the wiring. It does not wait the full hour — it asserts state.leaseGcTimer !== null after start() and state.leaseGcTimer === null after stop(), proving the owner exists and is properly cleaned up. Existing GC unit tests in src/tasks/task-route-lease.test.ts still pass.

P1 #2 — patch quality carried over from c1b4939578

  • src/cron/isolated-agent/run.issue-91613-preflight.test.tsvi.mock("./run-delivery.runtime.js") now exposes updateResolvedTaskRouteLease so the prior CI fail (checks-node-core-runtime-cron-isolated-agent) no longer reproduces.
  • src/tasks/task-route-lease.test.ts and src/cron/isolated-agent/delivery-target.issue-92460.test.tsbeforeEach now opens an isolated temp OPENCLAW_STATE_DIR via openOpenClawStateDatabase({ env: { OPENCLAW_STATE_DIR: createTempStateDir() } }). resetTaskRouteLeasesForTests() calls and imports are dropped from both suites — a fresh DB has no rows, so the reset was unnecessary, and running it before the temp env was installed was the P2 finding (default shared state DB leak).

Verification on 7fea99fdb8:

  • pnpm tsgo:core → exit 0
  • pnpm build → exit 0
  • node scripts/run-oxlint.mjs (full repo, check-lint shard source) → exit 0
  • node scripts/run-vitest.mjs src/cron/service/ops.test.ts → 19 passed (no regression on existing manual/timeout tests; task ledger cron: ids preserved)
  • node scripts/run-vitest.mjs src/cron/service/ops.issue-92460-manual-lease.test.ts → 5 passed (new)
  • node scripts/run-vitest.mjs src/tasks/task-route-lease.test.ts → 13 passed
  • node scripts/run-vitest.mjs src/cron/isolated-agent/delivery-target.issue-92460.test.ts → 10 passed
  • node scripts/run-vitest.mjs src/cron/isolated-agent/run.issue-91613-preflight.test.ts → 3 passed
  • 4 repro scripts → 7 + 6 + 7 + 6 = 26 PASS

check:changed delegates to Blacksmith Testbox (per AGENTS.md); it was not run locally. Local gates above cover the same source paths.

Branch: fix/92460-task-route-lease (5 commits ahead of openclaw/main), force-pushed to wangmiao0668000666/openclaw.

@wangmiao0668000666

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

The P1 finding from the re-review on 7fea99fdb8 is fixed in 9d81b04815:

P1 #1 (post-review) — manual runId must reach runIsolatedAgentJob and the resolver

The previous cut (7fea99fdb8) acquired the manual lease under the manual: runId, but finishPreparedManualRun still passed runId: taskRunId (the cron: task ledger id) to executeJobCoreWithTimeout. The runId then flowed through to runIsolatedAgentJobresolveCronDeliveryContextresolveDeliveryTargetgetActiveTaskRouteLease(jobPayload.runId), where the resolver looked up the lease under the cron: id while the lease lived under the manual: id — so the resolver missed the lease for queued manual runs.

Fix: src/cron/service/ops.ts:1000 now passes runId: runId (i.e. prepared.runId, the manual: id when the caller provides one, else the same cron: id as before) instead of runId: taskRunId. The task ledger row is unchanged on the cron: id, so downstream run-log readers do not see manual:-prefixed rows in the cron registry.

Regression coverage (end-to-end, not mocked):

  • src/cron/service/ops.issue-92460-manual-lease.test.ts — new test "forwards the manual: runId through executeJobCoreWithTimeout to runIsolatedAgentJob" drives the real run(state, id, "force", { runId }) path with a mock that records runIsolatedAgentJob's runId arg, then asserts the captured id equals the manual: id and not the cron: task ledger id. 6 passed in the file (5 existing + 1 new).

  • scripts/repro/issue-92460-manual-cron-lease.mts — rewritten to drive the real executeJobCoreWithTimeout pipeline (executeJobCoreWithTimeoutexecuteJobCoreexecuteDetachedCronJobrunIsolatedAgentJob) against a real on-disk SQLite state DB, with a runIsolatedAgentJob mock that records the runId arg. It asserts:

    1. lease is acquired under the manual: id;
    2. runIsolatedAgentJob receives runId="manual:..." (the value the resolver will look up);
    3. resolver-side getActiveTaskRouteLease(manualRunId) recovers the lease, and looking up by the internal cron: id does NOT find a lease (namespace split);
    4. terminal completion settles the lease under the manual: id.

    4 PASS in the new repro. This is the property the prior repro missed — the previous one called acquireTaskRouteLease and getActiveTaskRouteLease directly, bypassing the real executeJobCoreWithTimeout plumbing where the id mismatch happens.

P1 (Codex Surface Drift) — false positive
The review's "1 Codex extension export added" metric is based on a comment string in the diff ("codex, and ACP leases also live in the shared state DB"). No extensions/codex/** files were modified by this PR. The 5 commits only touch src/cron/**, src/tasks/**, and scripts/repro/. See git diff 937fe7ff47..HEAD -- extensions/ (empty) and the file list in the PR body.

P2 (persistent route state, retention/upgrade) — maintainer decision
Nothing to change on the PR side; per the review, this is a maintainer acceptance item for the SQLite route-lease table semantics.

Verification on 9d81b04815:

  • pnpm tsgo:core → exit 0
  • pnpm build → exit 0
  • node scripts/run-oxlint.mjs (full repo) → exit 0
  • node scripts/run-vitest.mjs src/cron/service/ops.test.ts src/cron/service/ops.issue-92460-manual-lease.test.ts25 passed (19 + 6)
  • node scripts/run-vitest.mjs src/tasks/task-route-lease.test.ts → 13 passed
  • node scripts/run-vitest.mjs src/cron/isolated-agent/delivery-target.issue-92460.test.ts src/cron/isolated-agent/run.issue-91613-preflight.test.ts → 13 passed
  • pnpm exec tsx scripts/repro/issue-92460-manual-cron-lease.mts4 PASS (new repro, real executeJobCoreWithTimeout pipeline)
  • 4 repro scripts in total → 7 + 6 + 7 + 4 = 24 PASS

Branch: fix/92460-task-route-lease (6 commits ahead of openclaw/main), force-pushed to wangmiao0668000666/openclaw.

@clawsweeper

clawsweeper Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jun 20, 2026
@wangmiao0668000666

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Both findings from the 4th-round review on 9d81b04815 are addressed in 38733d1b71:

P1 (proof gap) — current-head after-fix proof

ClawSweeper said: "earlier proof predates the 9d81b04 manual run-id fix" and "the PR body is not usable proof text". The PR body now opens with a Current-head after-fix proof section that shows the latest manual-path repro output, re-run on the current 38733d1b71 head against a real on-disk SQLite state DB. The repro drives the real executeJobCoreWithTimeoutexecuteJobCoreexecuteDetachedCronJobrunIsolatedAgentJob pipeline (no lease-module short-circuit) and asserts:

  1. lease is acquired under the manual: id (the caller runId, the value the resolver will look up);
  2. runIsolatedAgentJob receives runId=manual:manual-92460-job:1718726400000:1 (matches the manual id, not the internal cron: task ledger id);
  3. resolver-side getActiveTaskRouteLease(runId) recovers the lease under the manual id, and the internal cron: id has no lease (namespace split);
  4. terminal completion settles the lease under the manual id.

4 PASS in the repro. The same proof is also the body of scripts/repro/issue-92460-manual-cron-lease.mts so any reviewer can re-run it on the current head.

P2 (test isolation defect) — withStateDirForStorePath reset/close under temp env

ClawSweeper said: "resetTaskRouteLeasesForTests runs before and after the temp OPENCLAW_STATE_DIR scope" and that the reset helper opens the shared state DB from the current env, so it could delete task_route_leases from the developer's default state DB instead of the fixture.

src/cron/service/ops.issue-92460-manual-lease.test.ts:75-100 now wraps resetTaskRouteLeasesForTests() and closeOpenClawStateDatabaseForTest() inside the withEnvAsync({ OPENCLAW_STATE_DIR: stateRoot }, ...) callback so both run against the temp fixture only. The outer reset and the finally reset were removed; both happen under the temp env. 6 passed in the file (5 existing + 1 new runId-alignment test, no regression).

Verification on 38733d1b71:

  • pnpm tsgo:core → exit 0
  • node scripts/run-vitest.mjs src/cron/service/ops.issue-92460-manual-lease.test.ts6 passed
  • node scripts/run-vitest.mjs src/cron/service/ops.test.ts → 19 passed (no regression on existing manual/timeout tests)
  • node scripts/run-vitest.mjs src/tasks/task-route-lease.test.ts → 13 passed
  • node scripts/run-vitest.mjs src/cron/isolated-agent/delivery-target.issue-92460.test.ts src/cron/isolated-agent/run.issue-91613-preflight.test.ts → 13 passed
  • pnpm exec tsx scripts/repro/issue-92460-manual-cron-lease.mts4 PASS (current head, real pipeline)
  • 4 repro scripts in total → 7 + 6 + 7 + 4 = 24 PASS

Branch: fix/92460-task-route-lease (7 commits ahead of openclaw/main), force-pushed to wangmiao0668000666/openclaw. PR body now opens with the current-head after-fix proof block.

@clawsweeper

clawsweeper Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Command router queued. I will update this comment with the next step.

@clawsweeper

clawsweeper Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@openclaw-barnacle openclaw-barnacle Bot removed the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jun 20, 2026
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jun 20, 2026
@wangmiao0668000666
wangmiao0668000666 force-pushed the fix/92460-task-route-lease branch from 38733d1 to 6a7164f Compare June 20, 2026 15:16
@wangmiao0668000666 wangmiao0668000666 changed the title fix(cron): wire task-route lease into isolated cron delivery-target (closes #92460) fix(cron): wire task-route lease into isolated cron delivery-target (closes #92460, stacked on #95352) Jun 20, 2026
@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jun 20, 2026
@wangmiao0668000666

Copy link
Copy Markdown
Contributor Author

Splitting this PR per ClawSweeper's triage: needs-pr-context finding and the persistent XL + 3× 🚨 merge-risk signal.

New layout (2 stacked PRs):

PR Branch Scope Files
#95352 feat/task-route-lease-module Generic task_route_leases SQLite module + tests + lifecycle repro (no cron coupling, reusable for subagent/ACP/codex) 7 / +966
#95012 (this) fix/92460-task-route-lease Cron-side acquire/settle/routability/manual runId/GC + 4 cron repro scripts (closes #92460) 16 / +1721

Recommended merge order: #95352 first → rebase #95012 onto #95352's merge commit → merge #95012.

Until #95352 lands, this PR's diff-vs-main still shows the full union (22 files / +2685) because GitHub can't take a fork branch as a PR base. After #95352 merges, this branch will be rebased and the diff-vs-main will shrink to the 16 cron files (+1721).

The current head 6a7164f617 on fix/92460-task-route-lease is identical to the prior head 38733d1b71 (the previous round's response to ClawSweeper's 4th-cut P1) — the work is preserved. The split is purely a branch-organization change; no functional changes have been made.

PR-A (#95352) is the smaller, more reviewable unit and unblocks rebase-onto-it for #95012. Both should now be reviewable independently; please prioritize #95352.

@openclaw-barnacle openclaw-barnacle Bot removed the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jun 20, 2026
@wangmiao0668000666

Copy link
Copy Markdown
Contributor Author

Closing in favor of a fresh PR using the same branch code, to drop the 5-round stale ClawSweeper review history (which has carried the same and downgraded ratings across iterations). The new PR is opened from the same branch with a clean body that explicitly documents the stacked relationship to #95352 (lease module infrastructure). The branch SHA is unchanged at — same 9 commits, same content.

Recommended merge order: #95352 first → rebase the new PR onto #95352's merge commit → merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P1 High-priority user-facing bug, regression, or broken workflow. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. scripts Repository scripts size: XL status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Isolated cron completion announcer drops explicit delivery.channel on final controller return

1 participant