Skip to content

fix(sdk): reset skipToTurnComplete when a new chat turn starts - #4744

Open
wuweiweiwu wants to merge 4 commits into
triggerdotdev:mainfrom
wuweiweiwu:fix/chat-skip-to-turn-complete-reset
Open

fix(sdk): reset skipToTurnComplete when a new chat turn starts#4744
wuweiweiwu wants to merge 4 commits into
triggerdotdev:mainfrom
wuweiweiwu:fix/chat-skip-to-turn-complete-reset

Conversation

@wuweiweiwu

Copy link
Copy Markdown

✅ Checklist

  • I have followed every step in the contributing guide
  • The PR title follows the convention.
  • I ran and tested the code works

Testing

Reproduced with useTriggerChatTransport + useChat and the stop pattern from the ai-chat frontend docs:

  1. Send a message so a turn is streaming.
  2. Call transport.stopGeneration(chatId), then useChat's stop().
  3. Send another message.

Before this change the second turn never renders: no parts arrive, status stays streaming, and the session stays isStreaming: true, so a stop button stays on screen until the page is reloaded. The run itself is fine and everything persists, so a reload shows the full response.

Cause: stopGeneration sets state.skipToTurnComplete = true, and the read loop only clears that when it sees a TURN_COMPLETE record. The abort closes the reader before that record arrives, so the flag survives into the next turn and every record of that turn is skipped, including its own TURN_COMPLETE.

After this change the same sequence streams the second turn normally. Verified against 4.5.11 and 4.5.12 (both affected) with the equivalent patch applied to the built SDK.


Changelog

Reset skipToTurnComplete when a new chat turn or action is sent, so a message sent after stopGeneration streams normally instead of leaving the chat stuck in a streaming state.

@changeset-bot

changeset-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9902a08

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/sdk Patch
@trigger.dev/python Patch
@internal/dashboard-agent Patch
@trigger.dev/build Patch
@trigger.dev/core Patch
@trigger.dev/react-hooks Patch
@trigger.dev/redis-worker Patch
@trigger.dev/rsc Patch
@trigger.dev/schema-to-json Patch
@trigger.dev/database Patch
@trigger.dev/otlp-importer Patch
@trigger.dev/rbac Patch
@trigger.dev/sso Patch
trigger.dev Patch
@internal/clickhouse Patch
@internal/llm-model-catalog Patch
@internal/metrics-pipeline 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/testcontainers Patch
@internal/cache 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

@github-actions

Copy link
Copy Markdown
Contributor

Hi @wuweiweiwu, thanks for your interest in contributing!

This project requires that pull request authors are vouched, and you are not in the list of vouched users.

This PR will be closed automatically. See https://github.com/triggerdotdev/trigger.dev/blob/main/CONTRIBUTING.md for more details.

@github-actions github-actions Bot closed this Aug 21, 2026
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The chat transport resets skipToTurnComplete before starting a new message or action stream. Tests cover message and action turns after an incomplete stopped turn and verify that pending text streams and turn-completed is emitted. A patch changeset documents the SDK fix.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 2 files.
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 primary SDK fix and follows the repository convention.
Description check ✅ Passed The description includes a completed checklist, detailed testing steps, and a clear changelog; missing issue and screenshots are non-critical.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Open in Devin Review

@matt-aitken matt-aitken reopened this Aug 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for your contribution! We require all external PRs to be opened in draft status first so you can address CodeRabbit review comments and ensure CI passes before requesting a review. Please re-open this PR as a draft. See CONTRIBUTING.md for details.

@github-actions github-actions Bot closed this Aug 21, 2026
@matt-aitken matt-aitken reopened this Aug 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for your contribution! We require all external PRs to be opened in draft status first so you can address CodeRabbit review comments and ensure CI passes before requesting a review. Please re-open this PR as a draft. See CONTRIBUTING.md for details.

@github-actions github-actions Bot closed this Aug 21, 2026
@matt-aitken matt-aitken reopened this Aug 21, 2026
@matt-aitken
matt-aitken marked this pull request as draft August 21, 2026 15:45
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for your contribution! We require all external PRs to be opened in draft status first so you can address CodeRabbit review comments and ensure CI passes before requesting a review. Please re-open this PR as a draft. See CONTRIBUTING.md for details.

@github-actions github-actions Bot closed this Aug 21, 2026
@matt-aitken matt-aitken reopened this Aug 21, 2026
coderabbitai[bot]

This comment was marked as resolved.

Co-authored-by: Wei-Wei Wu <wei-wei@momentic.ai>
@wuweiweiwu

Copy link
Copy Markdown
Author

Added regression tests in packages/trigger-sdk/test/chat-transport-events.test.ts: a stop that never sees TURN_COMPLETE, followed by sendMessages and by sendAction. Each asserts the next stream delivers its chunk and a turn-completed event.

The .out stub honours the Last-Event-ID cursor, so a resubscribe cannot replay skipped records. Both tests fail without the reset and pass with it.

Written by Devin

coderabbitai[bot]

This comment was marked as resolved.

@wuweiweiwu
wuweiweiwu marked this pull request as ready for review August 21, 2026 19:31

@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 found 1 new potential issue.

Open in Devin Review

Comment on lines +873 to +875
// A stop that never saw its TURN_COMPLETE leaves the flag set, and the new
// turn would be skipped record by record.
state.skipToTurnComplete = false;

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.

🔍 Stopped-turn leftover chunks no longer skipped

Clearing skipToTurnComplete (chat.ts:875, chat.ts:1290) means the new subscription streams every record from its resume cursor. The sinceInSeq guard at chat.ts:1994 filters only stale TURN_COMPLETE records, not leftover text-deltas from the stopped turn. If any remain on .out below the new send, they can now surface in the new turn's UI. Depends on server stop semantics; the author's test streamed cleanly.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Real trade-off, and it is intentional. Some notes for a maintainer who knows the server stop semantics:

  • The skip flag exists to drop the tail of a stopped turn, and it clears only on that turn's TURN_COMPLETE. If the stopped turn never writes one, the flag survives every later turn, so the chat is stuck in isStreaming until a reload. That is the bug this PR fixes.
  • Leftover deltas cannot be filtered by cursor today: session-in-event-id rides on turn-complete records only (writeTurnCompleteChunk in ai.ts), so the sinceInSeq guard cannot see data records. A per-record turn marker on .out would be needed for exact filtering.
  • Scope of the residue: only records written between the abort and the moment the stop lands on the agent, and only if the stopped turn never completes. The cost is a few extra text-deltas in the next turn instead of a chat that no longer streams at all.

If stop does write a TURN_COMPLETE for the stopped turn, the flag clears on the next subscription anyway and this reset changes nothing. Happy to follow a different approach if you want the residue filtered server-side.

Written by Devin

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