fix(connect): remove tunnels after hosts go offline - #8153
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This change adds end-to-end managed-tunnel recovery and automated Cloudflare tunnel deletion, including new signed relay APIs, persistence fields, startup behavior, and scheduled production cleanup. Its cross-service scope and external resource side effects warrant human review. You can add or adjust custom eligibility rules. Learn more. |
There was a problem hiding this comment.
Effect service conventions: four findings, all in newly added error-handling code (three Effect.catchTag usages and one startup Effect.catchCause). Details inline.
Posted via Macroscope — Effect Service Conventions
There was a problem hiding this comment.
One convention finding on the new retry predicate. Previously reported Effect.catchTag usages in ManagedEndpointReaper.ts, ManagedEndpointProvider.ts and the Effect.catchCause in server.ts are resolved in this head commit.
Posted via Macroscope — Effect Service Conventions
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a9386bf. Configure here.

Cloudflare tunnels stayed provisioned after an environment crashed or went offline. Environments linked through web or mobile could not recreate a deleted tunnel.
The relay now removes tunnels that stay disconnected for at least five minutes. Updated hosts recreate tunnels under the same URL, while older hosts remain protected until they register recovery support.
Verified with 146 focused tests, 129 server integration tests, and typechecks for the relay, server, shared client runtime, web app, and mobile app.
Built by GPT-5.6 Sol with the Codex harness.
Note
High Risk
Changes authentication-adjacent relay APIs, optimistic concurrency on tunnel allocations, and automatic tunnel lifecycle on startup—errors could leave environments unreachable or delete tunnels incorrectly.
Overview
Adds managed tunnel recovery so linked environments can replace a deleted or rejected Cloudflare tunnel without re-pairing, while the relay can retire unused tunnels after extended downtime.
On the T3 server, the cloud managed endpoint runtime now exposes a recovery request stream, a link-state lock, and logic to detect repeated tunnel authorization failures from
cloudflared(distinct from generic transport errors). Connector exit or sustained rejection enqueues recovery; startup registers recovery with the relay when possible and otherwise reconciles the CLI link, then listens for recovery events and calls the relay with the stored environment credential and signed proofs.Cloud HTTP adds
registerManagedCloudTunnelRecoveryandrecoverManagedCloudTunnel, maps relay 401/403/409 to permanent failures, and wraps relay config / unlink / reconcile paths in the link lock so persisted tunnel config cannot race recovery.On the relay, allocation rows gain
generation(replacingupdatedAtas the CAS token), plus recovery registration fields. Allocation mutations incrementgeneration, gate on tunnel id where needed, and addwithClaimedTunnel(FOR UPDATE) so tunnel delete/release cannot race a new provision.ManagedEndpointProvider.releasecan require expected tunnel id, status, and inactivity time; provisioning steps fail if ownership changes mid-flight.A Postgres migration adds
recovery_enabled_at,recovery_environment_public_key, andgenerationonrelay_managed_endpoint_allocations. User and internal docs describe automatic replacement after long offline periods and the same public hostname.Reviewed by Cursor Bugbot for commit 4831d73. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Remove offline managed tunnels and add recovery via
ManagedEndpointReaperManagedEndpointReaperservice that sweeps inactive and down managed tunnels past a 5-minute grace period, deletes orphans after revalidation, and callsprovider.releasewith expected tunnel preconditions for owned allocations. Wired into the*/5cron in worker.ts.ManagedEndpointAllocationsfromupdatedAt-based togeneration-based concurrency in ManagedEndpointAllocations.ts.recordTunnel,recordDns,markReady,claimRelease,claimDeprovision, andremoveClaimednow require and return a numericgeneration. NewwithClaimedTunnelprovides transactional row locking withFOR UPDATE. Migration addsgeneration,recovery_enabled_at, andrecovery_environment_public_keycolumns torelay_managed_endpoint_allocations./v1/environments/:id/tunnel/recoveryand/v1/environments/:id/tunnel), JWT proof verification,registerManagedCloudTunnelRecoveryandrecoverManagedCloudTunnelin http.ts, and server-layer recovery orchestration in server.ts that listens for runtimerecoveryRequestsand retries with backoff.CloudManagedEndpointRuntimein ManagedEndpointRuntime.ts now emits recovery requests on connector exit and after 4 unauthorized tunnel registration errors, annotates failed statuses with afailurediscriminator (unsupported-platform,not-installed,spawn-failed), and exposeswithLinkStateLockto serialize link-state mutations.ManagedEndpointProvider.Service.releasenow returnsEffect<boolean>instead ofEffect<void>— all in-tree callers and mocks are updated but out-of-tree consumers must adapt.ManagedEndpointAllocationgains a requiredgenerationfield; all mutating allocation calls must pass the current generation or the update is a no-op (returnsnull/false).Macroscope summarized 4831d73.