{"record":{"id":"9573dada10529a0d","repo":"OpenHands/OpenHands","slug":"no-backend-is-configured-9573da","errorCode":null,"errorMessage":"No backend is configured.","messagePattern":"No backend is configured\\.","errorType":"exception","errorClass":"NoBackendAvailableError","httpStatus":null,"severity":"error","filePath":"src/api/automation-service/automation-service.api.ts","lineNumber":92,"sourceCode":"  const requestHeaders = await buildAutomationRequestHeaders();\n  Object.entries(requestHeaders).forEach(([name, value]) => {\n    config.headers.set(name, value);\n  });\n\n  // Import uses an explicit baseURL/header pair so its POST, PATCH, and\n  // cleanup stay pinned to the backend selected when the mutation started.\n  if (config.baseURL) return config;\n\n  // Resolve the local backend on every call so it tracks the\n  // currently-active local backend (and any host/key edits made via the\n  // manage-backends UI), rather than freezing whatever value the\n  // agent-server-config produced at module load time.\n  // Using the backend registry (rather than the build-time VITE_SESSION_API_KEY\n  // env var) ensures the published npm package picks up the runtime-injected\n  // session key that scripts/static-server.mjs seeds into localStorage, fixing\n  // the 401 errors reported in issue #829.\n  const backend = getEffectiveLocalBackend();\n  if (!backend) throw new NoBackendAvailableError();\n  // eslint-disable-next-line no-param-reassign\n  config.baseURL = backend.host;\n\n  const apiKey = backend.apiKey?.trim();\n  if (apiKey) {\n    config.headers.set(\"X-Session-API-Key\", apiKey);\n  }\n  return config;\n});\n\nfunction normalizeAutomationSdkVersion(version: unknown): string | null {\n  if (typeof version !== \"string\") return null;\n  const trimmed = version.trim();\n  return trimmed || null;\n}\n\nfunction getAutomationSdkVersionFromResponse(\n  response: AutomationSdkVersionResponse,","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/OpenHands/OpenHands/blob/500b4c533e9393e44cb92894bcbb18438ef473b6/src/api/automation-service/automation-service.api.ts#L74-L110","documentation":"NoBackendAvailableError thrown inside the localAutomationAxios request interceptor when getEffectiveLocalBackend() returns null. This interceptor runs on every automation-service axios call that doesn't have an explicit baseURL pinned (import/cleanup calls set baseURL explicitly and skip this path). It resolves the host and session key from the backend registry on each request so edits in the Manage Backends UI are picked up dynamically.","triggerScenarios":"Any AutomationService static method that uses localAutomationAxios without a pinned baseURL (listAutomations, getAutomation, updateAutomation, deleteAutomation, etc.) is invoked when no local backend is registered. The interceptor fires before the HTTP request leaves the browser.","commonSituations":"Automation service calls issued before the backend registry is seeded during app bootstrap; user switched to a cloud backend and the automation UI still triggers a local-path call; the backend was deleted between the automation list loading and a subsequent action.","solutions":["Ensure a local backend is registered before interacting with the Automation tab.","Guard automation UI components with the active backend kind check — only show automation controls when getActiveBackend().backend.kind === 'local'.","If automation calls must work with cloud, they already route through callCloudProxy; verify the code path isn't falling through to localAutomationAxios due to a missing kind check."],"exampleFix":"// before: automation call without backend guard\nconst automations = await AutomationService.listAutomations();\n\n// after: guard on backend kind\nif (getActiveBackend().backend.kind !== 'local') return;\nconst automations = await AutomationService.listAutomations();","handlingStrategy":"validation","validationCode":"import { getActiveBackend } from '#/api/backend-registry/active-store';\n\nfunction canCallLocalAutomation(): boolean {\n  return getActiveBackend().backend.kind === 'local';\n}\n\n// Before automation API calls:\nif (!canCallLocalAutomation()) return;","typeGuard":"import { isNoBackendAvailableError } from '#/api/agent-server-client-options';\n\nfunction isAutomationNoBackend(e: unknown): boolean {\n  return isNoBackendAvailableError(e);\n}","tryCatchPattern":"try {\n  const data = await AutomationService.listAutomations();\n} catch (error) {\n  if (isNoBackendAvailableError(error)) {\n    // Backend registry empty or cloud-only; hide automation UI\n    return [];\n  }\n  throw error;\n}","preventionTips":["Gate the Automation tab on getActiveBackend().backend.kind === 'local'.","Check backend availability before automation mutations, not just queries.","Handle the interceptor-level error the same way as getAgentServerClientOptions errors."],"tags":["backend-registry","automation","axios-interceptor","configuration"],"backgroundTag":null,"analyzedSha":"500b4c533e9393e44cb92894bcbb18438ef473b6","analyzedAt":"2026-08-12T10:07:46.034Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}