cloudflare / cloudflare/sandbox-sdk
tunnels.get() is not idempotent in wrangler dev: second call throws 'Tunnel recovery attempts were exhausted' on a healthy tunnel and never recovers
- Dominant language
- TypeScript
- Stars
- 1.1k
- Forks
- 114
- Avg merge
- 22h 42m
- Merged PRs (30d)
- 14
Description
### Versions
@cloudflare/sandbox 0.12.5, wrangler 4.123.0, `wrangler dev --local` (WSL2 + Docker Desktop)
### Describe the bug
`sandbox.tunnels.get(port)` is not idempotent under `wrangler dev --local`: after the first successful call establishes a healthy quick tunnel, a **second** `tunnels.get(port)` for the same port immediately throws:
```
OperationInterruptedError: Tunnel recovery attempts were exhausted
```
The tunnel itself is still healthy at that moment (the `*.trycloudflare.com` URL keeps serving 200s), yet every subsequent `tunnels.get` on that Durable Object keeps failing with the same error. The exhausted state never resets on its own — only destroying and recreating the sandbox (stop → start) clears it.
The same happens after any transient failure (e.g. the target port briefly not listening): recovery attempts get consumed and the counter never resets even after the origin becomes healthy, so the DO is permanently unable to return a tunnel URL.
### Expected behavior
`tunnels.get(port)` should be idempotent per port (return the existing healthy tunnel), and the recovery-attempt counter should reset after a successful connection — or at least be resettable without recycling the sandbox.
### Repro
```ts
const sandbox = getSandbox(env.Sandbox, 'repro')
await sandbox.startProcess('python3 -m http.server 8080', { processId: 'srv' })
const a = await sandbox.tunnels.get(8080) // ok, URL works
const b = await sandbox.tunnels.get(8080) // throws OperationInterruptedError
```
### Workaround
Cache the tunnel URL out-of-band (we write it to a file in the container) and only call `tunnels.get` when the dev-server process is (re)started; never call it twice per container lifetime.
Contributor guide
Assessment
This issue has not been assessed yet.