Skip to content

fix(core,sdk): stop chat.agent losing messages during recovery - #4907

Open
ericallam wants to merge 1 commit into
feat/transcript-storage-tri-13667from
feature/tri-13752-chatagent-version-handover-duplicates-messages-and-turns
Open

fix(core,sdk): stop chat.agent losing messages during recovery#4907
ericallam wants to merge 1 commit into
feat/transcript-storage-tri-13667from
feature/tri-13752-chatagent-version-handover-duplicates-messages-and-turns

Conversation

@ericallam

Copy link
Copy Markdown
Member

Summary

When a chat.agent run boots to continue a session (a version handover, or a retry after a crash), it replays the unacknowledged user messages off session.in and dispatches them itself. A previous change stopped the live tail from re-answering those same messages by folding them into the resume cursor in one step. That cursor is what the next boot reads to know where to resume, and folding in every recovered message at once let it advance past a message the run had not answered yet. So if the run answered the first recovered message, wrote its turn boundary, then crashed before dispatching the rest, the next boot resumed past those messages and they were never answered.

Fix

A recovered message is now claimed on the session-stream router instead of folded into the cursor. A claim does two independent things:

  • it drops the message however late the live tail re-delivers it, so a recovered message is never answered twice;
  • it holds the resume cursor behind that message until the boot has dispatched it, so a turn boundary never publishes a cursor past a message still waiting for a turn.

The boot settles each claim as it dispatches the message, or right away for a message it folds into the seed chain or deliberately skips, so the cursor only advances over messages that have actually been handled. A claimed record whose route re-read never arrives over the tail degrades to being answered twice on the next boot, never to being dropped.

Covered by router-level unit tests for the claim/settle floor and a chat.agent boot test asserting the cursor published after the first recovered turn stays behind the still-unanswered ones.

@changeset-bot

changeset-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2303464

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/core Patch
@trigger.dev/python Patch
@internal/dashboard-agent Patch
@trigger.dev/build Patch
trigger.dev 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/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

@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 80f8c417-49c8-4b13-8dbb-ea953986bccd

📥 Commits

Reviewing files that changed from the base of the PR and between 7d29239 and 2303464.

📒 Files selected for processing (1)
  • packages/trigger-sdk/src/v3/ai.ts

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

📜 Recent review details
⏰ Context from checks skipped due to timeout. (15)
  • GitHub Check: packages / 📊 Merge Reports
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (21, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (14, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (17, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (18, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (16, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (20, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (15, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (13, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (11, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (12, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (19, 24)
  • GitHub Check: e2e / 🧪 CLI v3 tests (warp-windows-latest-x64-8x - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (warp-windows-latest-x64-8x - npm)
  • GitHub Check: internal / 🧪 Unit Tests: Internal
🧰 Additional context used
📓 Path-based instructions (7)
Always import from `@trigger.dev/sdk`.

📄 CodeRabbit inference engine (AGENTS.md)

Files:

  • packages/trigger-sdk/src/v3/ai.ts
**Prefer static imports over dynamic imports.**

📄 CodeRabbit inference engine (AGENTS.md)

Files:

  • packages/trigger-sdk/src/v3/ai.ts
Add crumbs as you write code — not just when debugging.

📄 CodeRabbit inference engine (AGENTS.md)

Files:

  • packages/trigger-sdk/src/v3/ai.ts
In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code

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

Files:

  • packages/trigger-sdk/src/v3/ai.ts
Use function declarations instead of default exports

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

Files:

  • packages/trigger-sdk/src/v3/ai.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/trigger-sdk/src/v3/ai.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/trigger-sdk/src/v3/ai.ts
🪛 ast-grep (0.45.2)
packages/trigger-sdk/src/v3/ai.ts

[warning] 7322-7325: Avoid logging sensitive data
Context: logger.warn("chat.agent: transcript load failed; continuing from the stream tail", {
error: error instanceof Error ? error.message : String(error),
sessionId: sessionIdForSnapshot,
})
Note: [CWE-532] Insertion of Sensitive Information into Log File.

(log-sensitive-data-typescript)

🔇 Additional comments (3)
packages/trigger-sdk/src/v3/ai.ts (3)

2322-2326: LGTM!

Also applies to: 2357-2363, 7449-7453, 7543-7565, 7596-7602, 7611-7621, 8056-8056, 9637-9637, 10013-10013


7794-7802: 📐 Maintainability & Code Quality | ⚡ Quick win

Update the stale comment; it describes the pre-fix cursor semantics.

This comment says the recovery block "already advanced the .in cursor past the recovered message." That described the old behavior, where the router raised the checkpoint cursor to the highest recovered sequence at restore time. Line 2357-2363 documents the new behavior: restore no longer raises the cursor; each recovered sequence is marked individually and held back until its claim settles.

In this branch, the cursor for the dispatched message only advances because dispatchBootInjected() on line 7805 calls settleRecovered for it right here — not because the recovery block "already" advanced it earlier. Update the comment so a future reader does not reintroduce the premature-cursor-advance bug this PR fixes, based on a stale description of the mechanism.

As per path instructions, this file relies on very precise "why" comments to keep the recovery flow's non-obvious invariants correct; an inaccurate one here is a higher risk than in an ordinary file.

📝 Suggested comment update
         /**
          * A retry (e.g. OOM) or continuation whose boot recovery restored an
-         * in-flight `.in` message dispatches it as the first turn. The recovery
-         * block already advanced the `.in` cursor past the recovered message,
-         * so the preload wait below would otherwise strand it (the run would
+         * in-flight `.in` message dispatches it as the first turn, settling its
+         * claim right here via `dispatchBootInjected()`. Without this branch the
+         * preload wait below would strand it (the run would
          * sit waiting for a "first message" that already arrived). `onPreload`
          * is a one-time start-of-run hook and is deliberately not re-run on a
          * recovered/continuation turn.
          */

7271-7283: 🎯 Functional Correctness

No change needed. SessionChannelRouter.settleRecovered removes seqNum from #owed with Set.delete, so repeated calls are safe and do not throw.


Walkthrough

The session router now tracks recovered sequence claims separately from unsettled claims. It drops late live-tail records for claimed sequences and holds the resume floor until each claim settles. Chat recovery passes individual sequence numbers to the router, settles them when recovered payloads dispatch, and settles undispatched entries immediately. New tests cover router floor behavior and prevent duplicate recovered chat messages and turns.

Priority: ➖ Normal — Schedule this recovery fix because it prevents chat.agent from losing or duplicating user messages after handovers or crashes across the core and SDK packages.

Severity of issue fixed: Medium

Merge Risk: 🔵 Low · up to 23034

Recovery now preserves individual message claims and cursor progress, but a gap-handling edge case may still cause a recovered message to be skipped under a narrow replay-window failure condition. This is a bounded correctness risk requiring owner awareness before merge.

🚥 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 3 functions across 3 files. (1 skipped: 1 … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: preventing chat.agent from losing messages during recovery.
Description check ✅ Passed The description provides a detailed summary of the failure, the recovery claim/settle fix, and the test coverage. It does not include all template sections, such as the issue reference, checklist, cha…
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.
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 3 functions across 3 files. (1 skipped: 1 too large.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/tri-13752-chatagent-version-handover-duplicates-messages-and-turns

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.

@pkg-pr-new

pkg-pr-new Bot commented Sep 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

@trigger.dev/build

npm i https://pkg.pr.new/@trigger.dev/build@2303464

trigger.dev

npm i https://pkg.pr.new/trigger.dev@2303464

@trigger.dev/core

npm i https://pkg.pr.new/@trigger.dev/core@2303464

@trigger.dev/python

npm i https://pkg.pr.new/@trigger.dev/python@2303464

@trigger.dev/react-hooks

npm i https://pkg.pr.new/@trigger.dev/react-hooks@2303464

@trigger.dev/redis-worker

npm i https://pkg.pr.new/@trigger.dev/redis-worker@2303464

@trigger.dev/rsc

npm i https://pkg.pr.new/@trigger.dev/rsc@2303464

@trigger.dev/schema-to-json

npm i https://pkg.pr.new/@trigger.dev/schema-to-json@2303464

@trigger.dev/sdk

npm i https://pkg.pr.new/@trigger.dev/sdk@2303464

commit: 2303464

coderabbitai[bot]

This comment was marked as resolved.

@ericallam
ericallam marked this pull request as ready for review September 8, 2026 11:57
devin-ai-integration[bot]

This comment was marked as resolved.

@ericallam
ericallam force-pushed the feature/tri-13752-chatagent-version-handover-duplicates-messages-and-turns branch from 7d29239 to 9a2cfbb Compare September 8, 2026 12:38
@ericallam
ericallam changed the base branch from main to feat/transcript-storage-tri-13667 September 8, 2026 12:38
Rebased onto the transcript-storage stack. A continuation boot claims recovered session.in seqNums on the router and holds the resume cursor behind each until the boot settles it, so suppressing the tail's re-answer no longer advances the cursor past an un-answered message. Adds a changeset and router/boot tests.
@ericallam
ericallam force-pushed the feature/tri-13752-chatagent-version-handover-duplicates-messages-and-turns branch from 9a2cfbb to 2303464 Compare September 8, 2026 13:13
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