decolua/9router · info · Error
cancelled
Error message
cancelled
What it means
waitForHealth() polls a tunnel URL until it responds, bounded by HEALTH_CHECK.timeoutMs. Between polls it checks the caller-supplied cancelToken; if token.cancelled is true it throws 'cancelled' immediately instead of continuing to poll. This is a cooperative-cancellation signal, not a failure of the health check itself — some upstream code (enableTunnel) cancelled the wait.
Source
Thrown at src/lib/tunnel/cloudflare/healthCheck.js:24
let hostname;
try { hostname = new URL(url).hostname; } catch { return false; }
if (!await resolveDns(hostname, HEALTH_CHECK.dnsTimeoutMs)) return false;
try {
const res = await fetch(`${url}/api/health`, {
signal: AbortSignal.timeout(HEALTH_CHECK.fetchTimeoutMs),
});
return res.ok;
} catch {
return false;
}
}
export async function waitForHealth(url, cancelToken = { cancelled: false }) {
const start = Date.now();
while (Date.now() - start < HEALTH_CHECK.timeoutMs) {
if (cancelToken.cancelled) throw new Error("cancelled");
if (await probeUrlAlive(url)) return true;
await new Promise((r) => setTimeout(r, HEALTH_CHECK.intervalMs));
}
throw new Error(`Health check timeout after ${HEALTH_CHECK.timeoutMs}ms`);
}
View on GitHub (pinned to 90b52e06ff)
Solutions
- Treat 'cancelled' as an expected control-flow signal — catch it and stop silently rather than surfacing as an error.
- Ensure a fresh cancelToken ({ cancelled: false }) is created for every enableTunnel() call (the manager does this in enableTunnel).
- If you see it spuriously, check whether code shares one token object across enable/disable calls.
- Wait for the health check to complete before issuing a disable, or gate disable UI during startup.
Example fix
// before
await waitForHealth(url, sharedToken);
// after
const token = { cancelled: false }; // fresh token per attempt
try {
await waitForHealth(url, token);
} catch (e) {
if (e.message === "cancelled") return; // user cancelled — not an error
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Ensure a fresh token before waiting
const cancelToken = { cancelled: false };
if (cancelToken.cancelled) return; // don't even start Type guard
const isCancelError = (e) => e instanceof Error && e.message === "cancelled";
Try / catch
try {
await waitForHealth(url, token);
} catch (e) {
if (isCancelError(e)) return; // expected abort
throw e;
} Prevention
- Create a new { cancelled: false } token per enable attempt; never share tokens.
- Only flip cancelled=true from a single, well-defined disable path.
- Distinguish 'cancelled' from timeout errors in logging so cancels aren't mistaken for failures.
- Avoid issuing disable during the health-wait phase unless the user explicitly cancels.
When it happens
Trigger: User disables the tunnel or navigates away while enableTunnel() is still waiting for the cloudflared tunnel URL to become healthy; the manager sets cancelToken.cancelled = true and the next poll iteration throws. Also thrown if the same shared cancelToken object is reused across calls with cancelled already true.
Common situations: Clicking 'disable tunnel' in the dashboard during startup; a reconnect flow resetting the token mid-wait; or stale token reuse causing an immediate 'cancelled' even though no one cancelled this attempt.
Related errors
- Health check timeout after ${HEALTH_CHECK.timeoutMs}ms
- tunnel cancelled
- cancelled
- [Tunnel] direct URL not reachable yet, continuing via public
- Unsupported platform: ${platform}
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/15a0912bcce98a5d.
Report an issue: GitHub.