-
+
{tool.image ? (
{ e.target.style.display = "none"; }} />
@@ -28,11 +28,10 @@ export default function ToolSummaryCard({ toolId, tool, status }) {
{tool.name}
- {s.label}
+ {s.label}
chevron_right
-
{tool.description}
diff --git a/src/lib/tunnel/cloudflare/cloudflared.js b/src/lib/tunnel/cloudflare/cloudflared.js
index 664c75ad..38e4bf74 100644
--- a/src/lib/tunnel/cloudflare/cloudflared.js
+++ b/src/lib/tunnel/cloudflare/cloudflared.js
@@ -374,6 +374,14 @@ export async function spawnQuickTunnel(localPort, onUrlUpdate) {
child.on("exit", (code, signal) => {
cloudflaredProcess = null;
clearPid();
+ // Deliberate kill (restart/disable) — exit silently, no error noise
+ if (intentionalKill) {
+ intentionalKill = false;
+ clearTimeout(timeout);
+ cleanup();
+ if (!resolved) { resolved = true; reject(new Error("cloudflared killed")); }
+ return;
+ }
console.log(`[Tunnel] cloudflared exit code=${code} signal=${signal}`);
if (!resolved) {
resolved = true;
@@ -389,7 +397,6 @@ export async function spawnQuickTunnel(localPort, onUrlUpdate) {
}
return;
}
- if (intentionalKill) { intentionalKill = false; cleanup(); return; }
if (unexpectedExitHandler) unexpectedExitHandler();
cleanup();
});
diff --git a/src/lib/tunnel/cloudflare/manager.js b/src/lib/tunnel/cloudflare/manager.js
index 44f66ae3..e15b47b2 100644
--- a/src/lib/tunnel/cloudflare/manager.js
+++ b/src/lib/tunnel/cloudflare/manager.js
@@ -100,7 +100,10 @@ export async function enableTunnel(localPort = 20128) {
console.log("[Tunnel] enable success");
return { success: true, tunnelUrl, shortId, publicUrl };
} catch (e) {
- console.error(`[Tunnel] enable error: ${e.message}`);
+ // 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;
diff --git a/src/lib/tunnel/index.js b/src/lib/tunnel/index.js
index 19459e17..a539d49c 100644
--- a/src/lib/tunnel/index.js
+++ b/src/lib/tunnel/index.js
@@ -27,6 +27,7 @@ export {
export {
isTailscaleInstalled,
isTailscaleRunning,
+ isTailscaleRunningStrict,
isTailscaleLoggedIn,
installTailscale,
startLogin,
diff --git a/src/shared/services/initializeApp.js b/src/shared/services/initializeApp.js
index 1fe8e26e..d6ec300f 100644
--- a/src/shared/services/initializeApp.js
+++ b/src/shared/services/initializeApp.js
@@ -8,7 +8,7 @@ import {
isTunnelManuallyDisabled, isTunnelReconnecting, isTailscaleReconnecting,
getTunnelService, getTailscaleService, setTunnelUnexpectedExitCallback,
killCloudflared, isCloudflaredRunning, ensureCloudflared,
- isTailscaleRunning,
+ isTailscaleRunning, isTailscaleRunningStrict,
loadState,
checkInternet,
probeCloudflareAlive, probeTailscaleAlive,
@@ -174,7 +174,9 @@ async function safeRestartTunnel(reason) {
svc.lastRestartAt = Date.now();
console.log("[Tunnel] restart success");
} catch (err) {
- console.log("[Tunnel] restart failed:", err.message);
+ if (!/cloudflared killed|tunnel cancelled/.test(err.message)) {
+ console.log("[Tunnel] restart failed:", err.message);
+ }
}
}
@@ -185,8 +187,10 @@ async function safeRestartTailscale(reason) {
if (svc.cancelToken.cancelled) return;
if (svc.spawnInProgress) return;
- // Tailscale daemon is OS-level with built-in reconnect; trust it when running
- if (isTailscaleRunning()) return;
+ // Tailscale daemon is OS-level with built-in reconnect; trust it when running.
+ // Startup uses strict probe — cached state is cold after process/dev reload.
+ const running = reason === "startup" ? isTailscaleRunningStrict() : isTailscaleRunning();
+ if (running) return;
const force = FORCE_RESTART_REASONS.test(reason);
if (!force && Date.now() - svc.lastRestartAt < RESTART_COOLDOWN_MS) {