Skip to content

[Bug]: Ollama NDJSON stream abort errors produce reason=unknown in model fallback logs #58315

Description

@slideshow-dingo

Bug Description

When the Ollama provider times out or is aborted mid-NDJSON-stream, the model fallback decision log shows reason=unknown instead of reason=timeout. This makes debugging Ollama timeout cascades difficult.

Expected Behavior

Ollama timeout/abort errors should be classified as reason=timeout in model fallback decision logs, consistent with how other provider timeouts are classified.

Actual Behavior

model fallback decision: decision=candidate_failed requested=ollama/minimax-m2.7:cloud candidate=ollama/minimax-m2.7:cloud reason=unknown next=ollama/qwen3.5:cloud

The fallback chain still functions correctly, but reason=unknown is logged instead of reason=timeout.

Root Cause

In auth-profiles-*.js, resolveFailoverReasonFromError() does not handle Ollama-specific abort/NDJSON stream errors. When an Ollama call times out:

  1. The fetch AbortController fires and aborts the NDJSON stream reader
  2. The resulting error has no HTTP status (stream aborted before response completed), no error code, and a message like "stream closed" or the abort error message
  3. classifyFailoverReasonFromHttpStatus(status, message) → no status number to classify
  4. classifyFailoverReasonFromSymbolicCode(getErrorCode$1(err)) → no code
  5. Symbolic code list (ETIMEDOUT, ECONNRESET, etc.) → no match
  6. classifyFailoverReason(message) → no pattern matches "stream closed" or AbortError messages

Result: null → defaults to "unknown" in logModelFallbackDecision().

Impact

  • Severity: Low — fallback works correctly, but log visibility for Ollama timeout events is degraded
  • Frequency: Every Ollama timeout produces reason=unknown until fixed
  • Debugging cost: Makes it harder to distinguish Ollama timeouts from other failure types in logs

Reproduction

  1. Configure an Ollama provider with a cloud model that proxies through https://ollama.com:443
  2. Trigger a slow response or cold-start timeout (~138s observed in one case)
  3. Observe model fallback decision log with reason=unknown

Log excerpt:

[agent/embedded] Profile ollama:default timed out. Trying next account...
[agent/embedded] embedded run failover decision: runId=... stage=assistant decision=fallback_model reason=timeout provider=ollama/minimax-m2.7:cloud profile=sha256:...
[diagnostic] lane task error: lane=main durationMs=138273 error="FailoverError: LLM request timed out."
[model-fallback/decision] model fallback decision: decision=candidate_failed requested=ollama/minimax-m2.7:cloud candidate=ollama/minimax-m2.7:cloud reason=unknown next=ollama/qwen3.5:cloud

Note: reason=timeout is correctly passed to the embedded run failover decision, but the subsequent model fallback decision log shows reason=unknown. This suggests the error is classified correctly at the embedded run layer, but re-classified as unknown during the fallback attempt.

Suggested Fix

In resolveFailoverReasonFromError() in auth-profiles-*.js, add handling for AbortError / stream abort errors from Ollama:

  1. Add isAbortError(err) check early in resolveFailoverReasonFromError() to return "timeout" for AbortErrors before attempting HTTP status / symbolic code classification
  2. Alternatively, extend the message-based classifyFailoverReason() to match AbortError messages or "stream closed" patterns

This pattern is already used elsewhere in the codebase (e.g., isTimeoutErrorMessage handles "timed out" strings).

Version

OpenClaw 2026.3.28 (f9b1079)

Environment

  • OS: Linux (Ubuntu/Debian)
  • Install method: npm global (openclaw)
  • Provider: ollama (cloud model via https://ollama.com:443 proxy)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions