From dc0c54c7f1a7121ff23b6ccc0fac8530e6c1021d Mon Sep 17 00:00:00 2001 From: ethanclaw Date: Thu, 30 Apr 2026 15:45:41 +0800 Subject: [PATCH] fix(cron): warn when --agent is not specified on cron add (#42245) * fix(cron): warn when --agent is not specified on cron add Warn users when creating a cron job without specifying the --agent flag, so they know the job will run with the default agent (main). Fixes #42196 * fix(cron): warn when cron add omits --agent * fix(cron): name default agent in warning --------- Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com> Co-authored-by: Vincent Koc --- CHANGELOG.md | 1 + src/cli/cron-cli.test.ts | 106 +++++++++++++++++++++++++- src/cli/cron-cli/register.cron-add.ts | 10 +++ 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index deff027be42..f2e9ec02765 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ Docs: https://docs.openclaw.ai - Media: treat legacy Word/OLE attachments with `application/msword` or `application/x-cfb` MIME as binary so printable-looking `.doc` files are not embedded into prompts as text. Fixes #54176; carries forward #54380. Thanks @andyliu. - Config: accept documented `browser.tabCleanup` keys in strict root config validation, so configured tab cleanup no longer fails before runtime reads it. Fixes #74577. Thanks @lonexreb and @ezdlp. - Cron: validate disabled job schedule edits before persisting updates, so invalid cron changes no longer partially mutate stored jobs. Fixes #74459. Thanks @yfge. +- CLI/cron: warn when `openclaw cron add --message` omits a nonblank `--agent`, including blank agent values and session-key jobs, so scheduled agent-turn jobs make default-agent fallback explicit while system events stay quiet. Fixes #42196; carries forward #42245. Thanks @ethanclaw. - Channels/status: keep Telegram, Slack, and Google Chat read-only allowlist/default-target accessors on config-only paths, so status and channel summaries do not resolve SecretRef-backed runtime credentials. Thanks @eusine. - Active Memory: clarify the deprecated `modelFallbackPolicy` warning and config help so `modelFallback` is described as a chain-resolution last resort, not runtime failover. (#74602) Thanks @jeffrey701. - Channels/Discord: keep read-only allowlist/default-target accessors from resolving SecretRef-backed bot tokens, so status and channel summaries no longer fail when tokens are only available in gateway runtime. (#74737) Thanks @eusine. diff --git a/src/cli/cron-cli.test.ts b/src/cli/cron-cli.test.ts index b3834bc3eaa..a9c5bc3c2b2 100644 --- a/src/cli/cron-cli.test.ts +++ b/src/cli/cron-cli.test.ts @@ -12,7 +12,7 @@ const mocks = vi.hoisted(() => { defaultRuntime.log(value.endsWith("\n") ? value.slice(0, -1) : value); }), writeJson: vi.fn((value: unknown, space = 2) => { - defaultRuntime.log(JSON.stringify(value, null, space > 0 ? space : undefined)); + defaultRuntime.writeStdout(JSON.stringify(value, null, space > 0 ? space : undefined)); }), exit: vi.fn((code: number) => { throw new Error(`__exit__:${code}`); @@ -548,6 +548,110 @@ describe("cron cli", () => { const addCall = callGatewayFromCli.mock.calls.find((call) => call[0] === "cron.add"); const params = addCall?.[2] as { agentId?: string }; expect(params?.agentId).toBe("ops"); + expect(defaultRuntime.error).not.toHaveBeenCalledWith( + expect.stringContaining("No --agent specified"), + ); + }); + + it("warns when --agent is not specified on cron add with --message", async () => { + await runCronCommand([ + "cron", + "add", + "--name", + "No agent", + "--cron", + "* * * * *", + "--message", + "hello", + ]); + + expect(defaultRuntime.error).toHaveBeenCalledWith( + expect.stringContaining("No --agent specified"), + ); + expect(defaultRuntime.error).toHaveBeenCalledWith( + expect.stringContaining("default agent (main)"), + ); + }); + + it("keeps the missing --agent warning off cron add JSON stdout", async () => { + await runCronCommand([ + "cron", + "add", + "--name", + "No agent JSON", + "--cron", + "* * * * *", + "--message", + "hello", + "--json", + ]); + + expect(defaultRuntime.error).toHaveBeenCalledWith( + expect.stringContaining("No --agent specified"), + ); + const stdout = defaultRuntime.writeStdout.mock.calls.map(([value]) => value).join("\n"); + expect(stdout).not.toContain("No --agent specified"); + expect(JSON.parse(stdout)).toMatchObject({ + ok: true, + params: { + name: "No agent JSON", + payload: { kind: "agentTurn", message: "hello" }, + }, + }); + }); + + it("warns when --agent is blank on cron add with --message", async () => { + const params = await runCronAddAndGetParams([ + "--name", + "Blank agent", + "--cron", + "* * * * *", + "--message", + "hello", + "--agent", + " ", + ]); + + expect(params?.agentId).toBeUndefined(); + expect(defaultRuntime.error).toHaveBeenCalledWith( + expect.stringContaining("No --agent specified"), + ); + }); + + it("does not warn when --system-event is used (no agent needed)", async () => { + await runCronCommand([ + "cron", + "add", + "--name", + "System event", + "--cron", + "* * * * *", + "--system-event", + "tick", + ]); + + expect(defaultRuntime.error).not.toHaveBeenCalledWith( + expect.stringContaining("No --agent specified"), + ); + }); + + it("warns even when --session-key is provided (user should still specify agent explicitly)", async () => { + await runCronCommand([ + "cron", + "add", + "--name", + "With session key", + "--cron", + "* * * * *", + "--message", + "hello", + "--session-key", + "agent:my-agent:my-session", + ]); + + expect(defaultRuntime.error).toHaveBeenCalledWith( + expect.stringContaining("No --agent specified"), + ); }); it("sets lightContext on cron add when --light-context is passed", async () => { diff --git a/src/cli/cron-cli/register.cron-add.ts b/src/cli/cron-cli/register.cron-add.ts index 0b6e7930d3f..dcdc3fc2a39 100644 --- a/src/cli/cron-cli/register.cron-add.ts +++ b/src/cli/cron-cli/register.cron-add.ts @@ -6,6 +6,7 @@ import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, } from "../../shared/string-coerce.js"; +import { theme } from "../../terminal/theme.js"; import type { GatewayRpcOpts } from "../gateway-rpc.js"; import { addGatewayClientOptions, callGatewayFromCli } from "../gateway-rpc.js"; import { parsePositiveIntOrUndefined } from "../program/helpers.js"; @@ -231,6 +232,15 @@ export function registerCronAddCommand(cron: Command) { const sessionKey = normalizeOptionalString(opts.sessionKey); + if (payload.kind === "agentTurn" && !agentId) { + defaultRuntime.error( + theme.warn( + "No --agent specified; the job will run with the default agent (main). " + + "Specify --agent to choose a specific agent.", + ), + ); + } + const params = { name, description,