Skip to content

cron: mcp action=run defaults to force mode, bypassing all schedule guards #94270

Description

@davectr

Summary

The mcp__openclaw__cron tool's action=run path defaults to runMode: "force" when the caller omits the parameter. This silently bypasses every schedule guard — isJobEnabled, the cron expression's day-of-week/time check, and nextRunAtMs — for every agent or operator who doesn't explicitly pass runMode: "due". The run-store journal also provides no way to distinguish manual from scheduled fires in queryable fields.

Affected versions

Confirmed in 2026.6.1 and 2026.6.8. Not fixed by any changelog entry in the 2026.6.8 release.

Reproduction

  1. Register any cron job with a day-of-week constraint (e.g., 0 10 * * 0,6 — weekends only).
  2. On a weekday, call mcp__openclaw__cron with action: "run", jobId: "<id>" — omit runMode.
  3. Observe: the job fires immediately. The cron expression is not evaluated.

Root cause — source traced

MCP tool router (openclaw-tools-*.js, confirmed at line 2351 in 2026.6.8 bundle openclaw-tools-50pm1WIW.js):

mode: params.runMode === "due" || params.runMode === "force" ? params.runMode : "force"

The fall-through default is "force", so any caller who omits runMode gets force mode implicitly.

isJobDue (jobs-*.js, confirmed at line 1036 in jobs-BeLaoJgl.js):

if (opts.forced) return true;

When forced: true, isJobDue short-circuits immediately — isJobEnabled, hasScheduledNextRunAtMs, and the nowMs >= nextRunAtMs check are all skipped. The cron expression is never re-evaluated.

inspectManualRunPreflight (server-cron-*.js, line 2848 in server-cron-CNixZIMh.js) passes { forced: mode === "force" } directly to isJobDue, so the above short-circuit fires on every default call.

Incident evidence

Observed in production on 2026-06-03: a prior operator session issued four mcp__openclaw__cron action=run calls with explicit runMode: "force" while testing a cron family after a gateway restart. One of the four jobs (cfd01058) had schedule 0 10 * * 0,6 (weekend-only) and fired on a Wednesday. Each forced re-fire is a full billable agent turn; the four fires were externally indistinguishable from scheduled fires because the run-store journal carries no trigger: "manual" field (only a runId prefix string match distinguishes them).

Proposed fixes

1. Flip the runMode default (required)

In openclaw-tools-*.js, change:

// before
mode: params.runMode === "due" || params.runMode === "force" ? params.runMode : "force"

// after
mode: params.runMode === "due" || params.runMode === "force" ? params.runMode : "due"

Safe-by-default: callers who omit runMode now get schedule-respecting behavior. Callers who want to force can still pass runMode: "force" explicitly.

2. Granular isJobDue forced flag (enhancement)

The current forced boolean collapses two distinct intents: "ignore the schedule entirely" and "fire now but still respect day-of-week/enabled." Refactoring to { skipEnabled?: boolean, skipSchedule?: boolean } lets callers express the latter without bypassing guards they didn't intend to bypass.

3. trigger field in run-store records (observability)

Run-store journal records currently carry no field that distinguishes manual from scheduled fires in a queryable way. Adding trigger: "manual" | "scheduled" and triggeredBy: <caller-info> would make incident triage significantly cheaper — today the only signal is a runId prefix string match, which requires reading raw JSONL files.

4. Dedup window for rapid re-fires (optional)

A ≥60s dedup window on action=run for the same jobId would prevent rapid-repeat cascades (the 2026-06-03 incident had two fires 3 seconds apart: 23:18:05 and 23:18:08).

Priority assessment

Fix #1 is a one-line change with high blast-radius benefit — every deployment of OpenClaw is affected by the unsafe default today. Fix #3 is low-effort observability that makes any future cron incident faster to diagnose. Fixes #2 and #4 are enhancements that can ship independently.

Happy to submit a PR for Fix #1 and #3 if the approach looks good to maintainers.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:otherThis issue has meaningful maintainer-visible impact outside the owned taxonomy.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions