sipeed/picoclaw · error

Failed to load config

Error message

Failed to load config

What it means

Thrown in the react-query queryFn of ConfigPage (web/frontend/src/components/config/config-page.tsx:126) when GET /api/config through launcherFetch resolves with a non-2xx status. launcherFetch sends same-origin credentials and redirects to /launcher-login on 401 JSON responses, so this message surfaces for other statuses (500, 404, 403) or a 401 received while already on an auth page. A separate 5-second AbortController timer races the fetch; an abort rejects with AbortError before this throw, so this error strictly means 'server answered with an error status'.

Source

Thrown at web/frontend/src/components/config/config-page.tsx:126

    useState<LauncherForm>(EMPTY_LAUNCHER_FORM)
  const [launcherBaseline, setLauncherBaseline] =
    useState<LauncherForm>(EMPTY_LAUNCHER_FORM)
  const [autoStartEnabled, setAutoStartEnabled] = useState(false)
  const [autoStartBaseline, setAutoStartBaseline] = useState(false)
  const [saving, setSaving] = useState(false)
  const [showFactoryResetDialog, setShowFactoryResetDialog] = useState(false)

  const { data, isLoading, error } = useQuery({
    queryKey: ["config"],
    queryFn: async () => {
      const controller = new AbortController()
      const timer = setTimeout(() => controller.abort(), 5000)
      try {
        const res = await launcherFetch("/api/config", {
          signal: controller.signal,
        })
        if (!res.ok) {
          throw new Error("Failed to load config")
        }
        return res.json()
      } finally {
        clearTimeout(timer)
      }
    },
  })

  const { data: launcherConfig, isLoading: isLauncherLoading } = useQuery({
    queryKey: ["system", "launcher-config"],
    queryFn: getLauncherConfig,
  })

  const { data: versionInfo } = useQuery({
    queryKey: ["system", "version"],
    queryFn: getSystemVersionInfo,
    staleTime: 5 * 60 * 1000,
  })

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Open the network tab and read the actual status and body of GET /api/config — the fix depends on the status code
  2. For 500: check launcher backend logs and validate the config file it loads (permissions, syntax)
  3. For 404/502 behind a proxy: fix proxy routing so /api/config reaches the launcher backend
  4. For 401: log in at /launcher-login, or retry after the backend finishes restarting

Example fix

// before
if (!res.ok) {
  throw new Error("Failed to load config")
}

// after
if (!res.ok) {
  const body = await res.text().catch(() => "")
  throw new Error(`Failed to load config (HTTP ${res.status})${body ? ": " + body : ""}`)
}
Defensive patterns

Strategy: retry

Validate before calling

async function pingConfig(): Promise<boolean> {
  try {
    const res = await launcherFetch("/api/config", { method: "HEAD" })
    return res.ok
  } catch {
    return false
  }
}

Try / catch

// react-query: let the query error state carry it; distinguish timeout from HTTP error
try {
  const res = await launcherFetch("/api/config", { signal: controller.signal })
  if (!res.ok) throw new Error(`Failed to load config (HTTP ${res.status})`)
} catch (err) {
  if (err instanceof DOMException && err.name === "AbortError") {
    // 5s timeout — treat as transient, allow retry
  }
  throw err
}

Prevention

When it happens

Trigger: Launcher backend returned 500 for /api/config (config file unreadable/corrupt), 404 when the frontend is served from a different base path, 403 from an auth-protected setup, or 401 while the page is the login/setup page itself (redirect suppressed by isLauncherAuthPath).

Common situations: Config file on disk has bad permissions or invalid TOML/JSON so the backend fails to serialize it; frontend deployed behind a reverse proxy that routes /api elsewhere; session cookie expired exactly while on the login page; backend restarting and returning 502/503 from the proxy.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/38a4a1e5d8e713cd. Report an issue: GitHub.