Skip to content

fix(webactor): walk an undelivered report back to a caller behind the hop - #16

Merged
AStaroverov merged 1 commit into
fix/report-lost-messagesfrom
fix/report-back-to-caller
Jul 28, 2026
Merged

fix(webactor): walk an undelivered report back to a caller behind the hop#16
AStaroverov merged 1 commit into
fix/report-lost-messagesfrom
fix/report-back-to-caller

Conversation

@AStaroverov

Copy link
Copy Markdown
Owner

Stacked on #15 — review that one first. Base will be main once #15 lands.

Why

#15 routes a messageerror to 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 abortSignal fired — 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 response already 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.

forward   checkpoints  cid → cid/A/B → cid/A/B/B/C   ✗ post to C fails
report    route        cid/A/B                       ← checkpoints minus B→C
back      route        cid/A/B → cid                 usual routeEndsWith / reduceRoute

The report lands on the pending request with __route === cid and rejects it. No new routing machinery — it reuses the mechanism responses already travel on.

isCheckpointedEnvelope joins isRoutedEnvelope in utils/route so the two directions read the same way.

About the crossLink bug I reported earlier

While looking at this I went back to verify the devtools crossLink defect I had described, and it does not exist. Measured with a throwaway spec dumping the event stream for worker-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 crossLink is correct: it deduplicates the two-sided attach, where both threads announce the same pair and one arrives through ingest. A collision with a locally inferred link is not constructible, because identify builds node ids as name<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 — broken and 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

… 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>
@AStaroverov
AStaroverov merged commit c0c503f into fix/report-lost-messages Jul 28, 2026
5 checks passed
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