{"record":{"id":"7f53827fbeb0ea63","repo":"OpenHands/OpenHands","slug":"installing-plugins-is-only-available-on-a-local-ba","errorCode":null,"errorMessage":"Installing plugins is only available on a local backend.","messagePattern":"Installing plugins is only available on a local backend\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/api/plugins-management-service.ts","lineNumber":96,"sourceCode":"    if (isCloudBackend()) {\n      return [];\n    }\n\n    try {\n      const response = await getManagementClient().listInstalledPlugins();\n      return response.plugins ?? [];\n    } catch {\n      // Agent-server may predate the plugins router or be unreachable; surface\n      // an empty list rather than throwing (mirrors the catalog service).\n      return [];\n    }\n  }\n\n  static async installPlugin(\n    request: InstallPluginRequest,\n  ): Promise<InstalledPluginInfo> {\n    if (isCloudBackend()) {\n      throw new Error(\n        \"Installing plugins is only available on a local backend.\",\n      );\n    }\n    return getManagementClient().installPlugin(request);\n  }\n\n  static async setPluginEnabled(\n    name: string,\n    enabled: boolean,\n  ): Promise<{ name: string; enabled: boolean }> {\n    if (isCloudBackend()) {\n      throw new Error(\n        \"Enabling and disabling plugins is only available on a local backend.\",\n      );\n    }\n    return getManagementClient().setPluginEnabled(name, enabled);\n  }\n","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/OpenHands/OpenHands/blob/500b4c533e9393e44cb92894bcbb18438ef473b6/src/api/plugins-management-service.ts#L78-L114","documentation":"Thrown by PluginsManagementService.installPlugin() when the active backend is cloud. Plugin installation writes to the agent-server's local filesystem (cloning a git repo or copying a local path into the plugins directory), which only exists on a local agent-server. The guard is a hard block — there is no cloud fallback path for plugin installation.","triggerScenarios":"Any call to PluginsManagementService.installPlugin(request) while getActiveBackend().backend.kind === 'cloud'. This typically happens when the UI does not gate the 'Install' button behind a local-backend check, or when a user switches to a cloud backend mid-session and then tries to install a plugin from the Plugins page.","commonSituations":"The user is connected to a Cloud backend and navigates to the Plugins page, then clicks Install. Or a stale React component calls the mutation without re-checking the active backend kind after a backend switch.","solutions":["Switch to a local backend before installing plugins (the Plugins page should gate the install action on backend kind === 'local').","If the UI is not gating the button, ensure the component reads getActiveBackend() or useActiveBackendKind() and disables/hides the install action for cloud.","Do not attempt to install plugins through a cloud runtime sandbox — plugin installation is not supported there."],"exampleFix":"// Gate the install button on local backend\nconst { backend } = getActiveBackend();\n<Button isDisabled={backend.kind !== 'local'} onClick={install}>\n  Install\n</Button>","handlingStrategy":"validation","validationCode":"// Guard before calling installPlugin\nimport { getActiveBackend } from '#/api/backend-registry/active-store';\n\nfunction canManagePlugins(): boolean {\n  return getActiveBackend().backend.kind === 'local';\n}\n\nif (!canManagePlugins()) {\n  toast.error('Switch to a local backend to install plugins.');\n  return;\n}","typeGuard":"function isLocalActiveBackend(): boolean {\n  return getActiveBackend().backend.kind === 'local';\n}","tryCatchPattern":"try {\n  await PluginsManagementService.installPlugin(request);\n} catch (error) {\n  if (error instanceof Error && error.message.includes('only available on a local backend')) {\n    showBackendSwitchPrompt();\n  } else {\n    throw error;\n  }\n}","preventionTips":["Gate all plugin management UI buttons on getActiveBackend().backend.kind === 'local'.","Disable the Plugins management actions in the UI when a cloud backend is active.","If plugin support for cloud is needed in the future, add a server-side management path rather than calling the local-only client."],"tags":["plugins","cloud","local-backend","backend-guard"],"backgroundTag":null,"analyzedSha":"500b4c533e9393e44cb92894bcbb18438ef473b6","analyzedAt":"2026-08-12T10:07:46.034Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}