{"record":{"id":"68854f567422a110","repo":"Hmbown/CodeWhale","slug":"restore-points-require-the-runtime-bearer-token","errorCode":null,"errorMessage":"Restore points require the runtime bearer token.","messagePattern":"Restore points require the runtime bearer token\\.","errorType":"exception","errorClass":"Error","httpStatus":401,"severity":"error","filePath":"extensions/vscode/src/runtime.ts","lineNumber":133,"sourceCode":"  if (response.statusCode === 401) {\n    throw new Error(\"Thread summaries require the runtime bearer token.\");\n  }\n  if (response.statusCode !== 200) {\n    throw new Error(`Thread summary returned HTTP ${response.statusCode}.`);\n  }\n\n  return readThreadSummaries(response.body);\n}\n\nexport async function listSnapshots(config: RuntimeConfig, limit = 8): Promise<SnapshotEntry[]> {\n  const baseUrl = runtimeBaseUrl(config);\n  const response = await requestJson(\n    `${baseUrl}/v1/snapshots?limit=${encodeURIComponent(String(limit))}`,\n    config.token,\n  );\n\n  if (response.statusCode === 401) {\n    throw new Error(\"Restore points require the runtime bearer token.\");\n  }\n  if (response.statusCode !== 200) {\n    throw new Error(`Restore points returned HTTP ${response.statusCode}.`);\n  }\n\n  return readSnapshots(response.body);\n}\n\nexport function startRuntimeTerminal(config: RuntimeConfig): vscode.Terminal {\n  const terminal = vscode.window.createTerminal(\"CodeWhale Runtime\");\n  const args = [\n    \"serve\",\n    \"--http\",\n    \"--host\",\n    shellQuote(config.host),\n    \"--port\",\n    String(config.port),\n  ];","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/extensions/vscode/src/runtime.ts#L115-L151","documentation":"stage_local_copy validates the source directory before copying anything into staging: resolve_manifest_path must find plugin.json, kimi.plugin.json, or plugin.toml directly at the source root. This also rejects symlinked roots/manifests. The error means the directory you passed simply has no supported manifest at its top level, so it is not recognizable as a plugin bundle.","triggerScenarios":"Pointing the local-install API at a repository checkout root where the plugin lives in a subdirectory; pointing at the parent of the bundle; pointing at a file instead of a directory; a bundle shipping only an unsupported manifest filename.","commonSituations":"git clone of a plugin monorepo and installing the repo root instead of plugins/<name>; unzipping an archive that adds a wrapper folder (my-plugin-main/...) and passing the extraction dir; bundles that renamed their manifest (e.g. manifest.json) which is not in the supported set.","solutions":["cd into the extracted/cloned tree and locate the directory that directly contains plugin.json (or kimi.plugin.json/plugin.toml); install that directory.","If the archive added a wrapper folder, pass <extract-dir>/<wrapper>/ as the source.","If you maintain the bundle, name the manifest one of the three supported files and place it at the bundle root."],"exampleFix":"# before\nplugin install ./my-plugin-main          # wrapper folder, no manifest at root\n\n# after\nplugin install ./my-plugin-main/my-plugin  # directory holding plugin.json","handlingStrategy":"validation","validationCode":"if crate::plugins::agent_plugin::resolve_manifest_path(source).is_none() {\n    anyhow::bail!(\"{} has no plugin.json/kimi.plugin.json/plugin.toml at its root — pass the bundle directory itself\", source.display());\n}\nstage_local_copy(source, user_plugins_dir, max_size)?;","typeGuard":"fn is_plugin_bundle(dir: &Path) -> bool {\n    crate::plugins::agent_plugin::resolve_manifest_path(dir).is_some()\n}","tryCatchPattern":"match stage_local_copy(source, plugins_dir, max_size) {\n    Ok(staged) => staged,\n    Err(e) if e.to_string().contains(\"no plugin.json, kimi.plugin.json, or plugin.toml\") => {\n        // search one level down for the actual bundle root and retry\n        let child = std::fs::read_dir(source)?.find_map(|d| {\n            let d = d.ok()?.path();\n            is_plugin_bundle(&d).then_some(d)\n        }).context(\"no nested bundle found\")?;\n        stage_local_copy(&child, plugins_dir, max_size)?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Always pass the directory that directly contains the manifest, not a checkout or archive root.","After cloning/unzipping, ls for plugin.json before installing.","Name the manifest one of the three supported filenames at the bundle root."],"tags":["rust","plugins","install","manifest","validation"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}