fix(agents): keep cron cloud idle watchdog enabled (#94445)

* fix(agents): keep cron cloud idle watchdog enabled

* docs: align cron idle timeout guidance
This commit is contained in:
Bek
2026-06-22 18:47:19 -04:00
committed by GitHub
parent dcb6b0dd6f
commit 5e915e1f89
3 changed files with 14 additions and 12 deletions
+1 -1
View File
@@ -167,7 +167,7 @@ surfaces, while Codex native hooks remain a separate lower-level Codex mechanism
- Agent runtime: `agents.defaults.timeoutSeconds` default 172800s (48 hours); enforced in `runEmbeddedAgent` abort timer.
- Cron runtime: isolated agent-turn `timeoutSeconds` is owned by cron. The scheduler starts that timer when execution begins, aborts the underlying run at the configured deadline, then runs bounded cleanup before recording the timeout so a stale child session cannot keep the lane stuck.
- Session liveness diagnostics: with diagnostics enabled, `diagnostics.stuckSessionWarnMs` classifies long `processing` sessions that have no observed reply, tool, status, block, or ACP progress. Active embedded runs, model calls, and tool calls report as `session.long_running`; owned silent model calls also stay `session.long_running` until `diagnostics.stuckSessionAbortMs` so slow or non-streaming providers are not reported as stalled too early. Active work with no recent progress reports as `session.stalled`; owned model calls switch to `session.stalled` at or after the abort threshold, and ownerless stale model/tool activity is not hidden as long-running. `session.stuck` is reserved for recoverable stale session bookkeeping, including idle queued sessions with stale ownerless model/tool activity. Stale session bookkeeping releases the affected session lane immediately after recovery gates pass; stalled embedded runs are abort-drained only after `diagnostics.stuckSessionAbortMs` (default: at least 5 minutes and 3x the warning threshold) so queued work can resume without cutting off merely slow runs. Recovery emits structured requested/completed outcomes, and diagnostic state is marked idle only if the same processing generation is still current. Repeated `session.stuck` diagnostics back off while the session remains unchanged.
- Model idle timeout: OpenClaw aborts a model request when no response chunks arrive before the idle window. `models.providers.<id>.timeoutSeconds` extends this idle watchdog for slow local/self-hosted providers, but it is still bounded by any lower `agents.defaults.timeoutSeconds` or run-specific timeout because those control the whole agent run. Otherwise OpenClaw uses `agents.defaults.timeoutSeconds` when configured, capped at 120s by default. Cron-triggered runs with no explicit model or agent timeout disable the idle watchdog and rely on the cron outer timeout.
- Model idle timeout: OpenClaw aborts a model request when no response chunks arrive before the idle window. `models.providers.<id>.timeoutSeconds` extends this idle watchdog for slow local/self-hosted providers, but it is still bounded by any lower `agents.defaults.timeoutSeconds` or run-specific timeout because those control the whole agent run. Otherwise OpenClaw uses `agents.defaults.timeoutSeconds` when configured, capped at 120s by default. Cron-triggered cloud model runs with no explicit model or agent timeout use the same default idle watchdog; cron-triggered local or self-hosted model runs disable the implicit watchdog unless an explicit timeout is configured, so slow local providers should set `models.providers.<id>.timeoutSeconds`.
- Provider HTTP request timeout: `models.providers.<id>.timeoutSeconds` applies to that provider's model HTTP fetches, including connect, headers, body, SDK request timeout, total guarded-fetch abort handling, and model stream idle watchdog. Use this for slow local/self-hosted providers such as Ollama before raising the whole agent runtime timeout, and keep the agent/runtime timeout at least as high when the model request needs to run longer.
## Where things can end early
@@ -9,10 +9,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../../config/config.js";
import { notifyLlmRequestActivity } from "../../../shared/llm-request-activity.js";
import type { StreamFn } from "../../runtime/index.js";
import {
resolveLlmIdleTimeoutMs,
streamWithIdleTimeout,
} from "./llm-idle-timeout.js";
import { resolveLlmIdleTimeoutMs, streamWithIdleTimeout } from "./llm-idle-timeout.js";
const DEFAULT_LLM_IDLE_TIMEOUT_MS = 120_000;
@@ -145,11 +142,11 @@ describe("resolveLlmIdleTimeoutMs", () => {
);
});
it("disables the default idle timeout for cron when no timeout is configured", () => {
expect(resolveLlmIdleTimeoutMs({ trigger: "cron" })).toBe(0);
it("uses the default idle timeout for cron cloud model calls when no timeout is configured", () => {
expect(resolveLlmIdleTimeoutMs({ trigger: "cron" })).toBe(DEFAULT_LLM_IDLE_TIMEOUT_MS);
const cfg = { agents: { defaults: {} } } as OpenClawConfig;
expect(resolveLlmIdleTimeoutMs({ cfg, trigger: "cron" })).toBe(0);
expect(resolveLlmIdleTimeoutMs({ cfg, trigger: "cron" })).toBe(DEFAULT_LLM_IDLE_TIMEOUT_MS);
});
it("caps agents.defaults.timeoutSeconds for cron before disabling the default idle timeout", () => {
@@ -157,6 +154,15 @@ describe("resolveLlmIdleTimeoutMs", () => {
expect(resolveLlmIdleTimeoutMs({ cfg, trigger: "cron" })).toBe(DEFAULT_LLM_IDLE_TIMEOUT_MS);
});
it("keeps cron local provider model calls opted out of the implicit idle watchdog", () => {
expect(
resolveLlmIdleTimeoutMs({
trigger: "cron",
model: { baseUrl: "http://127.0.0.1:11434" },
}),
).toBe(0);
});
it.each([
"http://localhost:11434",
"http://127.0.0.1:11434",
@@ -183,10 +183,6 @@ export function resolveLlmIdleTimeoutMs(params?: {
return clampImplicitTimeoutMs(agentTimeoutMs);
}
if (params?.trigger === "cron") {
return 0;
}
// The default watchdog is a network-silence-as-hang guard for cloud providers.
// Local providers can legitimately stream nothing for many minutes during
// prompt evaluation or thinking, so falling back to the default would abort