diff --git a/src/infra/heartbeat-events-filter.ts b/src/infra/heartbeat-events-filter.ts index 611884f9dca..7e5114d1c41 100644 --- a/src/infra/heartbeat-events-filter.ts +++ b/src/infra/heartbeat-events-filter.ts @@ -86,12 +86,11 @@ function isHeartbeatNoiseEvent(evt: string): boolean { export function isExecCompletionEvent(evt: string): boolean { const lower = evt.toLowerCase(); // "exec finished" — emitExecSystemEvent (gateway/node approval path) - // "exec completed/failed/killed" — maybeNotifyOnExit (backgrounded allowlisted commands) + // "Exec completed/failed/killed (" — maybeNotifyOnExit (backgrounded allowlisted commands) + // Anchored to the parenthesised format to avoid false positives in free-form cron text return ( lower.includes("exec finished") || - lower.includes("exec completed") || - lower.includes("exec failed") || - lower.includes("exec killed") + /exec (?:completed|failed|killed) \(/.test(lower) ); } diff --git a/src/infra/outbound/targets.ts b/src/infra/outbound/targets.ts index 6863430153b..d7fed91a023 100644 --- a/src/infra/outbound/targets.ts +++ b/src/infra/outbound/targets.ts @@ -270,18 +270,17 @@ export function resolveHeartbeatDeliveryTarget(params: { } } - if (target === "none" && !forceLastTargetWhenNone) { - const base = resolveSessionDeliveryTarget({ entry }); - return buildNoHeartbeatDeliveryTarget({ - reason: "target-none", - lastChannel: base.lastChannel, - lastAccountId: base.lastAccountId, - }); - } - - // For async exec completion events, always fall back to the session's last - // delivery target even when heartbeat target is explicitly set to "none". - if (target === "none" && forceLastTargetWhenNone) { + if (target === "none") { + if (!forceLastTargetWhenNone) { + const base = resolveSessionDeliveryTarget({ entry }); + return buildNoHeartbeatDeliveryTarget({ + reason: "target-none", + lastChannel: base.lastChannel, + lastAccountId: base.lastAccountId, + }); + } + // For async exec completion events, always fall back to the session's last + // delivery target even when heartbeat target is explicitly set to "none". target = "last"; }