Skip to content

fix: stop retrying silent chat streams indefinitely - #4949

Merged
ericallam merged 4 commits into
triggerdotdev:mainfrom
gtremper:fix/chat-stream-retry-exhaustion
Sep 17, 2026
Merged

ericallam merged 4 commits into
triggerdotdev:mainfrom
gtremper:fix/chat-stream-retry-exhaustion

Conversation

@gtremper

@gtremper gtremper commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

When the server returns 200 without records, chat subscriptions can retry indefinitely. The client stall timer fires after 60 seconds, before S2 closes the response at its 120-second timeout, so the existing EOF budget never applies. Terminal failures also leave persisted isStreaming state active, so a reload starts another subscription.

Normal chat subscriptions now permit five reconnects after stall timeouts. A separate stall counter preserves unlimited retries for retryable connection failures, fetch timeouts, and browser wakeups. Decoded records restore the stall budget. Terminal failures clear state only for the owning subscription, and watch subscriptions remain unlimited.

The existing stall timer already ignores keepalives because the parser drops them before the timer reset. The comment correction does not change that behavior.

Checklist

  • The PR title follows the contribution convention.
  • The changes include tests and a changeset.
  • Local package builds, formatting, lint, and knip passed.
  • Maintainer CI and reference-project validation.

Testing

  • Regression tests reproduce timeout exhaustion, indefinite stalls, stale terminal state, and recovery beyond five connection failures.
  • Full suites at 8149b96: 695 SDK tests and 1,122 core tests passed.
  • Final error-message and constructor changes: all 43 stream tests passed.
  • Core and SDK builds passed.
  • Repository formatting, lint, and knip passed.
  • General and security review passes found no remaining defects.
  • The debug-marker check reports existing markers in apps/webapp/app/services/previewAutoArchive.server.ts; the changed files contain none.

The tests use local HTTP servers. They cover separate stall limits, fetch timeouts, body failures, keepalives, progress resets, mixed failures, wakeups, cancellation, watch recovery, and replacement ownership. Core tests exercise stall exhaustion with short timers. The SDK's six-minute silence window needs reference-project validation.

Changelog

Silent chat subscriptions now report Stream stalled: no records received after five stall retries. Network failures retain automatic recovery, and watch subscriptions remain unlimited.

Risk

Condition Result
Retryable connection or fetch failure No finite retry deadline; exponential backoff continues
Repeated connected silence The sixth 60-second stall ends the subscription
Browser wake or online event Reconnect without consuming or restoring the stall budget

With immediate response headers, six silent attempts take 368.5–377 seconds, about 6.1–6.3 minutes. Network delays extend this window. A healthy tool call with no records can also reach this limit: silence does not prove that the run is dead. The terminal error stops automatic client resumption but does not cancel the server-side run.

Shared core consumers retain their configured retry limits. Repeated successful responses without records now increase backoff until a decoded record arrives. Caller cancellation and token-refresh limits remain unchanged.

@changeset-bot

changeset-bot Bot commented Sep 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7825d1b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 27 packages
Name Type
@trigger.dev/core Patch
@trigger.dev/sdk Patch
@trigger.dev/build Patch
trigger.dev Patch
@trigger.dev/python Patch
@trigger.dev/redis-worker Patch
@trigger.dev/schema-to-json Patch
@internal/clickhouse Patch
@internal/llm-model-catalog Patch
@internal/metrics-pipeline Patch
@trigger.dev/rbac Patch
@internal/redis Patch
@internal/replication Patch
@internal/run-engine Patch
@internal/run-store Patch
@internal/schedule-engine Patch
@internal/tracing Patch
@internal/webhook-engine Patch
@internal/webhook-sources Patch
@internal/dashboard-agent Patch
@internal/cache Patch
@trigger.dev/react-hooks Patch
@trigger.dev/rsc Patch
@trigger.dev/database Patch
@trigger.dev/otlp-importer Patch
@trigger.dev/sso Patch
@internal/testcontainers Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@gtremper
gtremper marked this pull request as ready for review September 17, 2026 18:26
@coderabbitai

coderabbitai Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Walkthrough

Core SSE streams now track stall retries separately from connection retries. Retry state resets only after decoded records. Internal abort exhaustion reports Stream connection retries exhausted, while caller cancellation closes cleanly. Chat streams use bounded retries in non-watch mode and default retry behavior in watch mode. Superseded streams no longer update replacement stream state. Tests cover these retry, cancellation, watch, authorization, and token-refresh cases.

Priority: ⬇️ Low

Merge Risk: 🔵 Low · up to 7825d

A replacement stream can remain healthy while callers receive a spurious stream-error event from the old stream.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 4 files. (1 skipped: 1 … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main change: preventing silent chat streams from retrying indefinitely.
Description check ✅ Passed The description is mostly complete. It explains the problem, implementation, testing, risks, changelog, and checklist status. The template's issue reference and screenshots section are not included, b…
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 4 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@gtremper gtremper changed the title fix: stop retrying failed chat streams indefinitely fix: stop retrying silent chat streams indefinitely Sep 17, 2026
@ericallam
ericallam enabled auto-merge September 17, 2026 22:00
@ericallam
ericallam added this pull request to the merge queue Sep 17, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟡 Minor · Suppress stale stream-error events. · chat.ts:2442-2465

packages/trigger-sdk/src/v3/chat.ts:2442-2465
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Suppress stale stream-error events. The late token-refresh path can reject after sendMessages replaces the old stream. The old stream then enters this non-abort handler while activeStreams points to the replacement. The current guard protects session state only; unconditional emitEvent notifies onEvent of a false error for the active chat.

Move the stream-error emission inside the existing ownership guard. Keep controller.error(error) outside the guard so the superseded reader still settles with its own error.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 29592fdc-bd49-47b9-85dc-50638f858213

📥 Commits

Reviewing files that changed from the base of the PR and between 8149b96 and 7825d1b.

📒 Files selected for processing (3)
  • .changeset/quiet-chat-stream-retries.md
  • packages/core/src/v3/apiClient/runStream-retries.test.ts
  • packages/core/src/v3/apiClient/runStream.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • .changeset/quiet-chat-stream-retries.md

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

📜 Review details
⏰ Context from checks skipped due to timeout. (17)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (17, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (19, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (18, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (11, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (21, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (20, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (16, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (15, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (12, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (13, 24)
  • GitHub Check: e2e / 🧪 CLI v3 tests (warp-windows-latest-x64-8x - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (warp-windows-latest-x64-8x - pnpm)
  • GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp (1, 2)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (2)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (1)
  • GitHub Check: packages / 🧪 Unit Tests: Packages (3, 3)
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (7)
**Public packages** (`packages/*`): Use `build`.

📄 CodeRabbit inference engine (AGENTS.md)

Files:

  • packages/core/src/v3/apiClient/runStream-retries.test.ts
  • packages/core/src/v3/apiClient/runStream.ts
Use zod for validation in packages/core and apps/webapp

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Files:

  • packages/core/src/v3/apiClient/runStream-retries.test.ts
  • packages/core/src/v3/apiClient/runStream.ts
Never import the root package (`@trigger.dev/core`).

📄 CodeRabbit inference engine (packages/core/CLAUDE.md)

Files:

  • packages/core/src/v3/apiClient/runStream-retries.test.ts
  • packages/core/src/v3/apiClient/runStream.ts
Use vitest for all tests in the Trigger.dev repository

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Files:

  • packages/core/src/v3/apiClient/runStream-retries.test.ts
Use function declarations instead of default exports

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Files:

  • packages/core/src/v3/apiClient/runStream-retries.test.ts
  • packages/core/src/v3/apiClient/runStream.ts
Use types over interfaces for TypeScript Avoid using enums; prefer string unions or const objects instead

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Files:

  • packages/core/src/v3/apiClient/runStream-retries.test.ts
  • packages/core/src/v3/apiClient/runStream.ts
When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs Do not use high-cardinality attributes in OTEL metr...

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

Files:

  • packages/core/src/v3/apiClient/runStream-retries.test.ts
  • packages/core/src/v3/apiClient/runStream.ts
🔇 Additional comments (2)
packages/core/src/v3/apiClient/runStream.ts (1)

224-224: LGTM!

Also applies to: 300-300, 658-663

packages/core/src/v3/apiClient/runStream-retries.test.ts (1)

86-86: LGTM!

Also applies to: 121-121, 134-134, 185-185

Merged via the queue into triggerdotdev:main with commit 34c2d69 Sep 17, 2026
66 checks passed
Sign up for free to 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.

2 participants