{"record":{"id":"0e7847ebfe074667","repo":"jackwener/OpenCLI","slug":"unable-to-determine-remote-source-for-plugin-at","errorCode":null,"errorMessage":"Unable to determine remote source for plugin at ${dir}","messagePattern":"Unable to determine remote source for plugin at (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/plugin.ts","lineNumber":257,"sourceCode":"    throw new PluginError(`Failed to clone plugin: ${getErrorMessage(err)}`, 'Check the repository URL and your network connection.');\n  }\n\n  return tmpCloneDir;\n}\n\nfunction withTempClone<T>(cloneUrl: string, work: (cloneDir: string) => T): T {\n  const tmpCloneDir = cloneRepoToTemp(cloneUrl);\n  try {\n    return work(tmpCloneDir);\n  } finally {\n    try { fs.rmSync(tmpCloneDir, { recursive: true, force: true }); } catch {}\n  }\n}\n\nfunction resolveRemotePluginSource(lockEntry: LockEntry | undefined, dir: string): string {\n  const source = resolvePluginSource(lockEntry, dir);\n  if (!source || source.kind === 'local') {\n    throw new Error(`Unable to determine remote source for plugin at ${dir}`);\n  }\n  return source.url;\n}\n\nfunction pathExistsSync(p: string): boolean {\n  try {\n    fs.lstatSync(p);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\nfunction resolveRepoContainedPath(repoRoot: string, subPath: string): string {\n  const resolved = path.resolve(repoRoot, subPath);\n  if (!resolved.startsWith(repoRoot + path.sep) && resolved !== repoRoot) {\n    throw new PluginError(`Plugin path \"${subPath}\" escapes repo root.`);\n  }","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/src/plugin.ts#L239-L275","documentation":"resolveRemotePluginSource determines the remote git URL for an installed plugin from its lock entry or the plugin dir's git remote origin config. It throws a plain Error when the resolved source is missing or is of kind 'local', because a remote update (clone) cannot be performed for a locally-symlinked plugin. This guards updatePlugin's non-local branch.","triggerScenarios":"updatePlugin(name) on a plugin whose lock entry in ~/.opencli/plugins.lock.json is absent/corrupt and whose directory has no git remote (e.g. installed from a local path via 'local:' or file:// source but reached this branch), or a normalized-but-local source record.","commonSituations":"Manually edited or hand-pruned lock file; plugin installed from a local directory symlink but the local branch was bypassed; lock file written by an older opencli version lacking a valid source field.","solutions":["Check ~/.opencli/plugins.lock.json: ensure the plugin entry has a valid source (git or monorepo) with a url.","If the plugin is local-source, do not run remote update — reinstall/update via the local path.","Fix the git remote in the install dir (`git remote set-url origin <url>`) so getPluginSource can resolve it.","As a last resort, uninstall and reinstall the plugin from its remote source to regenerate the lock entry."],"exampleFix":"// before (lock entry missing source)\n{ \"myplugin\": { \"commitHash\": \"abc\", \"installedAt\": \"...\" } }\n// after\n{ \"myplugin\": { \"source\": { \"kind\": \"git\", \"url\": \"https://github.com/user/repo.git\" }, \"commitHash\": \"abc\", \"installedAt\": \"...\" } }","handlingStrategy":"validation","validationCode":"import * as fs from 'node:fs';\nconst lock = JSON.parse(fs.readFileSync(lockPath, 'utf-8'));\nconst entry = lock[pluginName];\nif (!entry?.source || entry.source.kind === 'local') {\n  // cannot remote-update; reinstall or update locally instead\n}","typeGuard":"function hasRemoteSource(entry) {\n  return !!entry && typeof entry === 'object'\n    && entry.source != null\n    && (entry.source.kind === 'git' || entry.source.kind === 'monorepo')\n    && typeof entry.source.url === 'string';\n}","tryCatchPattern":"try {\n  updatePlugin(name);\n} catch (err) {\n  if (err.message.startsWith('Unable to determine remote source')) {\n    // fall back to reinstalling from a known remote source\n  } else throw err;\n}","preventionTips":["Don't hand-edit ~/.opencli/plugins.lock.json; use opencli commands.","After restoring backups or dotfiles, verify lock entries still carry valid source records.","Run `opencli plugin list` to confirm sources resolve before scripting updates."],"tags":["plugin-update","lock-file","missing-source"],"backgroundTag":"missing-plugin-source","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}