From 02896ea17a13a32eb650ced27052558262c1782f Mon Sep 17 00:00:00 2001 From: Strider Date: Wed, 11 Mar 2026 21:13:38 +0800 Subject: [PATCH] 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, }; }