fix(cron): cancel timed-out runs before side effects (openclaw#22411) thanks @Takhoffman

Verified:
- pnpm check
- pnpm vitest run src/memory/qmd-manager.test.ts src/cron/service.issue-regressions.test.ts src/cron/isolated-agent.delivers-response-has-heartbeat-ok-but-includes.test.ts --maxWorkers=1

Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
Tak Hoffman
2026-02-22 15:45:27 -06:00
committed by GitHub
parent 64b273a71c
commit 556af3f08b
5 changed files with 195 additions and 8 deletions

View File

@@ -156,10 +156,19 @@ export async function runCronIsolatedAgentTurn(params: {
job: CronJob;
message: string;
abortSignal?: AbortSignal;
signal?: AbortSignal;
sessionKey: string;
agentId?: string;
lane?: string;
}): Promise<RunCronAgentTurnResult> {
const abortSignal = params.abortSignal ?? params.signal;
const isAborted = () => abortSignal?.aborted === true;
const abortReason = () => {
const reason = abortSignal?.reason;
return typeof reason === "string" && reason.trim()
? reason.trim()
: "cron: job execution timed out";
};
const isFastTestEnv = process.env.OPENCLAW_TEST_FAST === "1";
const defaultAgentId = resolveDefaultAgentId(params.cfg);
const requestedAgentId =
@@ -473,8 +482,8 @@ export async function runCronIsolatedAgentTurn(params: {
agentDir,
fallbacksOverride: resolveAgentModelFallbacksOverride(params.cfg, agentId),
run: (providerOverride, modelOverride) => {
if (params.abortSignal?.aborted) {
throw new Error("cron: isolated run aborted");
if (abortSignal?.aborted) {
throw new Error(abortReason());
}
if (isCliProvider(providerOverride, cfgWithAgentDefaults)) {
const cliSessionId = getCliSessionId(cronSession.sessionEntry, providerOverride);
@@ -517,7 +526,7 @@ export async function runCronIsolatedAgentTurn(params: {
runId: cronSession.sessionEntry.sessionId,
requireExplicitMessageTarget: true,
disableMessageTool: deliveryRequested,
abortSignal: params.abortSignal,
abortSignal,
});
},
});
@@ -529,6 +538,10 @@ export async function runCronIsolatedAgentTurn(params: {
return withRunSession({ status: "error", error: String(err) });
}
if (isAborted()) {
return withRunSession({ status: "error", error: abortReason() });
}
const payloads = runResult.payloads ?? [];
// Update token+model fields in the session store.
@@ -584,6 +597,10 @@ export async function runCronIsolatedAgentTurn(params: {
}
await persistSessionEntry();
}
if (isAborted()) {
return withRunSession({ status: "error", error: abortReason(), ...telemetry });
}
const firstText = payloads[0]?.text ?? "";
let summary = pickSummaryFromPayloads(payloads) ?? pickSummaryFromOutput(firstText);
let outputText = pickLastNonEmptyTextFromPayloads(payloads);
@@ -672,6 +689,9 @@ export async function runCronIsolatedAgentTurn(params: {
? [{ text: synthesizedText }]
: [];
if (payloadsForDelivery.length > 0) {
if (isAborted()) {
return withRunSession({ status: "error", error: abortReason(), ...telemetry });
}
const deliveryResults = await deliverOutboundPayloads({
cfg: cfgWithAgentDefaults,
channel: resolvedDelivery.channel,
@@ -683,6 +703,7 @@ export async function runCronIsolatedAgentTurn(params: {
identity,
bestEffort: deliveryBestEffort,
deps: createOutboundSendDeps(params.deps),
abortSignal,
});
delivered = deliveryResults.length > 0;
}
@@ -765,6 +786,9 @@ export async function runCronIsolatedAgentTurn(params: {
return withRunSession({ status: "ok", summary, outputText, delivered: true, ...telemetry });
}
try {
if (isAborted()) {
return withRunSession({ status: "error", error: abortReason(), ...telemetry });
}
const didAnnounce = await runSubagentAnnounceFlow({
childSessionKey: agentSessionKey,
childRunId: `${params.job.id}:${runSessionId}`,
@@ -785,6 +809,7 @@ export async function runCronIsolatedAgentTurn(params: {
endedAt: runEndedAt,
outcome: { status: "ok" },
announceType: "cron job",
signal: abortSignal,
});
if (didAnnounce) {
delivered = true;