From 02896ea17a13a32eb650ced27052558262c1782f Mon Sep 17 00:00:00 2001 From: Strider Date: Wed, 11 Mar 2026 21:13:38 +0800 Subject: [PATCH 1/2] fix(cron): persist payload.fallbacks in cron.update API The `fallbacks` field was read at runtime by `runWithModelFallback()` but silently dropped when updating a cron job. Add it to both `mergeCronPayload()` (same-kind merge) and `buildPayloadFromPatch()` (kind-change rebuild), following the same pattern as the `lightContext` fix (#31425). Closes #36263 Co-Authored-By: Claude Opus 4.6 --- src/cron/service.jobs.test.ts | 47 +++++++++++++++++++++++++++++++++++ src/cron/service/jobs.ts | 4 +++ 2 files changed, 51 insertions(+) diff --git a/src/cron/service.jobs.test.ts b/src/cron/service.jobs.test.ts index c514f7528ba..61b5a787e58 100644 --- a/src/cron/service.jobs.test.ts +++ b/src/cron/service.jobs.test.ts @@ -214,6 +214,53 @@ describe("applyJobPatch", () => { } }); + it("persists agentTurn payload.fallbacks updates when editing existing jobs", () => { + const job = createIsolatedAgentTurnJob("job-fallbacks", { + mode: "announce", + channel: "telegram", + }); + job.payload = { + kind: "agentTurn", + message: "do it", + fallbacks: ["anthropic/claude-sonnet-4-6"], + }; + + applyJobPatch(job, { + payload: { + kind: "agentTurn", + message: "do it", + fallbacks: ["openai/gpt-4o", "anthropic/claude-haiku-4-5"], + }, + }); + + expect(job.payload.kind).toBe("agentTurn"); + if (job.payload.kind === "agentTurn") { + expect(job.payload.fallbacks).toEqual(["openai/gpt-4o", "anthropic/claude-haiku-4-5"]); + } + }); + + it("applies payload.fallbacks when replacing payload kind via patch", () => { + const job = createIsolatedAgentTurnJob("job-fallbacks-switch", { + mode: "announce", + channel: "telegram", + }); + job.payload = { kind: "systemEvent", text: "ping" }; + + applyJobPatch(job, { + payload: { + kind: "agentTurn", + message: "do it", + fallbacks: ["anthropic/claude-sonnet-4-6"], + }, + }); + + const payload = job.payload as CronJob["payload"]; + expect(payload.kind).toBe("agentTurn"); + if (payload.kind === "agentTurn") { + expect(payload.fallbacks).toEqual(["anthropic/claude-sonnet-4-6"]); + } + }); + it("rejects webhook delivery without a valid http(s) target URL", () => { const expectedError = "cron webhook delivery requires delivery.to to be a valid http(s) URL"; const cases = [ diff --git a/src/cron/service/jobs.ts b/src/cron/service/jobs.ts index 542ba81053d..41ba1eeadca 100644 --- a/src/cron/service/jobs.ts +++ b/src/cron/service/jobs.ts @@ -704,6 +704,9 @@ function mergeCronPayload(existing: CronPayload, patch: CronPayloadPatch): CronP if (typeof patch.bestEffortDeliver === "boolean") { next.bestEffortDeliver = patch.bestEffortDeliver; } + if (Array.isArray(patch.fallbacks)) { + next.fallbacks = patch.fallbacks; + } return next; } @@ -772,6 +775,7 @@ function buildPayloadFromPatch(patch: CronPayloadPatch): CronPayload { channel: patch.channel, to: patch.to, bestEffortDeliver: patch.bestEffortDeliver, + fallbacks: patch.fallbacks, }; } From aa31765544377db6849f077a68cfed072947e08c Mon Sep 17 00:00:00 2001 From: Strider Date: Fri, 20 Mar 2026 21:04:18 +0800 Subject: [PATCH 2/2] fix: include fallbacks in agentTurn hint detection for partial patches coercePayload() now recognizes fallbacks as an agentTurn-specific field, so partial payload patches like { fallbacks: [...] } infer kind without requiring callers to also send kind explicitly. Co-Authored-By: Claude Opus 4.6 --- src/cron/normalize.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cron/normalize.ts b/src/cron/normalize.ts index b1afdfaaa12..1446d99d935 100644 --- a/src/cron/normalize.ts +++ b/src/cron/normalize.ts @@ -101,7 +101,8 @@ function coercePayload(payload: UnknownRecord) { typeof next.model === "string" || typeof next.thinking === "string" || typeof next.timeoutSeconds === "number" || - typeof next.allowUnsafeExternalContent === "boolean"; + typeof next.allowUnsafeExternalContent === "boolean" || + Array.isArray(next.fallbacks); if (hasMessage) { next.kind = "agentTurn"; } else if (hasText) {