From bf6583a68aad7fb245b06db7e1b48afcbda1f316 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 12 Nov 2026 18:52:53 +0000 Subject: [PATCH 1/2] test(server): resolving a link skips a sleeping first address (repro #35519) Fails on main: every resolve waits out the probe timeout of the first stored address before trying the next, so the second resolve never returns while the test clock stands still. AI-assisted; verified with the tests and tools listed above. --- apps/server/src/peer/PeerLinks.test.ts | 32 ++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/apps/server/src/peer/PeerLinks.test.ts b/apps/server/src/peer/PeerLinks.test.ts index f35e760bea4d7..ce37f65e778ac 100644 --- a/apps/server/src/peer/PeerLinks.test.ts +++ b/apps/server/src/peer/PeerLinks.test.ts @@ -446,6 +446,38 @@ it.effect("lists a peer as reachable at another address while its first one slee ).pipe(Effect.provide(NodeHttpServer.layerTest)), ); +it.effect("resolves a link at its answering address without waiting out a sleeping first", () => + Effect.scoped( + Effect.gen(function* () { + const b = yield* serveB(box); + const described = yield* HttpClient.get(`${b.url}/.well-known/t3/environment`).pipe( + Effect.flatMap((response) => response.text), + Effect.provide(FetchHttpClient.layer), + ); + const elsewhere = yield* serveRaw(() => + Effect.succeed(HttpServerResponse.text(described, { contentType: "application/json" })), + ); + const a = yield* makeA(laptop); + const pairing = yield* b.auth.issuePairingCredential(); + yield* a.links.link({ + url: b.url, + alternateUrls: [elsewhere], + pairingCode: pairing.credential, + access: "auto", + }); + + yield* Ref.set(b.asleep, true); + const first = yield* a.links.resolve(box.environmentId).pipe(Effect.forkScoped); + yield* Queue.take(b.unanswered); + yield* TestClock.adjust(Duration.seconds(3)); + expect((yield* Fiber.join(first)).url).toBe(elsewhere); + + // The clock no longer moves: a resolve that probes the sleeping address first never returns. + expect((yield* a.links.resolve(box.environmentId)).url).toBe(elsewhere); + }), + ).pipe(Effect.provide(NodeHttpServer.layerTest)), +); + it.effect("cancelling a descriptor probe aborts its request and leaves later resolves fresh", () => Effect.scoped( Effect.gen(function* () { From 4acc0b4edbb37dd626298cc6be6e0e63bc0a2ac4 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 12 Nov 2026 18:59:25 +0000 Subject: [PATCH 2/2] fix(server): forwarded calls to a linked environment no longer wait out a silent first address PeerLinks.resolve probed a link's stored addresses one at a time, so every forwarded call waited the full probe timeout of a silent first address before trying the one that answers. Race the addresses as listing already does; the token still goes only to an address that answered as the peer. AI-assisted; verified with the tests and tools listed above. --- apps/server/src/peer/PeerLinks.ts | 35 ++++++++++++++++--------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/apps/server/src/peer/PeerLinks.ts b/apps/server/src/peer/PeerLinks.ts index 48f61e6554a17..124f59b72ef62 100644 --- a/apps/server/src/peer/PeerLinks.ts +++ b/apps/server/src/peer/PeerLinks.ts @@ -642,36 +642,37 @@ const make = Effect.gen(function* () { }); } // The token goes only to an address that still answers as this peer, - // so one that now answers as another machine never receives it. - let fenced = true; - let label = link.label; - const url = yield* Effect.findFirst(link.urls, (candidate) => - answersAs(candidate, environmentId).pipe( - Effect.map((found) => { - if (Option.isNone(found)) return false; - fenced = linkLimitsHold(found.value); - label = found.value.label; - return true; - }), - ), - ); - if (Option.isNone(url)) { + // so one that now answers as another machine never receives it. The + // addresses race, so a silent one never delays one that answers. + const answered = + link.urls.length === 0 + ? Option.none() + : yield* Effect.raceAll( + link.urls.map((url) => + answersAs(url, environmentId).pipe( + Effect.flatMap(Effect.fromOption), + Effect.map((peer) => ({ url, peer })), + ), + ), + ).pipe(Effect.option); + if (Option.isNone(answered)) { return yield* new PeerLinkError({ reason: "unreachable", message: `${link.label} did not answer at ${link.urls.join(", ")}.`, }); } + const { url, peer } = answered.value; // A link made before the peer had to fence linked work, or a peer moved // back to such a version, would carry work past those limits. - if (!fenced) { + if (!linkLimitsHold(peer)) { return yield* new PeerLinkError({ reason: "incompatible", message: `${link.label} runs a T3 Code version that cannot keep a linked agent's limits. Update it, then link again.`, }); } return { - link: yield* withLabel(link, label), - url: url.value, + link: yield* withLabel(link, peer.label), + url, token: new TextDecoder().decode(stored.value), }; },