Skip to content

[Bug]: Stuck processing sessions are detected but never aborted — gateway requires external restart to recover #71127

Description

@EmpireCreator

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)

  1. Add diagnostics.stuckSessionAbortMs?: number to DiagnosticsConfig (must be > stuckSessionWarnMs; disabled by default or a conservative default such as 300_000 ms).
  2. 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.
  3. Emit one ERROR-level log entry on abort so operators can alert on recovery events without having to tail WARNs.
  4. (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.

Metadata

Metadata

Assignees

Labels

P1High-priority user-facing bug, regression, or broken workflow.bugSomething isn't workingbug:crashProcess/app exits unexpectedly or hangsclawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:crash-loopCrash, hang, restart loop, or process-level availability failure.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.

Type

No type

Fields

Priority

None yet

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions