From 0888debcd3eb39cd7a7eb094aee7729bfbfaf987 Mon Sep 17 00:00:00 2001
From: ly-wang19 <94427531+ly-wang19@users.noreply.github.com>
Date: Mon, 15 Jun 2026 23:54:00 +0800
Subject: [PATCH] fix(cron): add cron edit --clear-model
Completes the CLI half of #91298.
---
docs/automation/cron-jobs.md | 4 +++
docs/cli/cron.md | 2 +-
src/cli/cron-cli/register.cron-edit.test.ts | 28 +++++++++++++++++++++
src/cli/cron-cli/register.cron-edit.ts | 15 ++++++++++-
4 files changed, 47 insertions(+), 2 deletions(-)
diff --git a/docs/automation/cron-jobs.md b/docs/automation/cron-jobs.md
index 9497d488715..709e44842cc 100644
--- a/docs/automation/cron-jobs.md
+++ b/docs/automation/cron-jobs.md
@@ -157,6 +157,9 @@ If stdout is non-empty, that text is the delivered result. If stdout is empty an
Model override; uses the selected allowed model for the job.
+
+ On `cron edit`, removes the per-job model override so the job follows normal cron model-selection precedence (a stored cron-session override if set, otherwise the agent/default model). Cannot be combined with `--model`.
+
Thinking level override.
@@ -471,6 +474,7 @@ Model override note:
- If the model is allowed, that exact provider/model reaches the isolated agent run.
- If it is not allowed or cannot be resolved, cron fails the run with an explicit validation error.
- API `cron.update` payload patches can set `model: null` to clear a stored job model override.
+- `openclaw cron edit --clear-model` clears that override from the CLI (same effect as the `model: null` patch) and cannot be combined with `--model`.
- Configured fallback chains still apply because cron `--model` is a job primary, not a session `/model` override.
- Payload `fallbacks` replaces configured fallbacks for that job; `fallbacks: []` disables fallback and makes the run strict.
- A plain `--model` with no explicit or configured fallback list does not fall through to the agent primary as a silent extra retry target.
diff --git a/docs/cli/cron.md b/docs/cli/cron.md
index d7a64f14989..0305f2bdb1c 100644
--- a/docs/cli/cron.md
+++ b/docs/cli/cron.md
@@ -168,7 +168,7 @@ Use `--due` when you want the manual command to run only if the job is currently
## Models
-`cron add|edit --model [` selects an allowed model for the job.
+`cron add|edit --model ][` selects an allowed model for the job. `cron edit --clear-model` removes the per-job model override so the job follows normal cron model-selection precedence (a stored cron-session override if present, otherwise the agent/default model); it cannot be combined with `--model`.
If the model is not allowed or cannot be resolved, cron fails the run with an explicit validation error instead of falling back to the job's agent or default model selection.
diff --git a/src/cli/cron-cli/register.cron-edit.test.ts b/src/cli/cron-cli/register.cron-edit.test.ts
index d93cbf8906e..8e54408cda7 100644
--- a/src/cli/cron-cli/register.cron-edit.test.ts
+++ b/src/cli/cron-cli/register.cron-edit.test.ts
@@ -200,4 +200,32 @@ describe("cron edit command", () => {
},
);
});
+
+ it("clears the model override with --clear-model (CLI parity with cron.update model:null)", async () => {
+ const program = createCronProgram();
+
+ await program.parseAsync(["edit", "job-1", "--clear-model"], { from: "user" });
+
+ expect(callGatewayFromCli).toHaveBeenCalledWith(
+ "cron.update",
+ expect.objectContaining({ clearModel: true }),
+ {
+ id: "job-1",
+ patch: {
+ payload: {
+ kind: "agentTurn",
+ model: null,
+ },
+ },
+ },
+ );
+ });
+
+ it("documents the --clear-model flag alongside the sibling --clear-tools", () => {
+ const editCommand = createCronProgram().commands.find((command) => command.name() === "edit");
+ const help = editCommand?.helpInformation() ?? "";
+
+ expect(help).toContain("--clear-model");
+ expect(help).toContain("--clear-tools");
+ });
});
diff --git a/src/cli/cron-cli/register.cron-edit.ts b/src/cli/cron-cli/register.cron-edit.ts
index 9c45da944ac..71cee34d579 100644
--- a/src/cli/cron-cli/register.cron-edit.ts
+++ b/src/cli/cron-cli/register.cron-edit.ts
@@ -115,6 +115,11 @@ export function registerCronEditCommand(cron: Command) {
"Thinking level for agent jobs (off|minimal|low|medium|high|xhigh)",
)
.option("--model ", "Model override for agent jobs")
+ .option(
+ "--clear-model",
+ "Remove the per-job model override (restore normal cron model precedence)",
+ false,
+ )
.option("--timeout-seconds ", "Timeout seconds for agent or command jobs")
.option("--no-output-timeout-seconds ", "No-output timeout seconds for command jobs")
.option("--output-max-bytes ", "Maximum captured stdout/stderr bytes for command jobs")
@@ -279,6 +284,9 @@ export function registerCronEditCommand(cron: Command) {
);
}
const model = normalizeOptionalString(opts.model);
+ if (model && opts.clearModel) {
+ throw new Error("Use --model or --clear-model, not both");
+ }
const thinking = normalizeOptionalString(opts.thinking);
const toolsAllow = parseCronToolsAllow(opts.tools);
const rawTimeoutSeconds =
@@ -346,6 +354,7 @@ export function registerCronEditCommand(cron: Command) {
const hasAgentTurnPayloadField =
typeof opts.message === "string" ||
Boolean(model) ||
+ Boolean(opts.clearModel) ||
Boolean(thinking) ||
(hasTimeoutSeconds &&
!hasCommandSpecificPayloadField &&
@@ -373,7 +382,11 @@ export function registerCronEditCommand(cron: Command) {
} else if (hasAgentTurnPatch) {
const payload: Record = { kind: "agentTurn" };
assignIf(payload, "message", String(opts.message), typeof opts.message === "string");
- assignIf(payload, "model", model, Boolean(model));
+ if (opts.clearModel) {
+ payload.model = null;
+ } else {
+ assignIf(payload, "model", model, Boolean(model));
+ }
assignIf(payload, "thinking", thinking, Boolean(thinking));
assignIf(payload, "timeoutSeconds", timeoutSeconds, hasTimeoutSeconds);
assignIf(
]