Bug type
Crash (process/app exits or hangs)
Beta release blocker
No
Summary
The diagnostic subsystem detects stuck processing sessions and emits a stuck session WARN, but the runtime has no recovery action wired to that detection, so affected sessions remain frozen indefinitely until the gateway process is restarted externally.
Steps to reproduce
NOT_ENOUGH_INFO
(The condition was observed in production on a macOS node at 2026-04-24T13:30–13:37 UTC but I do not yet have a deterministic trigger. A session entered state=processing and never advanced; ingestBatch repeatedly re-acquired the queue every ~3–4s with messages=4 while no sendMessage ok was emitted. A minimal deterministic repro requires further investigation.)
Expected behavior
When the diagnostic subsystem determines that a session has remained in state=processing past a configurable threshold, the runtime should abort the in-flight run for that session, release the queue, and allow subsequent messages to be processed — restoring forward progress without an external gateway restart. This matches the implied contract of the existing diagnostics.stuckSessionWarnMs config surface, which today only gates the WARN log and has no corresponding abort/recovery mechanism.
Actual behavior
The stuck session was detected and logged as WARN stuck session: sessionId=<REDACTED> sessionKey=<REDACTED> state=processing age=147s queueDepth=1 (2026-04-24T13:32:11.235Z, subsequently re-fired at 177s, 207s, and beyond), but the runtime took no corrective action. ingestBatch continued re-acquiring the session queue every ~3–4 seconds with the same 4-message batch while no assistant sendMessage ok log was emitted. The session only recovered after the gateway process was externally restarted via launchctl kickstart -k ai.openclaw.gateway (PID 56517 → 56913), at which point plugins re-initialized in ~2.5s and normal processing resumed.
Code path confirming absence of recovery: in dist/diagnostic-ClMwNnUA.js:275, logSessionStuck() only calls diagnosticLogger.warn(...) and emitDiagnosticEvent({type: "session.stuck", ...}); the heartbeat caller at dist/diagnostic-ClMwNnUA.js:366 (if (state.state === "processing" && ageMs > stuckSessionWarnMs) logSessionStuck(...)) does not invoke any abort, cancel, or queue-drain routine. There is no stuckSessionAbortMs or equivalent recovery config key in plugin-sdk/src/config/types.base.d.ts (DiagnosticsConfig exposes only stuckSessionWarnMs).
OpenClaw version
2026.4.22 (00bd2cf)
Operating system
macOS 26.4.1 (BuildVersion 25E253)
Install method
npm global (/opt/homebrew/lib/node_modules/openclaw), Node v22.22.0, launched via launchd (ai.openclaw.gateway.plist, RunAtLoad=true, KeepAlive=true).
Model
fireworks/accounts/fireworks/models/kimi-k2p6 (primary agent model on the affected node).
Provider / routing chain
openclaw -> fireworks (direct; no intermediate gateway/proxy configured for this provider).
Additional provider/model setup details
- Primary agent model:
fireworks/accounts/fireworks/models/kimi-k2p6
- Embedding model:
openai/text-embedding-3-large
- Coding agent model:
openai-codex/gpt-5.3-codex
- LCM summary model:
google/gemini-2.5-flash-lite (via OpenRouter)
- Active plugins on gateway:
browser, imessage, lossless-claw, telegram
- The stuck session was bound to a Telegram direct-chat channel, but the freeze is in the core session-processing loop, not Telegram-specific.
diagnostics.stuckSessionWarnMs was left at its default on this node; the issue reproduces at the default threshold.
Logs, screenshots, and evidence
Reporter note: This report was prepared by a separate OpenClaw agent instance (model: anthropic/claude-opus-4-7) that SSHed into the affected node to inspect live logs, installed-package source (/opt/homebrew/lib/node_modules/openclaw/dist/), and launchd state. All quoted log lines, code references, file paths, and version strings below were read directly from the affected host at investigation time; narrative sections that could not be grounded in that evidence are marked NOT_ENOUGH_INFO per the form's instructions.
### 1. Stuck-session WARN (redacted identifiers)
2026-04-24T13:32:11.235Z WARN {"subsystem":"diagnostic"} stuck session: sessionId=<REDACTED> sessionKey=<REDACTED> state=processing age=147s queueDepth=1
2026-04-24T13:32:41.236Z WARN {"subsystem":"diagnostic"} stuck session: sessionId=<REDACTED> sessionKey=<REDACTED> state=processing age=177s queueDepth=1
2026-04-24T13:33:11.xxxZ WARN {"subsystem":"diagnostic"} stuck session: sessionId=<REDACTED> sessionKey=<REDACTED> state=processing age=207s queueDepth=1
Source file: `subsystem-CJBoMDt5.js:324` (`logToFile`). Emission site: `diagnostic-ClMwNnUA.js:278` inside `logSessionStuck()`.
### 2. `ingestBatch` re-acquisition loop during the stuck window (abridged, timestamps only)
2026-04-24T13:30:37.572Z … messages=4 (queueKey=<REDACTED>, session=<REDACTED>)
2026-04-24T13:30:41.296Z … messages=4
2026-04-24T13:30:44.794Z … messages=4
2026-04-24T13:30:48.387Z … messages=4
… (repeats every ~3–4s continuously through 13:32:39.564Z)
2026-04-24T13:32:11.235Z WARN stuck session … age=147s
… (loop continues)
Every entry logs `queuedAhead=0 wait=0ms` and the identical 4-message payload; the session never transitions out of `processing`. No `sendMessage ok` is logged for this session during the window.
### 3. Code references (confirming no recovery action)
// dist/diagnostic-ClMwNnUA.js:275
function logSessionStuck(params) {
if (!areDiagnosticsEnabledForProcess()) return;
const state = getDiagnosticSessionState(params);
diagnosticLogger.warn(`stuck session: sessionId=${state.sessionId ?? "unknown"} sessionKey=${state.sessionKey ?? "unknown"} state=${params.state} age=${Math.round(params.ageMs / 1e3)}s queueDepth=${state.queueDepth}`);
emitDiagnosticEvent({
type: "session.stuck",
sessionId: state.sessionId,
sessionKey: state.sessionKey,
state: params.state,
ageMs: params.ageMs,
queueDepth: state.queueDepth
});
markDiagnosticActivity();
}
// dist/diagnostic-ClMwNnUA.js:366 (30s heartbeat)
for (const [, state] of diagnosticSessionStates) {
const ageMs = now - state.lastActivity;
if (state.state === "processing" && ageMs > stuckSessionWarnMs)
logSessionStuck({ sessionId: state.sessionId, sessionKey: state.sessionKey, state: state.state, ageMs });
}
// dist/plugin-sdk/src/config/types.base.d.ts:228–236
export type DiagnosticsConfig = {
enabled?: boolean;
flags?: string[];
/** Threshold in ms before a processing session logs "stuck session" diagnostics. */
stuckSessionWarnMs?: number;
otel?: DiagnosticsOtelConfig;
cacheTrace?: DiagnosticsCacheTraceConfig;
};
No `stuckSessionAbortMs` or recovery callback is declared or referenced.
### 4. Recovery signal (post-external-restart)
2026-04-24T13:37:34.587Z INFO {"subsystem":"gateway"} ready (4 plugins: browser, imessage, lossless-claw, telegram; 2.4s)
Gateway PID transition: 56517 → 56913. No further `stuck session` warnings in the subsequent window.
Impact and severity
- Affected: A single agent session bound to a Telegram direct chat on a macOS node. Any channel-bound session that enters
state=processing without completing is exposed to the same failure mode; this is core-runtime, not Telegram-specific.
- Severity: High. While a session is stuck, the agent is fully unresponsive on that channel and the only recovery is an external gateway process restart (
launchctl kickstart -k ai.openclaw.gateway in this case). No self-healing occurs even though the diagnostic subsystem has correctly identified the problem.
- Frequency: Observed once on 2026-04-24 between 13:30–13:37 UTC. Not yet known to be intermittent vs. recurring; root trigger is not yet identified.
- Consequence: All user messages to the affected channel during the stuck window go unanswered.
ingestBatch keeps re-acquiring the same 4-message queue every ~3–4 s without ever emitting sendMessage ok, so inbound messages accumulate but never receive a response until the gateway is externally restarted. Operator intervention is required for every occurrence.
Additional information
- Regression bounds: NOT_ENOUGH_INFO (only one version — 2026.4.22, build
00bd2cf — has been tested against this failure; no last-known-good / first-known-bad comparison is available from the observed evidence).
- Temporary workaround observed to work: external
launchctl kickstart -k ai.openclaw.gateway restart (PID 56517 → 56913, gateway ready in ~2.4 s, no further stuck warnings in the following window).
- A local out-of-process watchdog was installed on the affected node as defense-in-depth (launchd job
com.openclaw.gateway-watchdog, 60 s interval, 300 s cooldown; triggers on ≥2 stuck session warnings or ≥20 ingestBatch messages=4 events with ≤2 sendMessage ok in a 180 s window). This is an external band-aid, not a fix, and is reported here only for context.
- No
kill -USR1 stack dump was captured before the restart, so the cause of the hang inside the processing loop (LLM call, tool call, or plugin await) is not yet identified from the observed evidence.
Proposed fix (for maintainer consideration)
- Add
diagnostics.stuckSessionAbortMs?: number to DiagnosticsConfig (must be > stuckSessionWarnMs; disabled by default or a conservative default such as 300_000 ms).
- In the diagnostic heartbeat (around
diagnostic-ClMwNnUA.js:366), when ageMs > stuckSessionAbortMs, invoke a new recovery hook (e.g. requestSessionAbort({ sessionId, sessionKey, reason: "stuck-timeout" })) that:
- cancels the in-flight run / tool call for that session,
- releases the session queue lock,
- emits a
session.aborted diagnostic event (counter + structured log),
- lets the next
ingestBatch start a fresh run.
- Emit one
ERROR-level log entry on abort so operators can alert on recovery events without having to tail WARNs.
- (Optional) On abort, capture a lightweight stack sample of the blocked run so root cause is diagnosable from logs rather than requiring a live
kill -USR1 attachment.
Bug type
Crash (process/app exits or hangs)
Beta release blocker
No
Summary
The diagnostic subsystem detects stuck processing sessions and emits a
stuck sessionWARN, but the runtime has no recovery action wired to that detection, so affected sessions remain frozen indefinitely until the gateway process is restarted externally.Steps to reproduce
NOT_ENOUGH_INFO
(The condition was observed in production on a macOS node at 2026-04-24T13:30–13:37 UTC but I do not yet have a deterministic trigger. A session entered
state=processingand never advanced;ingestBatchrepeatedly re-acquired the queue every ~3–4s withmessages=4while nosendMessage okwas emitted. A minimal deterministic repro requires further investigation.)Expected behavior
When the diagnostic subsystem determines that a session has remained in
state=processingpast a configurable threshold, the runtime should abort the in-flight run for that session, release the queue, and allow subsequent messages to be processed — restoring forward progress without an external gateway restart. This matches the implied contract of the existingdiagnostics.stuckSessionWarnMsconfig surface, which today only gates the WARN log and has no corresponding abort/recovery mechanism.Actual behavior
The stuck session was detected and logged as
WARN stuck session: sessionId=<REDACTED> sessionKey=<REDACTED> state=processing age=147s queueDepth=1(2026-04-24T13:32:11.235Z, subsequently re-fired at 177s, 207s, and beyond), but the runtime took no corrective action.ingestBatchcontinued re-acquiring the session queue every ~3–4 seconds with the same 4-message batch while no assistantsendMessage oklog was emitted. The session only recovered after the gateway process was externally restarted vialaunchctl kickstart -k ai.openclaw.gateway(PID 56517 → 56913), at which point plugins re-initialized in ~2.5s and normal processing resumed.Code path confirming absence of recovery: in
dist/diagnostic-ClMwNnUA.js:275,logSessionStuck()only callsdiagnosticLogger.warn(...)andemitDiagnosticEvent({type: "session.stuck", ...}); the heartbeat caller atdist/diagnostic-ClMwNnUA.js:366(if (state.state === "processing" && ageMs > stuckSessionWarnMs) logSessionStuck(...)) does not invoke any abort, cancel, or queue-drain routine. There is nostuckSessionAbortMsor equivalent recovery config key inplugin-sdk/src/config/types.base.d.ts(DiagnosticsConfigexposes onlystuckSessionWarnMs).OpenClaw version
2026.4.22 (00bd2cf)
Operating system
macOS 26.4.1 (BuildVersion 25E253)
Install method
npm global (
/opt/homebrew/lib/node_modules/openclaw), Node v22.22.0, launched via launchd (ai.openclaw.gateway.plist,RunAtLoad=true,KeepAlive=true).Model
fireworks/accounts/fireworks/models/kimi-k2p6(primary agent model on the affected node).Provider / routing chain
openclaw -> fireworks(direct; no intermediate gateway/proxy configured for this provider).Additional provider/model setup details
fireworks/accounts/fireworks/models/kimi-k2p6openai/text-embedding-3-largeopenai-codex/gpt-5.3-codexgoogle/gemini-2.5-flash-lite(via OpenRouter)browser, imessage, lossless-claw, telegramdiagnostics.stuckSessionWarnMswas left at its default on this node; the issue reproduces at the default threshold.Logs, screenshots, and evidence
Impact and severity
state=processingwithout completing is exposed to the same failure mode; this is core-runtime, not Telegram-specific.launchctl kickstart -k ai.openclaw.gatewayin this case). No self-healing occurs even though the diagnostic subsystem has correctly identified the problem.ingestBatchkeeps re-acquiring the same 4-message queue every ~3–4 s without ever emittingsendMessage ok, so inbound messages accumulate but never receive a response until the gateway is externally restarted. Operator intervention is required for every occurrence.Additional information
00bd2cf— has been tested against this failure; no last-known-good / first-known-bad comparison is available from the observed evidence).launchctl kickstart -k ai.openclaw.gatewayrestart (PID 56517 → 56913, gatewayreadyin ~2.4 s, no further stuck warnings in the following window).com.openclaw.gateway-watchdog, 60 s interval, 300 s cooldown; triggers on ≥2stuck sessionwarnings or ≥20ingestBatch messages=4events with ≤2sendMessage okin a 180 s window). This is an external band-aid, not a fix, and is reported here only for context.kill -USR1stack dump was captured before the restart, so the cause of the hang inside the processing loop (LLM call, tool call, or plugin await) is not yet identified from the observed evidence.Proposed fix (for maintainer consideration)
diagnostics.stuckSessionAbortMs?: numbertoDiagnosticsConfig(must be> stuckSessionWarnMs; disabled by default or a conservative default such as 300_000 ms).diagnostic-ClMwNnUA.js:366), whenageMs > stuckSessionAbortMs, invoke a new recovery hook (e.g.requestSessionAbort({ sessionId, sessionKey, reason: "stuck-timeout" })) that:session.aborteddiagnostic event (counter + structured log),ingestBatchstart a fresh run.ERROR-level log entry on abort so operators can alert on recovery events without having to tailWARNs.kill -USR1attachment.