Skip to content

Move pending stream messages to the continued run at continue-as-new - #1019

Merged
rmcdaniel merged 2 commits into
mainfrom
fix/1018-continue-as-new-messages
Nov 4, 2026
Merged

rmcdaniel merged 2 commits into
mainfrom
fix/1018-continue-as-new-messages

Conversation

@rmcdaniel

Copy link
Copy Markdown
Member

Fixes #1018. This PR is stacked on #1016 (the #1013 fix): its base is fix/1013-redrive-pending-messages, and I'll retarget it to main once #1016 merges. Both are planned for 2.20.2.

Problem

Continue-as-new copied only the message cursor (MessageStreamCursor::transferCursor), in two places:

  • the embedded executor (WorkflowExecutor, at continue-as-new);
  • the worker bridge (DefaultWorkflowTaskBridge, when an external worker completes a task with continue_as_new).

An inbound stream message keeps the workflow_run_id of the run that was current when it arrived, and inbox()->receive() reads only the receiving run's rows. So a message still pending at continue-as-new stayed on the closed run and was never received. Signals weren't affected; accepted, unapplied signals already move.

Fix

Both paths now call MessageService::transferMessagesToContinuedRun($run, $continuedRun), right after the instance's current_run_id switches. That moves pending inbound rows and the cursor together. New messages after that point already target the continued run.

Tests (each fails with the fix removed)

  • V2MessageStreamReplayTest::testAContinuedRunReceivesAMessageThatWasPendingAtContinueAsNew (embedded, new fixture TestMessageStreamContinueAsNewWorkflow):
    • Generation 1 receives m1 and continues as new, with m2 still pending.
    • Generation 2 receives m2. Without the fix it gets null.
  • V2WorkflowTaskBridgeTest::testContinueAsNewMovesPendingStreamMessagesToTheContinuedRun (worker bridge): a message pending when the worker completes with continue_as_new ends up on the continued run. Without the fix it stays on the old run.
  • Replay corpus: continue-as-new-pending-message-transfer.json plus the scenario testContinueAsNewPendingMessageCorpusReachesTheContinuedRun. It fails without the fix.

Local results

  • Feature suites pass 430 (4 skipped): MessageStream*, MessageCursorContinueAsNew, ContinueAsNew*, WorkflowTaskBridge, ReplayCorpus and FailedRunRedrive.
  • ReplayRegressionCorpusTest passes 101/101.
  • ECS and PHPStan are clean.

The docs agent is correcting message-streams.md and continue-as-new.md, which claimed this transfer already happened.

🤖 Generated with Claude Code

Continue-as-new copied only the message cursor, in both the embedded
executor and the worker bridge, so a stream message still pending when
a run continued as new stayed on the closed run and was never received
(#1018). Both paths now call MessageService::transferMessagesToContinuedRun.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rmcdaniel
rmcdaniel changed the base branch from fix/1013-redrive-pending-messages to main November 4, 2026 03:22
@sonarqubecloud

sonarqubecloud Bot commented Nov 4, 2026

Copy link
Copy Markdown

@rmcdaniel

Copy link
Copy Markdown
Member Author

General interim review (Security unavailable), exact head 7309416150ed260b23aca9fad8c6ddcd6f54bf8b: CLEARED. The reviewer was independent of the author. CI is 16 pass, 7 skipped, CLEAN.

  • Atomic: both continue-as-new paths transfer inside the task transaction. They lock the instance FOR UPDATE, switch current_run_id, then transfer. Sends serialize on the same lock, so no message can be stranded.
  • Scoped: only pending inbound rows move. Outbound rows, consumed rows and other instances' rows are left alone.
  • Ledger: in a 3-generation continue-as-new chain with replays, each replayed receive returns what was recorded, consumed rows stay on their own run, and no sends are duplicated. Continue-as-new followed by redrive is correct, and so is the reverse.
  • Counterfactual: the embedded, worker-bridge and corpus tests fail against main's src on MySQL and Postgres.
  • Suites and merge: all related suites pass, including WorkflowTaskBridge with 266 tests. The merge with Keep a stream's pending messages receivable whatever moves the run cursor #1017 is clean.

Without #1017, the next signal still hides moved messages. Merge this together with #1017, after #1017's H1 is fixed. Hold 2.20.2 until then.

@rmcdaniel
rmcdaniel merged commit 74aa0e6 into main Nov 4, 2026
23 checks passed
@rmcdaniel

Copy link
Copy Markdown
Member Author

Merged on General interim clearance of exact head 7309416150ed260b23aca9fad8c6ddcd6f54bf8b.

@rmcdaniel rmcdaniel mentioned this pull request Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pending stream messages are stranded on the old run at continue-as-new

1 participant