Skip to content

Conversation

@nicktrn
Copy link
Collaborator

@nicktrn nicktrn commented Jan 23, 2026

Summary

Fixes #2856 - The onComplete callback in useRealtimeRun was firing prematurely in self-hosted environments with reverse proxies like Traefik.

Root Cause

The callback was triggered when the long-poll stream ended, regardless of whether the run had actually completed. Reverse proxies often close idle connections, causing the stream to end prematurely. In this case it was caused by fetch abort due to React strict mode.

Fix

Changed the condition from checking if run exists to checking if run?.finishedAt exists, ensuring onComplete only fires when the run has reached a terminal state.


Open with Devin

…eam disconnects

The onComplete callback in useRealtimeRun and useRealtimeRunWithStreams was
firing whenever the SSE stream ended, regardless of whether the run had
actually completed. This caused issues in self-hosted environments where
reverse proxies (like Traefik) may close idle connections.

The fix changes the condition from checking `run` to checking `run?.finishedAt`,
ensuring onComplete only fires when the run has actually reached a terminal
state.

Fixes #2856

Co-authored-by: nicktrn <nicktrn@users.noreply.github.com>
@changeset-bot
Copy link

changeset-bot bot commented Jan 23, 2026

🦋 Changeset detected

Latest commit: a7d7535

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/react-hooks Patch
d3-chat Patch
references-d3-openai-agents Patch
references-nextjs-realtime Patch
references-realtime-hooks-test Patch
references-realtime-streams Patch
@trigger.dev/build Patch
@trigger.dev/core Patch
@trigger.dev/python Patch
@trigger.dev/redis-worker Patch
@trigger.dev/rsc Patch
@trigger.dev/schema-to-json Patch
@trigger.dev/sdk Patch
@trigger.dev/database Patch
@trigger.dev/otlp-importer Patch
trigger.dev Patch
@internal/cache Patch
@internal/clickhouse Patch
@internal/redis Patch
@internal/replication Patch
@internal/run-engine Patch
@internal/schedule-engine Patch
@internal/testcontainers Patch
@internal/tracing Patch
@internal/tsql Patch
@internal/zod-worker Patch
references-telemetry 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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 23, 2026

Walkthrough

This change updates the useRealtimeRun and useRealtimeRunWithStreams hooks to require run?.finishedAt in addition to isComplete before invoking the onComplete callback. Previously the callback could fire when isComplete was true and a run existed; now it only fires when the run has an actual finishedAt timestamp, preventing premature invocation when subscriptions end (e.g., network disconnects). Explanatory comments were added above each affected effect.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description lacks the required checklist and structured format from the template. Add the complete checklist section with items for contributing guide, PR title convention, and testing confirmation. Include Testing and Changelog sections per the template.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: preventing onComplete from firing prematurely when the stream disconnects, matching the code modification.
Linked Issues check ✅ Passed The PR successfully addresses the core requirement from issue #2856 by ensuring onComplete only fires when run.finishedAt exists, preventing premature callbacks when streams disconnect.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the onComplete callback behavior described in issue #2856; no out-of-scope modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

@nicktrn nicktrn marked this pull request as draft January 23, 2026 14:57
@vibe-kanban-cloud
Copy link

Review Complete

Your review story is ready!

View Story

Comment !reviewfast on this PR to re-generate the story.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (2)
packages/react-hooks/src/hooks/useRealtime.ts (2)

151-159: Clarify the onComplete contract now that error/stop won’t trigger it.

The JSDoc says onComplete fires on errors or when the subscription stops, but the new finishedAt gate prevents those cases. Please update the docs (or add a separate error/stop callback) so consumers don’t get misled.

📝 Suggested doc update
-  /**
-   * Callback this is called when the run completes, an error occurs, or the subscription is stopped.
-   *
-   * `@param` {RealtimeRun<TTask>} run - The run object
-   * `@param` {Error} [err] - The error that occurred
-   */
+  /**
+   * Callback called when the run reaches a terminal state (finishedAt is set).
+   * Subscription errors/stops are surfaced via `error`.
+   *
+   * `@param` {RealtimeRun<TTask>} run - The run object
+   * `@param` {Error} [err] - The error that occurred
+   */

317-325: Same onComplete contract mismatch in the stream variant.

Please keep the docs consistent with the finishedAt-only behavior (or expose a dedicated error/stop callback) to avoid breaking consumer expectations.

📝 Suggested doc update
-  /**
-   * Callback this is called when the run completes, an error occurs, or the subscription is stopped.
-   *
-   * `@param` {RealtimeRun<TTask>} run - The run object
-   * `@param` {Error} [err] - The error that occurred
-   */
+  /**
+   * Callback called when the run reaches a terminal state (finishedAt is set).
+   * Subscription errors/stops are surfaced via `error`.
+   *
+   * `@param` {RealtimeRun<TTask>} run - The run object
+   * `@param` {Error} [err] - The error that occurred
+   */
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d893b26 and 3543031.

📒 Files selected for processing (1)
  • packages/react-hooks/src/hooks/useRealtime.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

**/*.{ts,tsx}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • packages/react-hooks/src/hooks/useRealtime.ts
**/*.{ts,tsx,js,jsx}

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

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

  • packages/react-hooks/src/hooks/useRealtime.ts
**/*.ts

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

**/*.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 metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • packages/react-hooks/src/hooks/useRealtime.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • packages/react-hooks/src/hooks/useRealtime.ts
{packages,integrations}/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Add a changeset when modifying any public package in packages/* or integrations/* using pnpm run changeset:add

Files:

  • packages/react-hooks/src/hooks/useRealtime.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `.withStreams()` to subscribe to realtime streams from task metadata in addition to run changes
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Subscribe to run updates using `runs.subscribeToRun()` for realtime monitoring of task execution
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Use `useRun`, `useRealtimeRun` and other SWR/realtime hooks from `trigger.dev/react-hooks` for data fetching
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Use `useRun`, `useRealtimeRun` and other SWR/realtime hooks from `trigger.dev/react-hooks` for data fetching

Applied to files:

  • packages/react-hooks/src/hooks/useRealtime.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `.withStreams()` to subscribe to realtime streams from task metadata in addition to run changes

Applied to files:

  • packages/react-hooks/src/hooks/useRealtime.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Subscribe to run updates using `runs.subscribeToRun()` for realtime monitoring of task execution

Applied to files:

  • packages/react-hooks/src/hooks/useRealtime.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Use `trigger.dev/react-hooks` package for realtime subscriptions in React components

Applied to files:

  • packages/react-hooks/src/hooks/useRealtime.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: typecheck / typecheck

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@bbreunig
Copy link

@nicktrn this PR only fixes the onComplete function. However this would still not return a Server-sent Event since you haven't set liveSsl: true as I mentioned here: #2856 (comment)
Is there a PR planned for this as well? Or am I missing something obvious?

@nicktrn nicktrn marked this pull request as ready for review January 26, 2026 17:40
Copy link

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

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 potential bugs to report.

View in Devin Review to see 3 additional flags.

Open in Devin Review

@nicktrn
Copy link
Collaborator Author

nicktrn commented Jan 26, 2026

@nicktrn this PR only fixes the onComplete function. However this would still not return a Server-sent Event since you haven't set liveSsl: true as I mentioned here: #2856 (comment) Is there a PR planned for this as well? Or am I missing something obvious?

hey @bbreunig I've commented on the issue with some additional details re the most likely cause: react strict mode.
This causes the long poll to restart (we intentionally don't use sse here) and the previous fetch request to be aborted, which triggers onComplete.

I thought about adding a more specific fix here but this seems cleaner and covers more potential edge cases.

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.

bug: useRealtimeRun onComplete fires prematurely in self-hosted environment (Traefik + Docker Compose)

3 participants