Skip to content

fix(mothership): number recovered turns past an unreadable ring and floor the replay TTL - #8501

Merged
waleedlatif1 merged 3 commits into
stagingfrom
fix/stream-recovery-seq-and-ttl-floor
Oct 1, 2026
Merged

waleedlatif1 merged 3 commits into
stagingfrom
fix/stream-recovery-seq-and-ttl-floor

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

This PR fixes two Chat stream-recovery findings from the release review. Neither one is a regression against the current release, and neither blocks it.

1. A recovered turn can number its events at 0 over an unreadable ring (recover-stream.ts)

Release-review finding: a recovery whose readEvents comes back empty resumes numbering at 0, even though the stream's seq counter has survived.

Root cause. readEvents skips any ring entry that fails envelope parsing. If every retained entry is unreadable, events is empty. startsAtReplayHead(undefined) is true, so the code took the "ring intact" branch with resumeSeq = events.at(-1)?.seq ?? 0 = 0. The writer then numbers new events from 1. The append script ZADDs them next to the old scores and SETs the counter, which moves it backwards (N → k). A live tail whose cursor sits between the old and new numbering can then silently skip new events. The empty-events path predates the lost-head branch added in #8469; prod has the same behaviour.

Fix. Use the recovered event's seq only when an event was actually recovered. Otherwise resume from the stream's counter (getLatestSeq), which still falls back to 0 when the key is missing. The lost-head branch already took this path. The controller still starts from an empty context and re-attaches with an empty receipt, so nothing else changes.

2. A configured replay TTL below the heartbeat lets an idle live buffer expire (buffer.ts)

Release-review finding: the 20 s chat-lock heartbeat refreshes the live buffer's TTL, but COPILOT_STREAM_TTL_SECONDS accepted any value of 1 s or more.

Root cause. With a TTL under about 20 s, a run that goes quiet loses its events and seq keys between refreshes. The refresh only calls EXPIRE, so it cannot bring them back. The default (3600 s) is unaffected, and nothing in the repo sets the variable.

Fix. getStreamConfig floors the configured live TTL at 60 s, which is three heartbeats. The default and every value of 60 s or more behave as before. The completed-stream TTL is unchanged.

Behaviour changes

  • A recovery with no readable ring events numbers new events after the stream's counter instead of from 1. Readers use zrangebyscore, so the gap is harmless.
  • COPILOT_STREAM_TTL_SECONDS values below 60 now act as 60.

Not changed

  • Both findings are real. Neither one was rejected.
  • A broader variant, max(lastRecoveredSeq, counter), would also cover a corrupt tail behind a readable head. I left it out: the finding was the empty-ring case, and that variant adds a Redis read to every recovery for a case that needs corrupt data mid-ring.

Test plan

  • stream-recovery.integration.ts: new case, a ring whose entries are all unreadable recovers with new events numbered after the counter (min seq 5). Fails on the pre-fix code.
  • buffer-ttl.integration.ts: new case, a live buffer under a 5 s configured TTL gets at least 60 s. Fails on the pre-fix code. The existing park test now shortens the event and seq keys' TTLs explicitly, since the floor no longer lets the env do it.
  • Both new tests checked red with the fix reverted and green with it applied (bun run test:integration buffer-ttl.integration.ts stream-recovery.integration.ts, disposable Postgres/Redis).
  • recover-stream.test.ts and the session/ unit tests pass.
  • bun run lint, bun run type-check (apps/sim), bun run check:audits pass.
  • Full bun run test:integration (left to CI).

…loor the replay TTL

A recovered run whose replay ring read back empty while its seq counter
survived (every retained entry unreadable) resumed numbering at 0, writing
over the old range and moving the counter backwards past readers' cursors.
Resume from the counter whenever no event was recovered.

COPILOT_STREAM_TTL_SECONDS below the 20 s chat-lock heartbeat let an idle
live buffer expire between refreshes. Floor it at 60 s.
@vercel

vercel Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs Ready Ready Preview Oct 1, 2026 1:46am UTC

Request Review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@greptile-apps

greptile-apps Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Medium risk] Fixes event numbering in stream recovery after data loss.

The PR appears safe to merge; no outstanding finding or new actionable issue remains.

Summary

This PR makes recovery resume event numbering from the stream counter when no readable event remains, and floors the configured live replay-buffer TTL at 60 seconds. Integration tests cover both cases; the TTL assertions now allow for Redis’s whole-second readings.

Reviews (2) · Last reviewed commit: "Merge remote-tracking branch 'origin/sta..."

Comment thread apps/sim/lib/mothership/request/session/buffer-ttl.integration.ts Outdated

@cubic-dev-ai cubic-dev-ai 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.

No issues found across 5 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

@cubic-dev-ai cubic-dev-ai 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.

No issues found across 5 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai 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.

No issues found across 5 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

@waleedlatif1
waleedlatif1 enabled auto-merge (squash) October 1, 2026 01:47
@waleedlatif1
waleedlatif1 merged commit 96fd30d into staging Oct 1, 2026
23 checks passed
@waleedlatif1
waleedlatif1 deleted the fix/stream-recovery-seq-and-ttl-floor branch October 1, 2026 06:21

This branch was successfully deployed

1 active deployment
Preview — 4e148f28 Deployed Oct 1, 2026 by vercel[bot]
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