diff --git a/apps/server/src/cloud/ManagedEndpointRuntime.test.ts b/apps/server/src/cloud/ManagedEndpointRuntime.test.ts index 2916c8f96538..73bffa1f4f7b 100644 --- a/apps/server/src/cloud/ManagedEndpointRuntime.test.ts +++ b/apps/server/src/cloud/ManagedEndpointRuntime.test.ts @@ -912,6 +912,36 @@ describe("CloudManagedEndpointRuntime", () => { }), ); + it.effect("starts the connector without the host's GODEBUG setting", () => + Effect.gen(function* () { + const spawned: Array = []; + const spawner = ChildProcessSpawner.make((command) => + Effect.gen(function* () { + if (!ChildProcess.isStandardCommand(command)) { + return yield* Effect.die("Expected standard command."); + } + spawned.push(command); + const handle = makeHandle({ pid: 700, onKill: () => {} }); + yield* Effect.addFinalizer(() => handle.kill().pipe(Effect.ignore)); + return handle; + }), + ); + const runtime = yield* buildCloudManagedEndpointRuntime( + spawner, + layerRelayClientAvailable, + unusedHttpClient, + emptySecretStoreLayer, + { GODEBUG: "tlskyber=0" }, + ); + yield* runtime.applyConfig(tunnelConfig); + + const { env, extendEnv } = spawned[0]!.options; + expect(extendEnv).not.toBe(true); + expect(env?.GODEBUG).toBeUndefined(); + expect(env?.TUNNEL_TOKEN).toBe(tunnelConfig.connectorToken); + }), + ); + it.effect("supervises the active connector and restarts it after process exit", () => Effect.gen(function* () { const spawned: Array = []; diff --git a/apps/server/src/cloud/ManagedEndpointRuntime.ts b/apps/server/src/cloud/ManagedEndpointRuntime.ts index 6e86f87ffef9..d538703a3526 100644 --- a/apps/server/src/cloud/ManagedEndpointRuntime.ts +++ b/apps/server/src/cloud/ManagedEndpointRuntime.ts @@ -763,6 +763,8 @@ export const make = Effect.gen(function* () { detached: false, env: { ...env, + // Go >= 1.24 aborts at startup when a removed GODEBUG setting (e.g. tlskyber=0) is inherited. + GODEBUG: undefined, TUNNEL_TOKEN: config.connectorToken, }, shell: false, diff --git a/packages/shared/src/relayClient.test.ts b/packages/shared/src/relayClient.test.ts index 5319fe559637..365501b9be53 100644 --- a/packages/shared/src/relayClient.test.ts +++ b/packages/shared/src/relayClient.test.ts @@ -471,6 +471,58 @@ describe("RelayClient", () => { ), ); + it.effect.skipIf(windowsHost)( + "installs when the host environment sets a GODEBUG value Go has removed", + () => { + const hostEnv = { PATH: "", GODEBUG: "tlskyber=0" }; + const goRuntimeSpawner = ChildProcessSpawner.make((command) => + Effect.sync(() => { + if (!ChildProcess.isStandardCommand(command)) return makeHandle(); + // HostProcess.Environment stands in for the process.env a child inherits. + const { env, extendEnv } = command.options; + const childEnv = env === undefined || extendEnv ? { ...hostEnv, ...env } : env; + // Go >= 1.24 exits 2 before main when a removed GODEBUG setting has its old value. + if (childEnv.GODEBUG === "tlskyber=0") return makeHandle(2); + return command.args[0] === "version" + ? makeHandle( + 0, + `cloudflared version ${RelayClient.CLOUDFLARED_VERSION} (built 2026-01-01-00:00 UTC)\n`, + ) + : makeHandle(); + }), + ); + return Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const baseDir = yield* fileSystem.makeTempDirectoryScoped({ + prefix: "t3-cloudflared-test-", + }); + const manager = yield* RelayClient.makeCloudflaredRelayClient({ + baseDir, + releaseAsset: testReleaseAsset, + }); + + const installed = yield* manager.installWithProgress(() => Effect.void); + expect(installed).toEqual({ + status: "available", + executablePath: managedPathFor(baseDir, RelayClient.CLOUDFLARED_VERSION), + source: "managed", + version: RelayClient.CLOUDFLARED_VERSION, + }); + }).pipe( + Effect.scoped, + Effect.provide( + Layer.mergeAll( + NodeServices.layer, + NodeCrypto.layer, + layerHttpClient(testBinary), + Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, goRuntimeSpawner), + layerHostRuntime(hostEnv), + ), + ), + ); + }, + ); + it.effect("rejects downloads whose checksum does not match the pinned manifest", () => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; diff --git a/packages/shared/src/relayClient.ts b/packages/shared/src/relayClient.ts index ae48676e6e17..4ecefe32bdce 100644 --- a/packages/shared/src/relayClient.ts +++ b/packages/shared/src/relayClient.ts @@ -304,7 +304,13 @@ export const makeCloudflaredRelayClient = Effect.fn("cloudflared.make")(function (key: VersionProbeKey) => spawner .string( - ChildProcess.make(key.executablePath, ["version"], { stdin: "ignore", stderr: "ignore" }), + ChildProcess.make(key.executablePath, ["version"], { + stdin: "ignore", + stderr: "ignore", + // Go >= 1.24 aborts at startup when a removed GODEBUG setting (e.g. tlskyber=0) is inherited. + env: { GODEBUG: undefined }, + extendEnv: true, + }), ) .pipe( Effect.map(parseCloudflaredVersionOutput),