diff --git a/CHANGELOG.md b/CHANGELOG.md index 076d9362c9e..dcee21d6d22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -154,6 +154,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Cron: make rejected `payload.model` errors show the configured `agents.defaults.models` allowlist instead of echoing the rejected model twice. Fixes #79058. - Agents/subagents: retry parent wake announces when the announce-summary model run fails with fallback cooldown exhaustion instead of dropping the wake on the first transient provider overload. Refs #78581. - Providers/network: honor IPv4 CIDR and octet-wildcard `NO_PROXY` entries such as `100.64.0.0/10` and `100.64.*` before enabling trusted env-proxy mode for model-provider requests. Fixes #79030. - Docs/Docker: document a local Compose override for Docker Desktop DNS failures in the shared-network `openclaw-cli` sidecar, keeping the default compose setup hardened while unblocking `openclaw plugins install` when users opt in. Fixes #79018. Thanks @Jason-Vaughan. diff --git a/src/cron/isolated-agent/model-selection.ts b/src/cron/isolated-agent/model-selection.ts index 9ae0b2d07df..cf4baef8621 100644 --- a/src/cron/isolated-agent/model-selection.ts +++ b/src/cron/isolated-agent/model-selection.ts @@ -42,10 +42,23 @@ export type ResolveCronModelSelectionResult = error: string; }; -function formatCronPayloadModelRejection(modelOverride: string, error: string): string { +function formatAllowedModelRefs(params: { cfg: OpenClawConfig }): string { + const configured = params.cfg.agents?.defaults?.models; + if (configured && typeof configured === "object" && Object.keys(configured).length > 0) { + return Object.keys(configured).sort().join(", "); + } + return "(none configured)"; +} + +function formatCronPayloadModelRejection(params: { + cfg: OpenClawConfig; + modelOverride: string; + error: string; +}): string { + const { modelOverride, error } = params; if (error.startsWith("model not allowed:")) { const modelRef = error.slice("model not allowed:".length).trim(); - return `cron payload.model '${modelOverride}' rejected by agents.defaults.models allowlist: ${modelRef}`; + return `cron payload.model '${modelOverride}' rejected by agents.defaults.models allowlist: ${modelRef} is not in [${formatAllowedModelRefs({ cfg: params.cfg })}]`; } return `cron payload.model '${modelOverride}' rejected: ${error}`; } @@ -122,7 +135,11 @@ export async function resolveCronModelSelection( if ("error" in resolvedOverride) { return { ok: false, - error: formatCronPayloadModelRejection(modelOverride, resolvedOverride.error), + error: formatCronPayloadModelRejection({ + cfg: params.cfgWithAgentDefaults, + modelOverride, + error: resolvedOverride.error, + }), }; } provider = resolvedOverride.ref.provider; diff --git a/src/cron/isolated-agent/run.skill-filter.test.ts b/src/cron/isolated-agent/run.skill-filter.test.ts index 0fdb2fa3a1d..10f515c8530 100644 --- a/src/cron/isolated-agent/run.skill-filter.test.ts +++ b/src/cron/isolated-agent/run.skill-filter.test.ts @@ -222,6 +222,7 @@ describe("runCronIsolatedAgentTurn — skill filter", () => { agents: { defaults: { model: { primary: "openai-codex/gpt-5.4", fallbacks: defaultFallbacks }, + models: { "openai-codex/gpt-5.4": {} }, }, }, }, @@ -237,7 +238,7 @@ describe("runCronIsolatedAgentTurn — skill filter", () => { expect(result.status).toBe("error"); expect(result.error).toBe( - "cron payload.model 'anthropic/claude-sonnet-4-6' rejected by agents.defaults.models allowlist: anthropic/claude-sonnet-4-6", + "cron payload.model 'anthropic/claude-sonnet-4-6' rejected by agents.defaults.models allowlist: anthropic/claude-sonnet-4-6 is not in [openai-codex/gpt-5.4]", ); expect(logWarnMock).not.toHaveBeenCalled(); expect(runWithModelFallbackMock).not.toHaveBeenCalled();