fix(webactor): walk an undelivered report back to a caller behind the hop - #16
Merged
Merged
Conversation
… hop A report only travelled forward, so it reached a caller waiting on a response but not one waiting behind an intermediate relay: a request that failed on a later hop still retried until its `abortSignal` fired. The way back is already recorded. An envelope on its way out carries the checkpoints of every hop it crossed, and `response` turns exactly those into a route. Applying the same arithmetic — the checkpoints minus the hop that just failed — sends the report back the way the message came, and the usual route matching reduces it hop by hop until it lands on the pending `request`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
#15 routes a
messageerrorto whoever was waiting, but only forward. That covers a caller waiting on a response — the failed envelope is routed, so the report inherits its route and continues.It does not cover a caller waiting on a request. A request is still on its way out and carries no route, so the report was terminal: only the endpoint adjacent to the failing hop learned about it. A request that failed on a later relay went on retrying every 500 ms until its
abortSignalfired — the exact symptom #15 set out to remove, just one topology over.What changed
The way back is already recorded. An envelope on its way out accumulates the checkpoints of every hop it crossed, and
responsealready turns exactly those into a route. The same arithmetic applies here: the checkpoints minus the hop that just failed are the way back to the sender.The report lands on the pending
requestwith__route === cidand rejects it. No new routing machinery — it reuses the mechanism responses already travel on.isCheckpointedEnvelopejoinsisRoutedEnvelopeinutils/routeso the two directions read the same way.About the
crossLinkbug I reported earlierWhile looking at this I went back to verify the devtools
crossLinkdefect I had described, and it does not exist. Measured with a throwaway spec dumping the event stream forworker-chain, with and without the recording that supposedly triggered it: identical graphs — 38 nodes, 25 links, 1020 messages, 4 worker threads, 12 cross-thread links — and zero type-filtered drops in that scenario, so the suspect line never even runs there.The guard in
crossLinkis correct: it deduplicates the two-sided attach, where both threads announce the same pair and one arrives throughingest. A collision with a locally inferred link is not constructible, becauseidentifybuilds node ids asname<threadId-pointerId>and a remote id cannot be produced locally.Verification
Unit 140/140, e2e 32/32,
tsc/ oxlint / oxfmt clean.The new test drives
caller — middle — brokenand asserts the request rejects with the transport's error. Without the change it hangs for the full ten seconds and fails on timeout.🤖 Generated with Claude Code