Skip to content

[Bug]: T3 Connect runs any cloudflared on PATH without a version check; Homebrew 2023.8.2 crash-loops on --output #13964

Description

@JorgeMenaDev

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

packages/contracts or packages/shared

Steps to reproduce

  1. On macOS arm64, have an old Homebrew cloudflared on PATH. Mine was 2023.8.2, installed in 2023 and never upgraded.
  2. Make sure T3's managed copy isn't installed yet (~/.t3/tools/cloudflared/2026.5.2/darwin-arm64/cloudflared absent).
  3. Enable T3 Connect on that Mac from the desktop app.
  4. Open the environment from the iOS app.

Expected behavior

T3 runs a cloudflared that supports the flags it passes, either its pinned managed copy or a PATH binary it has checked. If the tunnel can't stay up, the host and the phone say so.

Actual behavior

The phone shows Failed to connect. Reconnecting... Reason: Relay could not reach the environment endpoint (endpoint_request_failed) forever. My other Mac, on the same account, connects fine.

The relay error hides the real problem. RelayClient.resolve checks the override env var, then the managed path, then any cloudflared on PATH. It doesn't check the PATH binary's version, and it reports version: CLOUDFLARED_VERSION for it anyway:

const resolve: RelayClientShape["resolve"] = Effect.gen(function* () {
const config = yield* loadCloudflaredConfig;
if (Option.isSome(config.executableOverride)) {
return (yield* isExecutableFile(config.executableOverride.value))
? {
status: "available",
executablePath: config.executableOverride.value,
source: "override",
version: CLOUDFLARED_VERSION,
}
: { status: "missing", version: CLOUDFLARED_VERSION };
}
if (yield* isExecutableFile(managedPath)) {
return {
status: "available",
executablePath: managedPath,
source: "managed",
version: CLOUDFLARED_VERSION,
};
}
const pathExecutable = yield* resolvePathExecutable;
if (pathExecutable) {
return {
status: "available",
executablePath: pathExecutable,
source: "path",
version: CLOUDFLARED_VERSION,
};
}
return releaseAsset
? { status: "missing", version: CLOUDFLARED_VERSION }
: {
status: "unsupported",
platform,
arch,
version: CLOUDFLARED_VERSION,
};
});

Because a PATH binary counts as available, the managed copy never gets installed. The server then spawns it with --output default:

["tunnel", "--no-autoupdate", "--loglevel", "info", "--output", "default", "run"],

cloudflared 2023.8.2 doesn't have that flag, so it exits right away:

$ TUNNEL_TOKEN=x /opt/homebrew/bin/cloudflared tunnel --no-autoupdate --loglevel info --output default run
Incorrect Usage. flag provided but not defined: -output

The supervisor restarts it with backoff (1, 2, 4, 8, 16, 32 s, then every 60 s) and nothing registers. recoverManagedCloudTunnel gets Relay internal error: upstream_unavailable every 2 minutes. None of this reaches the UI. I found it by reading server.trace.ndjson and running the binary by hand.

Impact

Blocks work completely

Version or commit

T3 Code (Nightly) 0.0.43-nightly.20260926.2282; code refs at main @ de251fc

Environment

macOS 27.0 (Darwin 27.0.0) arm64, MacBook Pro. Homebrew cloudflared 2023.8.2 on PATH. T3 Connect iOS app.

Logs or stack traces

# server.trace.ndjson, "Relay client process started; waiting for tunnel connection" (same tunnelId each time)
16:26:03 pid 3103
16:26:03 pid 3538
16:26:04 pid 4264
16:26:06 pid 4910
16:26:10 pid 5205
16:26:18 pid 6151
16:26:34 pid 7166
16:27:06 pid 9145
16:28:06 pid 13702

# environment.cloud.recoverManagedCloudTunnel
16:28:03 Failure  EnvironmentHttpInternalServerError: T3 Connect: Relay internal error: upstream_unavailable.
16:30:03 Failure  EnvironmentHttpInternalServerError: T3 Connect: Relay internal error: upstream_unavailable.
16:32:03 Success  (after installing the managed 2026.5.2 copy, see workaround)

Relay trace ID from the phone: 00dd36630a221ea911bd41bf682473b5.

Workaround

Put T3's pinned binary at the managed path. resolve checks it before PATH, and the server picks it up on its next restart attempt, with no app restart:

DEST=~/.t3/tools/cloudflared/2026.5.2/darwin-arm64
curl -fsSL -o /tmp/cf.tgz https://github.com/cloudflare/cloudflared/releases/download/2026.5.2/cloudflared-darwin-arm64.tgz
shasum -a 256 /tmp/cf.tgz   # ba94054c9fd4297645093d59d51442e5e546d07bb0516120e694a13d5b216d38
tar -xzf /tmp/cf.tgz -C /tmp && mkdir -p "$DEST" && install -m 755 /tmp/cloudflared "$DEST/cloudflared"

brew upgrade cloudflared also works. It breaks again whenever T3 bumps CLOUDFLARED_VERSION, though, because the managed path changes and T3 falls back to PATH.

Possible fixes

  1. Prefer the managed copy. Install it when missing, even if PATH has a cloudflared, and keep PATH only as a fallback.
  2. Or run cloudflared --version on a PATH candidate and skip it below a minimum version. Report the real version, not CLOUDFLARED_VERSION.
  3. When the relay client crash-loops, show that in the connect status. The phone's endpoint_request_failed doesn't point to the host.

Related but different causes: #7447 (tunnel counted as running while unreachable), #8844 (same phone error on Windows, maybe the same PATH issue).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions