Skip to content

Give the cron Run Now safety timer a per-run token #2076

Closed

Description

@shanselman

Lane 5 of 5 for #2071. Filed by a Copilot agent.

Problem: in CronPage.xaml.cs (~238-252), the 90 s Task.Delay continuation reads the _runningJobIds HashSet off the UI thread. It also carries no run identity. So if a job finishes and the user runs it again, the first run's timer clears the newer run's "running" state.

Fix: record a token per run. When the timer fires, check it and clear the state on the UI thread, only if the token still matches.

Coordinate with: #2032, which also changes CronPage.

Done when:

  • a unit test on the extracted run tracker shows a stale timer does not clear a newer run
  • the required validation passes

Activity

  1. self-assigned this
    on Nov 8, 2026
  2. added
    clawsweeper:intake:bugPreliminary submission classification; full source review pending.
    clawsweeper:intake:needs-reproPreliminary submission classification; full source review pending.
    P2Normal priority bug or improvement with limited blast radius.
    clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.
    impact:ux-frictionUser-facing flow adds avoidable confusion or support burden without fully blocking progress.
    issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.
    and removed
    clawsweeper:intake:bugPreliminary submission classification; full source review pending.
    clawsweeper:intake:needs-reproPreliminary submission classification; full source review pending.
    on Nov 8, 2026
  3. clawsweeper commented on Nov 8, 2026

    @clawsweeper

    Codex review: this still needs some work.

    Summary
    Keep open: current main still permits an earlier run's safety timer to clear a newer run's state, and this collaborator-authored issue requires maintainer judgment before closure.

    Reproducibility: Source establishes the sequence: complete run A, start run B before A's 90-second timeout, then let A's callback clear B by the shared job ID; no builds, tests, or live reproduction were run.

    Next step
    Coordinate the protected audit lane with the pending page replacement, then establish a failing stale-timer regression and guard UI-thread cleanup with per-run identity.

    Review details

    Best possible solution:

    Keep run identity and timeout cleanup under UI-thread ownership so stale callbacks leave newer runs and their refresh loops untouched.

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

    Source establishes the sequence: complete run A, start run B before A's 90-second timeout, then let A's callback clear B by the shared job ID; no builds, tests, or live reproduction were run.

    Is this the best way to solve the issue?

    A per-run token checked inside the dispatched callback directly addresses the defect; moving only the HashSet lookup onto the UI thread would leave stale-run cleanup possible.

    AGENTS.md: found and applied where relevant.

    Remaining risk / open question:

    • The timing sequence is established from source but has no executed regression or visible UI proof.
    • The pending Automations replacement changes the same production owner, so repair placement needs coordination.

    Codex review notes: model internal, reasoning medium; reviewed against e70292487958.

    Label changes

    Label changes:

    • add P2: A source-proven race affects manual cron status and action gating without evidence of a blocked core workflow or failed job execution.
    • add impact:ux-friction: A newer run can lose its running indicator and refresh loop while job actions become available prematurely.
    • add issue-rating: 🦞 diamond lobster: Current issue advisory state selects this label.
    • add clawsweeper:source-repro: Current issue advisory state selects this label.

    Label justifications:

    • P2: A source-proven race affects manual cron status and action gating without evidence of a blocked core workflow or failed job execution.
    • impact:ux-friction: A newer run can lose its running indicator and refresh loop while job actions become available prematurely.
    Evidence reviewed

    What I checked:

    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; those execution lanes are retired.
    • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

    Reviewed November 8, 2026, 1:29 AM ET / 06:29 UTC.

  4. shanselman commented on Nov 11, 2026

    @shanselman
    CollaboratorAuthor

    Hi, I'm a Copilot triage agent doing an overnight sweep (not Scott). A heads-up for whoever owns this lane, no action needed from me.

    On current main the code this issue describes is gone. The 90 s Task.Delay continuation and the _runningJobIds set in CronPage.xaml.cs were replaced when #1893 (feat(automations): native Automations page with web parity) landed. Run ownership now lives in AutomationsStore.RunNowAsync (src/OpenClaw.Shared/Cron/AutomationsStore.cs):

    • each Run Now creates a RunInvocation, and the acknowledgement and failure callbacks only act if OwnsRun(jobId, invocation) still matches
    • the run watcher, including its safety timeout, only clears the running state if OwnsWatcher(jobId, cts) still matches, so an older timer should not clear a newer run
    • AutomationsStoreConcurrencyTests already covers related ordering: LateAcknowledgementCannotRetireOrReplaceANewerInvocation and SuccessiveRunsRequireTheAcceptedRunIdDespiteStaleHistoryAndListState

    What I did not do: run the app or reproduce the old race. This is from reading main only. I also could not find a test that exercises the safety-timeout path specifically (the OwnsWatcher guard), so that might be a small follow-up if the owner wants a direct regression for the original scenario.

    If a maintainer agrees this is covered, it looks ready to close. I'm leaving that decision to you, and I'm not closing anything. Related: #2082 (fix: keep older Run Now attempts from clearing newer runs) targets the same old code and no longer applies.

  5. bkudiess commented on Nov 13, 2026

    @bkudiess
    Collaborator

    Closing as fixed by #1893 (feat(automations): native Automations page with web parity). AutomationsStore gives each Run Now its own invocation, and the safety timeout checks that it still owns the job before clearing it, so an older attempt can no longer clear a newer run. The superseded fix in #2082 is closed; details are in #2082 (comment).

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

Metadata

Metadata

Assignees

Labels

P2Normal priority bug or improvement with limited blast radius.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:ux-frictionUser-facing flow adds avoidable confusion or support burden without fully blocking progress.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.leverLever delivery tracking issue

Type

No type

Fields

Priority

None yet

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions