decolua/9router · error · Error

Restart failed

Error message

Restart failed

What it means

After patching a Headroom setting, TokenSaverClient restarts the local Headroom proxy via POST /api/headroom/restart. The route rejects with 400 when the configured headroomUrl is not loopback (EXTERNAL_PROXY — external proxies must be started outside 9Router) or when the proxy is NOT_INSTALLED, and 500 for spawn/process errors; the client surfaces data.error or the generic 'Restart failed'.

Source

Thrown at src/app/(dashboard)/dashboard/token-saver/TokenSaverClient.js:333

      variant: "danger",
      onConfirm: () => removeExtraConfirmed(extra),
    });
  }, [removeExtraConfirmed]);

  // Toggle an extra's active state (persist setting), then restart the proxy so
  // the new --code-aware / --disable-kompress flags take effect.
  const toggleExtraActive = useCallback(async (extra, value) => {
    setExtrasActionError("");
    if (extra === "code") setCodeAware(value);
    if (extra === "ml") setKompress(value);
    const key = extra === "code" ? "headroomCodeAware" : "headroomKompress";
    await patchSetting({ [key]: value });
    if (!headroomStatus.running) return;
    setRestartingProxy(true);
    try {
      const res = await fetch("/api/headroom/restart", { method: "POST" });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(data.error || "Restart failed");
      await refreshHeadroomStatus();
    } catch (e) {
      setExtrasActionError(e.message);
    } finally {
      setRestartingProxy(false);
    }
  }, [headroomStatus.running, refreshHeadroomStatus]);

  const handleCavemanLevel = (level) => {
    setCavemanLevel(level);
    patchSetting({ cavemanLevel: level });
  };

  const handlePonytailEnabled = (value) => {
    setPonytailEnabled(value);
    patchSetting({ ponytailEnabled: value });
  };

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Verify the Headroom proxy is installed for the current user; install it from the token-saver dashboard before restarting.
  2. Check the headroomUrl setting — only http://127.0.0.1 or http://localhost URLs can be restarted from here; start external proxies yourself.
  3. Confirm port 8787 (or the port in headroomUrl) is free and not blocked.
  4. Inspect the API response's error/code fields (e.g. NOT_INSTALLED, EXTERNAL_PROXY) rather than the generic message to target the fix.

Example fix

// before
if (!res.ok) throw new Error(data.error || "Restart failed");
// after
if (!res.ok) throw new Error(data.code === "EXTERNAL_PROXY" ? "External proxies cannot be restarted from the dashboard" : data.error || `Restart failed (HTTP ${res.status})`);
Defensive patterns

Strategy: validation

Validate before calling

const status = await fetch("/api/headroom/status").then((r) => r.json()).catch(() => null);
if (!status?.installed) throw new Error("Headroom not installed — restart unavailable");
if (status.url && !/^https?:\/\/(localhost|127\.0\.0\.1)/.test(status.url)) throw new Error("External proxy — restart it outside 9Router");

Type guard

const isLoopbackUrl = (u) => { try { const url = new URL(u); return ["http:", "https:"].includes(url.protocol) && ["localhost", "127.0.0.1", "::1"].includes(url.hostname); } catch { return false; } };

Try / catch

try {
  const res = await fetch("/api/headroom/restart", { method: "POST" });
  const data = await res.json().catch(() => ({}));
  if (!res.ok) {
    if (data.code === "EXTERNAL_PROXY") throw new Error("Start external proxies yourself — dashboard restart is loopback-only");
    if (data.code === "NOT_INSTALLED") throw new Error("Install Headroom first");
    throw new Error(data.error || `Restart failed (HTTP ${res.status})`);
  }
} catch (e) {
  showRestartError(e.message);
}

Prevention

When it happens

Trigger: POST /api/headroom/restart returns non-OK: 400 EXTERNAL_PROXY when settings.headroomUrl points at a non-loopback host, 400 NOT_INSTALLED when the Headroom binary/venv is missing, or 500 when restartHeadroomProxy fails to spawn the process on the parsed port (default 8787).

Common situations: User configured a remote/external Headroom URL then clicked restart; Headroom was never installed; port 8787 occupied or spawn failure; dashboard session stale after the server was updated.

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/4cd7f1c736dd0fc2. Report an issue: GitHub.