fix(cron): validate delivery.channel for announce mode with multi-channel

This commit is contained in:
Cengiz 2026-03-15 22:34:19 +00:00
parent 392ddb56e2
commit b856f2729e
1 changed files with 45 additions and 0 deletions

View File

@ -1,3 +1,4 @@
import { loadConfig } from "../../config/config.js";
import { normalizeCronJobCreate, normalizeCronJobPatch } from "../../cron/normalize.js";
import {
readCronRunLogEntriesPage,
@ -6,6 +7,7 @@ import {
} from "../../cron/run-log.js";
import type { CronJobCreate, CronJobPatch } from "../../cron/types.js";
import { validateScheduleTimestamp } from "../../cron/validate-timestamp.js";
import { listConfiguredMessageChannels } from "../../infra/outbound/channel-selection.js";
import {
ErrorCodes,
errorShape,
@ -21,6 +23,33 @@ import {
} from "../protocol/index.js";
import type { GatewayRequestHandlers } from "./types.js";
/**
* Validates that announce-mode delivery has an explicit channel when multiple
* channels are configured. Without this, delivery silently fails at runtime
* because the "last" channel fallback cannot resolve an unambiguous target.
* See: https://github.com/openclaw/openclaw/issues/47524
*/
async function validateDeliveryChannelForAnnounce(
delivery: Record<string, unknown> | undefined,
): Promise<string | null> {
if (!delivery || typeof delivery !== "object") {
return null;
}
const mode = typeof delivery.mode === "string" ? delivery.mode.trim().toLowerCase() : undefined;
if (mode !== "announce") {
return null;
}
if (typeof delivery.channel === "string" && delivery.channel.trim()) {
return null;
}
const cfg = loadConfig();
const configured = await listConfiguredMessageChannels(cfg);
if (configured.length > 1) {
return `delivery.channel is required when mode is "announce" and multiple channels are configured (${configured.join(", ")}). Set --channel explicitly.`;
}
return null;
}
export const cronHandlers: GatewayRequestHandlers = {
wake: ({ params, respond, context }) => {
if (!validateWakeParams(params)) {
@ -118,6 +147,13 @@ export const cronHandlers: GatewayRequestHandlers = {
);
return;
}
const deliveryChannelError = await validateDeliveryChannelForAnnounce(
(jobCreate as { delivery?: Record<string, unknown> }).delivery,
);
if (deliveryChannelError) {
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, deliveryChannelError));
return;
}
const job = await context.cron.add(jobCreate);
context.logGateway.info("cron: job created", { jobId: job.id, schedule: jobCreate.schedule });
respond(true, job, undefined);
@ -165,6 +201,15 @@ export const cronHandlers: GatewayRequestHandlers = {
return;
}
}
if (patch.delivery) {
const deliveryChannelError = await validateDeliveryChannelForAnnounce(
patch.delivery as Record<string, unknown>,
);
if (deliveryChannelError) {
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, deliveryChannelError));
return;
}
}
const job = await context.cron.update(jobId, patch);
context.logGateway.info("cron: job updated", { jobId });
respond(true, job, undefined);