{"record":{"id":"a31f92c53a67c3f4","repo":"OpenHands/OpenHands","slug":"reading-plugin-files-is-only-available-on-a-local","errorCode":null,"errorMessage":"Reading plugin files is only available on a local backend.","messagePattern":"Reading plugin files is only available on a local backend\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/api/plugins-service.ts","lineNumber":132,"sourceCode":"    } catch {\n      return [];\n    }\n  }\n\n  /**\n   * Fetch one plugin file's content for the detail-modal viewer. `basePath` is\n   * the plugin directory reported by the agent-server (`path`/`install_path`)\n   * and `relativePath` a POSIX path from the plugin's `files` listing.\n   *\n   * Local backend only — plugin files live on the local agent-server's disk.\n   * Errors propagate so the caller can render a load-error state.\n   */\n  static async getPluginFileContent(\n    basePath: string,\n    relativePath: string,\n  ): Promise<PluginFileContent> {\n    if (getActiveBackend().backend.kind === \"cloud\") {\n      throw new Error(\n        \"Reading plugin files is only available on a local backend.\",\n      );\n    }\n\n    const buffer = await new FileClient(\n      getAgentServerClientOptions(),\n    ).downloadFile(`${basePath}/${relativePath}`);\n    if (isLikelyBinary(buffer)) {\n      return { kind: \"binary\", text: null };\n    }\n    return {\n      kind: \"text\",\n      text: new TextDecoder(\"utf-8\", { fatal: false }).decode(buffer),\n    };\n  }\n}\n\nexport default PluginsService;","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/OpenHands/OpenHands/blob/500b4c533e9393e44cb92894bcbb18438ef473b6/src/api/plugins-service.ts#L114-L150","documentation":"Thrown by PluginsService.getPluginFileContent() when the active backend is cloud. This method reads a single plugin file's content from the agent-server's local disk via FileClient.downloadFile(). Plugin files (source code, skills, config) only exist on the local agent-server's filesystem, so reading them is meaningless and would fail against a cloud backend. The guard surfaces this as a clear error rather than a confusing 404/network error from the cloud endpoint.","triggerScenarios":"Any call to PluginsService.getPluginFileContent(basePath, relativePath) when getActiveBackend().backend.kind === 'cloud'. Typically triggered when the plugin detail modal tries to render the file viewer while the user is on a cloud backend.","commonSituations":"The user opens a plugin's detail modal (which lists files and shows content) while connected to a Cloud backend, or the plugin file viewer component loads without checking the active backend kind.","solutions":["Switch to a local backend to view plugin file contents.","Gate the plugin file viewer / detail modal on backend.kind === 'local' and show a placeholder message for cloud.","If viewing plugin source is needed in cloud, link to the plugin's upstream git repository instead of reading local files."],"exampleFix":"// Gate the file viewer on local backend\nconst { backend } = getActiveBackend();\nif (backend.kind !== 'local') {\n  return <Placeholder>File viewing requires a local backend.</Placeholder>;\n}\n<FileViewer path={path} />","handlingStrategy":"validation","validationCode":"// Guard before calling getPluginFileContent\nimport { getActiveBackend } from '#/api/backend-registry/active-store';\n\nif (getActiveBackend().backend.kind !== 'local') {\n  // Show a placeholder instead of fetching file content\n  return { kind: 'text', text: 'File viewing requires a local backend.' };\n}","typeGuard":"function isLocalActiveBackend(): boolean {\n  return getActiveBackend().backend.kind === 'local';\n}","tryCatchPattern":"try {\n  const content = await PluginsService.getPluginFileContent(basePath, relativePath);\n} catch (error) {\n  if (error instanceof Error && error.message.includes('only available on a local backend')) {\n    setFileViewMode('unsupported');\n  } else {\n    throw error;\n  }\n}","preventionTips":["Gate the plugin detail modal's file viewer on backend.kind === 'local'.","Link to the plugin's upstream git repo for cloud users who want to browse files.","Keep the file viewer component lazy-loaded so it does not mount unnecessarily on cloud."],"tags":["plugins","cloud","local-backend","backend-guard","file-viewer"],"backgroundTag":null,"analyzedSha":"500b4c533e9393e44cb92894bcbb18438ef473b6","analyzedAt":"2026-08-12T10:07:46.034Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}