Skip to content

feat: detect stalled provider streams and auto-restart - #44677

Closed
wtnm wants to merge 8 commits into
anomalyco:devfrom
wtnm:stream-idle-watchdog
Closed

feat: detect stalled provider streams and auto-restart#44677
wtnm wants to merge 8 commits into
anomalyco:devfrom
wtnm:stream-idle-watchdog

Conversation

@wtnm

@wtnmwtnm commented Aug 24, 2026

Copy link
Copy Markdown

Issue for this PR

Closes #

No tracked issue. Related open PR on the same problem from the transport layer: #40010.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

A provider can return response headers and then stop sending tokens while keeping the connection open; both session paths waited forever in that state.

This adds an idle watchdog that fails the provider event stream when no event arrives within a window (default 120s). On the legacy path the failure surfaces as a retryable APIError, so the existing SessionRetry machinery retries it with backoff. In the V2 runner a stalled attempt restarts up to 2 times while nothing has been published for the assistant message; post-output stalls keep today's failAssistant behavior, and an overflow signal followed by a stall routes to compaction instead of resending.

The watchdog lives in @opencode-ai/llm as guardIdle (Stream.timeoutOrElse per-pull deadline — bare Stream.timeout would end stalled streams silently, looking like clean completion) and wraps both native and AI SDK runtimes at the session LLM boundary. experimental.streamIdleTimeoutMs overrides the window, 0 disables it.

How did you verify your code works?

  • bun typecheck in packages/llm, packages/opencode, packages/core.
  • New TestClock-based watchdog tests in packages/llm (pass-through under budget, typed failure on stall, elements before deadline preserved).
  • New restart-predicate tests plus fixture-based runner stall tests in packages/core (restart wiring, post-output no-restart, budget exhaustion, overflow skip).
  • Existing opencode retry/processor/provider-error suites and the full core suite pass.

Screenshots / recordings

Not applicable — no UI changes.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

wtnmand others added 5 commits August 24, 2026 13:53
Fails a provider event stream with a typed LLMError (TransportReason kind IdleTimeout) when no element arrives within the configured window, using Stream.timeoutOrElse per-pull deadline instead of bare Stream.timeout which would silently end a stalled stream.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
MessageV2.fromError recognizes the watchdog's LLMError TransportReason kind IdleTimeout and maps it to a retryable APIError so SessionRetry.policy schedules another attempt; the stalled wording is also covered by the retryable message patterns.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Wraps both native and AI SDK event streams at the session LLM service boundary with the llm-package idle watchdog so a provider that stops sending tokens fails the stream instead of hanging. experimental.streamIdleTimeoutMs overrides the duration (0 disables); the field is declared on the opencode-side Info type pending the core schema carrying it.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Wraps the provider turn stream with the idle watchdog and retries a stalled attempt up to STREAM_IDLE_RETRIES times while nothing has been published for the assistant message; post-output stalls keep today's failAssistant behavior. Compaction transitions, interrupts, and tool-fiber settlement are preserved per attempt.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
A context-overflow providerError followed by a stall must route to compaction, not resend the identical oversized request twice more before recovery.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@github-actionsgithub-actionsBot added the needs:compliance This means the issue will auto-close after 2 hours. label Aug 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

Found a potentially related PR:

fix(provider): recover stalled model streams

This PR appears related as it also addresses stalled model streams. Both PRs target the same problem domain of detecting and recovering from LLM provider streams that stop producing tokens. You may want to check if #40010 is still open or if it was superseded by this new implementation.

@github-actionsgithub-actionsBot removed the needs:compliance This means the issue will auto-close after 2 hours. label Aug 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

wtnmand others added 3 commits August 24, 2026 17:49
The watchdog previously counted silence from request start, false-tripping on long prompt processing before any token arrived. Now the deadline arms only after the stream begins delivering elements, and it is applied at the HTTP transport byte level so keep-alive frames and SSE comments reset it before framing drops them.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Three 800ms pauses under a 1s budget sum past it; only a single gap may trip the watchdog.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Silence between tool-call and its tool-result or tool-error is local execution time (bash, subagents, MCP calls, permission gates), and silence after step-finish is the next request transition; neither may trip the watchdog. The deadline now disarms on those boundary tags until the next element arrives, so only gaps inside continuous provider generation are bounded. Chunk-aware matching because pulls yield arrays.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@wtnm

wtnm commented Aug 24, 2026

Copy link
Copy Markdown
Author

Закрыт автором: требуется переоценка подхода после полевых проверок.

@wtnmwtnm closed this Aug 24, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@wtnm