From a9ddc3c60d3b9be0a6da557cb2d55e7e02dcf2c8 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 11 Nov 2026 06:24:19 +0000 Subject: [PATCH 1/3] test(server): one dropped probe lists a live linked peer as unreachable Reproduces #34195: statusOf probes each stored address once, so a single transient failure lists a healthy link as unreachable. 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 2ff34859c8a9..7ef2f15ce859 100644 --- a/apps/server/src/peer/PeerLinks.test.ts +++ b/apps/server/src/peer/PeerLinks.test.ts @@ -440,6 +440,38 @@ it.effect("cancelling a descriptor probe aborts its request and leaves later res ).pipe(Effect.provide(NodeHttpServer.layerTest)), ); +it.effect("one dropped descriptor request does not list a live peer as unreachable", () => + Effect.scoped( + Effect.gen(function* () { + const dropNext = yield* Ref.make(false); + const dropOnce = HttpRouter.middleware( + (httpEffect) => + Effect.gen(function* () { + const request = yield* HttpServerRequest.HttpServerRequest; + if ( + request.url.startsWith("/.well-known/t3/environment") && + (yield* Ref.getAndSet(dropNext, false)) + ) { + return HttpServerResponse.empty({ status: 503 }); + } + return yield* httpEffect; + }), + { global: true }, + ); + const b = yield* servePeer( + box, + Layer.merge(McpHttpServer.toolkitRegistration(ProbeToolkit, probeHandlers), dropOnce), + ); + const a = yield* makeA(laptop); + const pairing = yield* b.auth.issuePairingCredential(); + yield* a.links.link({ url: b.url, pairingCode: pairing.credential, access: "auto" }); + + yield* Ref.set(dropNext, true); + expect((yield* a.links.list).map((link) => link.status)).toEqual(["reachable"]); + }), + ).pipe(Effect.provide(NodeHttpServer.layerTest)), +); + it.effect("reads one stored link without asking the peer", () => Effect.scoped( Effect.gen(function* () { From 93717a970a96c48fc5362b078954954e1ce939a8 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 11 Nov 2026 06:33:10 +0000 Subject: [PATCH 2/3] fix(server): one dropped probe no longer lists a live linked peer as unreachable A linked environment's status came from a single descriptor request per stored address, so one reset connection or brief 503 showed a healthy link as 'Not answering right now.' until Settings > Connections was reopened. Retry a failed descriptor request once inside the existing probe timeout. Certificate rejections are not retried, and an unreachable peer still reports within the same timeout. Fixes pingdotgg/t3code#34195 AI-assisted; verified with the tests and tools listed above. --- apps/server/src/peer/PeerLinks.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/server/src/peer/PeerLinks.ts b/apps/server/src/peer/PeerLinks.ts index f5796d7a6994..0fd6cb7aba2f 100644 --- a/apps/server/src/peer/PeerLinks.ts +++ b/apps/server/src/peer/PeerLinks.ts @@ -222,6 +222,8 @@ const make = Effect.gen(function* () { const describe = (origin: string) => clientFor(origin).pipe( Effect.flatMap((client) => client.metadata.descriptor()), + // One dropped connection must not list a live peer as down; the timeout still bounds both tries. + Effect.retry({ times: 1, while: (error) => certificateErrorCode(error) === undefined }), Effect.timeout(probeTimeout(origin)), Effect.asSome, Effect.catch((error) => { From 7e69f920f250363c323b9b6668d3312ec6721ad7 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 11 Nov 2026 06:48:48 +0000 Subject: [PATCH 3/3] fix(server): retry a failed peer probe after a short pause An immediate retry lands inside the same brief network drop, so a live linked peer still listed as unreachable. Space the retry by one second, still bounded by the probe timeout. AI-assisted; verified with the tests and tools listed above. --- apps/server/src/peer/PeerLinks.test.ts | 2 +- apps/server/src/peer/PeerLinks.ts | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/server/src/peer/PeerLinks.test.ts b/apps/server/src/peer/PeerLinks.test.ts index 7ef2f15ce859..ecd4b9ac86ca 100644 --- a/apps/server/src/peer/PeerLinks.test.ts +++ b/apps/server/src/peer/PeerLinks.test.ts @@ -440,7 +440,7 @@ it.effect("cancelling a descriptor probe aborts its request and leaves later res ).pipe(Effect.provide(NodeHttpServer.layerTest)), ); -it.effect("one dropped descriptor request does not list a live peer as unreachable", () => +it.live("one dropped descriptor request does not list a live peer as unreachable", () => Effect.scoped( Effect.gen(function* () { const dropNext = yield* Ref.make(false); diff --git a/apps/server/src/peer/PeerLinks.ts b/apps/server/src/peer/PeerLinks.ts index 0fd6cb7aba2f..b05cf0a0afa5 100644 --- a/apps/server/src/peer/PeerLinks.ts +++ b/apps/server/src/peer/PeerLinks.ts @@ -26,6 +26,7 @@ import * as Option from "effect/Option"; import * as Predicate from "effect/Predicate"; import * as Redacted from "effect/Redacted"; import * as Result from "effect/Result"; +import * as Schedule from "effect/Schedule"; import * as Schema from "effect/Schema"; import * as SqlClient from "effect/sql/SqlClient"; import * as FetchHttpClient from "effect/http/FetchHttpClient"; @@ -222,8 +223,12 @@ const make = Effect.gen(function* () { const describe = (origin: string) => clientFor(origin).pipe( Effect.flatMap((client) => client.metadata.descriptor()), - // One dropped connection must not list a live peer as down; the timeout still bounds both tries. - Effect.retry({ times: 1, while: (error) => certificateErrorCode(error) === undefined }), + // A brief network drop must not list a live peer as down; the timeout still bounds both tries. + Effect.retry({ + times: 1, + schedule: Schedule.spaced("1 second"), + while: (error) => certificateErrorCode(error) === undefined, + }), Effect.timeout(probeTimeout(origin)), Effect.asSome, Effect.catch((error) => {