decolua/9router · warning
[Tunnel] kill warn: ${e.message}
Error message
[Tunnel] kill warn: ${e.message} What it means
disableTunnel tries to kill the active cloudflared process via killCloudflared(activeLocalPort). If that throws (process already gone, permission denied, missing pid info, signal error), it logs '[Tunnel] kill warn: <message>' and continues teardown: clears the pid file, clears state, and marks the tunnel disabled in settings. The kill failure alone does not fail the disable operation.
Source
Thrown at src/lib/tunnel/cloudflare/manager.js:119
return { success: true, tunnelUrl, shortId, publicUrl };
} catch (e) {
// Suppress noise when spawn was deliberately killed (restart/disable superseded it)
if (!/cloudflared killed|tunnel cancelled/.test(e.message)) {
console.error(`[Tunnel] enable error: ${e.message}`);
}
throw e;
} finally {
svc.spawnInProgress = false;
}
}
export async function disableTunnel() {
console.log("[Tunnel] disable");
// Abort any in-flight enable so it cannot resurrect state after we clear it
svc.cancelToken.cancelled = true;
setUnexpectedExitHandler(null);
try { killCloudflared(svc.activeLocalPort); } catch (e) { console.warn(`[Tunnel] kill warn: ${e.message}`); }
clearPid();
const state = loadState();
if (state) saveState({ shortId: state.shortId, tunnelUrl: null });
await updateSettings({ tunnelEnabled: false, tunnelUrl: "" });
// Force-clear flags so a subsequent enable is not blocked by a stuck spawnInProgress
svc.spawnInProgress = false;
svc.activeLocalPort = null;
return { success: true };
}
export async function getTunnelStatus() {
const settings = await getSettings();
const settingsEnabled = settings.tunnelEnabled === true;
const state = loadState();
const shortId = state?.shortId || "";
const publicUrl = shortId ? `https://r${shortId}.abc-tunnel.us` : "";View on GitHub (pinned to 90b52e06ff)
Solutions
- Ignore if state shows tunnel disabled — disable succeeded and the process was likely already dead.
- Check for orphaned cloudflared processes manually (ps aux | grep cloudflared) and kill them.
- Clear the stale pid/state files in the data dir and re-run disableTunnel.
- Fix permission context (run disable as the same user that spawned cloudflared).
Defensive patterns
Strategy: try-catch
Validate before calling
// check for a live cloudflared before/after disable
import { execSync } from "child_process";
const alive = (() => { try { return execSync("pgrep -f cloudflared", { stdio: "pipe" }).toString().trim(); } catch { return ""; } })(); Try / catch
// the library already swallows the kill error; mirror that in userland
try {
await disableTunnel();
} catch (e) {
console.warn("disable failed:", e.message);
} Prevention
- Call disableTunnel only once; guard against double-click/race.
- After unexpected cloudflared exits, clean up orphan processes before re-enabling.
- Keep the pid/state files in a user-writable data dir.
- Run enable and disable under the same user account.
When it happens
Trigger: killCloudflared throws while disabling: cloudflared already exited, pid file points at a dead/foreign PID (EPERM), the process is owned by another user, or the platform-specific kill command is unavailable.
Common situations: Double-disable races; cloudflared crashed earlier (so kill has nothing to kill); Docker/containers restricting signals; Windows process-permission issues; stale pid file after a reboot with PID reuse.
Related errors
- Unsupported platform: ${platform}
- cancelled
- Health check timeout after ${HEALTH_CHECK.timeoutMs}ms
- tunnel cancelled
- [Tunnel] cloudflared exited unexpectedly, scheduling respawn
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/1a2a3cec344c1f9a.
Report an issue: GitHub.