fix(cron): prevent agent default model from overriding cron payload model#58294
fix(cron): prevent agent default model from overriding cron payload model#58294steipete merged 3 commits intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR fixes a bug where cron jobs with an explicit The fix in Key changes:
Confidence Score: 5/5This PR is safe to merge; the fix is minimal, well-scoped, and the behavior change is intentional and correctly documented. The fix is a single ternary expression change in run.ts with clear semantics: ensure fallbacksOverride is always a defined array when a payload model override is present. The root cause analysis is correct (confirmed by reading resolveFallbackCandidates in model-fallback.ts), and the unchanged path for jobs without a model override is explicitly preserved. The new test file covers all documented scenarios. The only remaining finding is a P2 style suggestion to tighten one assertion from toBeDefined() to toEqual([]), which does not affect merge safety. No files require special attention; the change is narrowly confined to run.ts lines 486-504.
|
| Filename | Overview |
|---|---|
| src/cron/isolated-agent/run.ts | Adds hasCronPayloadModelOverride guard to ensure fallbacksOverride is always a defined array when a payload model override is present, preventing silent fallback to the agent primary model. |
| src/cron/isolated-agent/run.cron-model-override-forwarding.test.ts | New test file with 5 scenarios locking in the fallback override forwarding behavior for cron payload model overrides. |
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/cron/isolated-agent/run.cron-model-override-forwarding.test.ts
Line: 168-172
Comment:
**Test assertion could be more precise**
The assertions here confirm that `fallbacksOverride` is defined and is an array, but since both `payloadFallbacks` and `resolveAgentModelFallbacksOverride` are mocked to return `undefined` in this test, the resulting array will be exactly `[]`. Asserting `toEqual([])` would lock in the exact value and catch any future regression where the array unexpectedly gains entries.
```suggestion
expect(capturedFallbacksOverride).toEqual([]);
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(cron): prevent agent default model f..." | Re-trigger Greptile
| // (not undefined), which prevents the agent primary from being appended | ||
| // as a silent fallback. | ||
| expect(capturedFallbacksOverride).toBeDefined(); | ||
| expect(Array.isArray(capturedFallbacksOverride)).toBe(true); | ||
| }); |
There was a problem hiding this comment.
Test assertion could be more precise
The assertions here confirm that fallbacksOverride is defined and is an array, but since both payloadFallbacks and resolveAgentModelFallbacksOverride are mocked to return undefined in this test, the resulting array will be exactly []. Asserting toEqual([]) would lock in the exact value and catch any future regression where the array unexpectedly gains entries.
| // (not undefined), which prevents the agent primary from being appended | |
| // as a silent fallback. | |
| expect(capturedFallbacksOverride).toBeDefined(); | |
| expect(Array.isArray(capturedFallbacksOverride)).toBe(true); | |
| }); | |
| expect(capturedFallbacksOverride).toEqual([]); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cron/isolated-agent/run.cron-model-override-forwarding.test.ts
Line: 168-172
Comment:
**Test assertion could be more precise**
The assertions here confirm that `fallbacksOverride` is defined and is an array, but since both `payloadFallbacks` and `resolveAgentModelFallbacksOverride` are mocked to return `undefined` in this test, the resulting array will be exactly `[]`. Asserting `toEqual([])` would lock in the exact value and catch any future regression where the array unexpectedly gains entries.
```suggestion
expect(capturedFallbacksOverride).toEqual([]);
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 670947d98c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const cronFallbacksOverride = hasCronPayloadModelOverride | ||
| ? (payloadFallbacks ?? resolveAgentModelFallbacksOverride(params.cfg, agentId) ?? []) | ||
| : (payloadFallbacks ?? resolveAgentModelFallbacksOverride(params.cfg, agentId)); |
There was a problem hiding this comment.
Preserve default fallback chain for payload model overrides
When payload.model is set and payload.fallbacks is absent, this now computes fallbacksOverride from resolveAgentModelFallbacksOverride(...) ?? [], which ignores agents.defaults.model.fallbacks. In configs that rely on global default fallbacks (but have no per-agent override), runWithModelFallback now receives a defined empty override and skips all configured default fallbacks, so cron jobs fail on the first transient model error instead of trying the configured fallback chain. This is a behavior regression introduced by the new ?? [] path.
Useful? React with 👍 / 👎.
steipete
left a comment
There was a problem hiding this comment.
Root cause checks out.
I traced this against src/cron/isolated-agent/run.ts and src/agents/model-fallback.ts: when a cron payload carries an explicit model override but neither payload.fallbacks nor agent-level fallbacks are configured, we currently pass fallbacksOverride: undefined into runWithModelFallback. That re-enables appending the agent primary as a last-resort candidate, which is exactly how the requested model gets silently replaced by the agent default.
This patch keeps the no-override path intact and adds the right focused regression coverage. Mergeable as the canonical fix for the silent-fallback variant.
steipete
left a comment
There was a problem hiding this comment.
One regression risk here.
When a cron payload carries an explicit model override, this patch forces fallbacksOverride to [] whenever there is no per-agent override. That does stop the unwanted implicit primary append, but it also disables the normal agents.defaults.model.fallbacks chain entirely for cron model overrides.
That differs from the shared override semantics used elsewhere. In src/agents/agent-scope.ts:255, explicit/session model overrides still inherit the global default fallback chain when there is no per-agent override. With this patch, the equivalent cron path would fail after the requested model instead of trying the configured defaults.
Please preserve the existing default fallback chain for explicit cron model overrides and only prevent the extra implicit primary append. The new tests should also cover the case where agents.defaults.model.fallbacks is configured and no per-agent override exists.
…odel (openclaw#58065) When a cron job specifies a model override via the Advanced settings, runWithModelFallback could silently fall back to the agent's configured primary model. This happened because fallbacksOverride was undefined when neither payload.fallbacks nor per-agent fallbacks were configured, causing resolveFallbackCandidates to append the agent primary as a last-resort candidate. A transient failure on the cron-selected model (rate limit, model-not-found, etc.) would then succeed on the agent default, making it appear as if the override was ignored entirely. Fix: when the cron payload carries an explicit model override, ensure fallbacksOverride is always a defined array (empty when no fallbacks are configured) so the agent primary is never silently appended. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace toBeDefined() + toBeInstanceOf(Array) with toEqual([]) to catch regressions where the array unexpectedly gains entries. Addresses review feedback. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6d658e0 to
655e0f3
Compare
steipete
left a comment
There was a problem hiding this comment.
Fixed the overcorrection.
- cron now uses the shared
resolveEffectiveModelFallbacks(...)path, so explicitpayload.modeloverrides still inherit configured default fallbacks - the hidden agent-primary append is still blocked for override runs
- added regression coverage for both the empty-chain case and inherited default fallbacks
Scoped verification:
pnpm test src/cron/isolated-agent/run.cron-model-override-forwarding.test.tspnpm test src/cron/isolated-agent/run.payload-fallbacks.test.tspnpm test src/agents/agent-scope.test.ts
Local pnpm check is currently blocked by unrelated tsgo failures in untouched OpenRouter files on latest main.
|
Landed. Thanks @aaronagent. Source head: Merge commit: Notes:
|
…odel (openclaw#58294) * fix(cron): prevent agent default model from overriding cron payload model (openclaw#58065) When a cron job specifies a model override via the Advanced settings, runWithModelFallback could silently fall back to the agent's configured primary model. This happened because fallbacksOverride was undefined when neither payload.fallbacks nor per-agent fallbacks were configured, causing resolveFallbackCandidates to append the agent primary as a last-resort candidate. A transient failure on the cron-selected model (rate limit, model-not-found, etc.) would then succeed on the agent default, making it appear as if the override was ignored entirely. Fix: when the cron payload carries an explicit model override, ensure fallbacksOverride is always a defined array (empty when no fallbacks are configured) so the agent primary is never silently appended. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: use stricter toEqual([]) assertion for fallbacksOverride Replace toBeDefined() + toBeInstanceOf(Array) with toEqual([]) to catch regressions where the array unexpectedly gains entries. Addresses review feedback. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: preserve cron override fallback semantics (openclaw#58294) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
…odel (openclaw#58294) * fix(cron): prevent agent default model from overriding cron payload model (openclaw#58065) When a cron job specifies a model override via the Advanced settings, runWithModelFallback could silently fall back to the agent's configured primary model. This happened because fallbacksOverride was undefined when neither payload.fallbacks nor per-agent fallbacks were configured, causing resolveFallbackCandidates to append the agent primary as a last-resort candidate. A transient failure on the cron-selected model (rate limit, model-not-found, etc.) would then succeed on the agent default, making it appear as if the override was ignored entirely. Fix: when the cron payload carries an explicit model override, ensure fallbacksOverride is always a defined array (empty when no fallbacks are configured) so the agent primary is never silently appended. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: use stricter toEqual([]) assertion for fallbacksOverride Replace toBeDefined() + toBeInstanceOf(Array) with toEqual([]) to catch regressions where the array unexpectedly gains entries. Addresses review feedback. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: preserve cron override fallback semantics (openclaw#58294) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
Summary
modeloverride,fallbacksOverrideis now always a defined array (empty[]when no fallbacks are configured), which preventsrunWithModelFallbackfrom silently appending the agent's configured primary model as a last-resort fallback candidate.resolveCronModelSelectionis unchanged. Payload-levelfallbacksarrays are still respected when present.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Root Cause / Regression History (if applicable)
src/cron/isolated-agent/run.ts, thefallbacksOverrideparameter passed torunWithModelFallbackwas computed aspayloadFallbacks ?? resolveAgentModelFallbacksOverride(params.cfg, agentId). When neither the cron payloadfallbacksarray nor the per-agent model fallbacks were configured (bothundefined),fallbacksOverrideresolved toundefined. InsiderunWithModelFallback→resolveFallbackCandidates(model-fallback.ts:399), whenfallbacksOverride === undefined, the agent's configured primary model is appended as the last fallback candidate. If the cron-selected model encounters any transient failure (rate limit, model-not-found in registry, overloaded, etc.), the fallback chain would reach and succeed on the agent's primary model — making it appear as though the cron model override was completely ignored.fallbacksOverridewas a defined array (rather thanundefined) when a cron payload model override was present.LiveSessionModelSwitchErrorhandling in cron isolated sessions but did not address this separate path where the agent default silently wins through the fallback candidate list.runWithModelFallback, but it became more visible with the recent model provider expansions (multi-provider setups with qwen/gemini/codex) where transient failures are more common across provider boundaries.resolveCronModelSelectionis correct — the payload model IS resolved and passed torunWithModelFallbackasprovider/model. The issue is exclusively in the fallback candidate list construction.Regression Test Plan (if applicable)
src/cron/isolated-agent/run.cron-model-override-forwarding.test.tsmodelset,runWithModelFallbackmust receive a definedfallbacksOverridearray (notundefined), preventing the agent primary from being silently appended as a fallback candidate.runWithModelFallback, which is the exact boundary where the bug manifests.run.cron-model-override.test.tscovers pre-run session persistence but did not verify the fallback candidate behavior.User-visible / Behavior Changes
Diagram (if applicable)
Security Impact (required)
Repro + Verification
Environment
Steps
anthropic/claude-opus-4-6)google/gemini-2.0-flash)Expected
google/gemini-2.0-flashActual
anthropic/claude-opus-4-6(the agent default)Evidence
src/cron/isolated-agent/run.cron-model-override-forwarding.test.ts(5 tests)Human Verification (required)
fallbacksOverrideis a defined arrayfallbacksOverrideisundefined(existing behavior preserved)fallbacksarray is usedrunEmbeddedPiAgentvia passthroughReview Conversations
Compatibility / Migration
Risks and Mitigations
🤖 Generated with Claude Code