Skip to content

fix(sdk): preserve stopped chat boundaries across successor sends - #4954

Closed
gtremper wants to merge 1 commit into
triggerdotdev:mainfrom
gtremper:fix/chat-stop-successor-boundary
Closed

gtremper wants to merge 1 commit into
triggerdotdev:mainfrom
gtremper:fix/chat-stop-successor-boundary

Conversation

@gtremper

Copy link
Copy Markdown
Contributor

Stop acknowledgments can close a later response, and lost Stop state can replay old tool output after a reload.
The SDK now records Stop before the request, retains its boundary through hydration, and requires transcript reload for accepted responses without sequence correlation.

Checklist

  • The PR title follows the contribution convention.
  • The changes include regression tests and a patch changeset.
  • Maintainer CI and reference-project validation.

Testing

  • Published SDK 4.6.3: 12 of the 15 new HTTP regressions fail
  • Corrected source: all 15 cases pass with real HTTP, SSE parsing, and the AI SDK reader
  • Full SDK suite: 740 tests pass across 81 files
  • Core and SDK builds, repository formatting, and lint pass
  • Knip: one existing unused export, RedisCacheStoreConfig, in unchanged internal-packages/cache/src/stores/redis.ts:7
  • Independent correctness and security reviews: no remaining findings
  • Coverage: delayed acknowledgments, failed Stop, reloads, passive abort, repeated Stop, missing boundaries, session recreation, and blocked duplicate retries
  • Compatibility: sequence-free responses without a stopped boundary

Changelog

New chat responses remain intact after Stop, including slow acknowledgments and page reloads.

Risk

  • Failed Stop: the local reader closes, but the remote run can continue
  • Persisted state: new optional stopped-boundary and reload fields
  • Reference-project validation: not run locally

@changeset-bot

changeset-bot Bot commented Sep 19, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4ca683d

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 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
@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

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 Sep 19, 2026
@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 7fd3e8c8-dfc4-4f10-8f2e-82bb72abc08e

📥 Commits

Reviewing files that changed from the base of the PR and between 7e92a02 and 4ca683d.

📒 Files selected for processing (5)
  • .changeset/chat-stop-successor-boundary.md
  • packages/trigger-sdk/src/v3/chat-stop.test.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/trigger-sdk/src/v3/chat.ts
  • packages/trigger-sdk/test/chat-transport-events.test.ts
✨ 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 found 1 potential issue.

Devin Review

Comment on lines +1670 to +1675
private assertTranscriptReady(chatId: string, state: ChatSessionState): void {
if (!state.requiresTranscriptReload) return;
this.coordinator?.release(chatId);
throw new Error(
"Stopped chat response cannot be matched. Reload the chat before sending another message."
);

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.

🔴 Transcript reload leaves chat blocked

After requiresTranscriptReload is set, seedResumeCursor leaves it and the stopped boundary intact. Reloaded chats still reject every send and reconnect.

Learn more

The transport sets requiresTranscriptReload after the server accepts a successor without returning its input sequence. The public transcript-loading flow calls seedResumeCursor, which neither advances an existing cursor nor clears this flag or the supersede gate. assertTranscriptReady therefore keeps rejecting sends after the requested reload, and reconnectToStream also keeps returning null.

Example: A stopped chat accepts message B without a sequence, so message B throws the reload error. useLoadTranscript then loads a snapshot through B and calls seedResumeCursor(chatId, "20"). The session still has requiresTranscriptReload: true; sending message C throws the same error instead of continuing from event 20.

Recommended fix: Add an explicit transcript-reload operation that force-applies the loaded output cursor and clears requiresTranscriptReload, skipToTurnComplete, supersededInputSeq, and stale active-turn state. Make useLoadTranscript use that operation only after a successful fresh transcript load.

Devin Review


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

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.

1 participant