{"record":{"id":"95a0c6369d45f39c","repo":"can1357/oh-my-pi","slug":"path-must-be-repository-relative","errorCode":null,"errorMessage":"path must be repository-relative","messagePattern":"path must be repository-relative","errorType":"validation","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/gh.ts","lineNumber":267,"sourceCode":"\t\t\t\t\treturn executeSearchCommits(this.session, params, signal);\n\t\t\t\tcase \"search_repos\":\n\t\t\t\t\treturn executeSearchRepos(this.session, params, signal);\n\t\t\t\tcase \"run_watch\":\n\t\t\t\t\treturn executeRunWatch(this.session, this.name, params, signal, onUpdate);\n\t\t\t}\n\t\t});\n\t}\n}\n\nasync function executeFileRead(\n\tsession: ToolSession,\n\tparams: GithubInput,\n\tsignal: AbortSignal | undefined,\n): Promise<AgentToolResult<GhToolDetails>> {\n\tconst repo = await resolveGitHubRepo(session.cwd, normalizeOptionalString(params.repo), undefined, signal);\n\tconst filePath = requireNonEmpty(normalizeOptionalString(params.path), \"path\");\n\tif (filePath.startsWith(\"/\")) {\n\t\tthrow new ToolError(\"path must be repository-relative\");\n\t}\n\tconst branch = normalizeOptionalString(params.branch);\n\tconst endpointPath = filePath\n\t\t.split(\"/\")\n\t\t.map(segment => encodeURIComponent(segment))\n\t\t.join(\"/\");\n\tconst ref = parseRepoRef(repo);\n\tconst args = [\n\t\t\"api\",\n\t\t...ghApiHostArgs(ref),\n\t\t`/repos/${ref.slug}/contents/${endpointPath}`,\n\t\t\"--method\",\n\t\t\"GET\",\n\t\t\"-H\",\n\t\t\"Accept: application/vnd.github+json\",\n\t\t\"-H\",\n\t\t\"Accept-Encoding: identity\",\n\t];","sourceCodeStart":249,"sourceCodeEnd":285,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/gh.ts#L249-L285","documentation":"executeFileRead reads a file's contents from a GitHub repository via the gh-backed API. The `path` parameter must be relative to the repository root; an absolute path (leading '/') would produce a malformed API path, so it is rejected up front with a ToolError.","triggerScenarios":"Calling the github file-read op with path: '/src/index.ts' or path: '/README.md' — any string whose first character is '/'.","commonSituations":"Agents converting local absolute paths directly to GitHub paths; users pasting URLs where the leading slash of the path segment survived; code that does path.join('/', file).","solutions":["Remove the leading slash: 'src/index.ts' instead of '/src/index.ts'","Strip the repo-root prefix if the input came from a local absolute path","Normalize with something like path.replace(/^\\/+/, '') before invoking"],"exampleFix":"// before\nawait executeFileRead({ path: \"/src/index.ts\" });\n// after\nawait executeFileRead({ path: \"src/index.ts\" });","handlingStrategy":"validation","validationCode":"const rel = normalizeOptionalString(params.path)?.replace(/^\\\\/+/, \"\");\nif (!rel) throw new Error(\"path required\");\nawait executeFileRead({ ...params, path: rel });","typeGuard":"const isRepoRelativePath = (p: string): boolean => p.length > 0 && !p.startsWith(\"/\");","tryCatchPattern":"try {\n  await gh.readFile({ path });\n} catch (err) {\n  if (err instanceof ToolError && err.message === \"path must be repository-relative\") {\n    path = path.replace(/^\\\\/+/, \"\");\n    // retry once with the corrected path\n  } else throw err;\n}","preventionTips":["Normalize paths with .replace(/^\\\\/+/, '') at the boundary","Never pass local absolute paths directly as GitHub paths","Strip URL path prefixes when deriving the repo path from a URL"],"tags":["input-validation","path","github"],"backgroundTag":"absolute-path-not-allowed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}