{"record":{"id":"ea21fb134c89c395","repo":"can1357/oh-my-pi","slug":"searching-from-root-directory-is-not-allowed","errorCode":null,"errorMessage":"Searching from root directory '/' is not allowed","messagePattern":"Searching from root directory '/' is not allowed","errorType":"validation","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/glob.ts","lineNumber":204,"sourceCode":"\t\tconst preparationController = !this.#customOps?.glob && signal ? new AbortController() : undefined;\n\t\tconst abortPreparation = (): void => preparationController?.abort();\n\t\tif (preparationController && signal) {\n\t\t\tsignal.addEventListener(\"abort\", abortPreparation, { once: true });\n\t\t}\n\t\tconst immediateAbortSignal = this.#customOps?.glob ? signal : preparationController?.signal;\n\t\tconst execution = untilAborted(immediateAbortSignal, async () => {\n\t\t\tconst formatScopePath = (targetPath: string): string => formatPathRelativeToCwd(targetPath, this.session.cwd);\n\t\t\tconst scopedPaths = toPathList(pathInput);\n\t\t\tconst effectivePaths = scopedPaths.length > 0 ? scopedPaths : [\".\"];\n\t\t\tconst rawPatternInputs = this.#customOps\n\t\t\t\t? effectivePaths\n\t\t\t\t: await expandDelimitedPathEntries(effectivePaths, this.session.cwd, { splitter: parseFindPattern });\n\t\t\tconst rawPatterns = rawPatternInputs.map(input => normalizePathLikeInput(input).replace(/\\\\/g, \"/\"));\n\t\t\tconst aliasResolvedPatterns = this.#rootPathAlias\n\t\t\t\t? rawPatterns.map(pattern => (/^\\/+$/.test(pattern) ? \".\" : pattern))\n\t\t\t\t: rawPatterns;\n\t\t\tif (aliasResolvedPatterns.some(pattern => /^\\/+$/.test(pattern))) {\n\t\t\t\tthrow new ToolError(\"Searching from root directory '/' is not allowed\");\n\t\t\t}\n\t\t\tconst internalRouter = InternalUrlRouter.instance();\n\t\t\tconst normalizedPatterns: string[] = [];\n\t\t\tfor (const rawPattern of aliasResolvedPatterns) {\n\t\t\t\tif (!internalRouter.canHandle(rawPattern)) {\n\t\t\t\t\tnormalizedPatterns.push(rawPattern);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (isSshUrl(rawPattern)) {\n\t\t\t\t\tthrow new ToolError(\n\t\t\t\t\t\t`find cannot operate on a remote ssh:// path: ${rawPattern}. ssh:// has no local file to glob; use \\`read ${rawPattern}\\` to list or inspect the remote path.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (hasGlobPathChars(rawPattern)) {\n\t\t\t\t\tif (!/^memory:\\/\\//i.test(rawPattern)) {\n\t\t\t\t\t\tthrow new ToolError(`Glob patterns are not supported for internal URLs: ${rawPattern}`);\n\t\t\t\t\t}\n\t\t\t\t\tconst memoryGlob = splitMemoryGlobPattern(rawPattern);","sourceCodeStart":186,"sourceCodeEnd":222,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/glob.ts#L186-L222","documentation":"The glob/find tool resolves its path entries and refuses to search from the filesystem root '/'. Searching '/' would traverse the entire system, so any pattern that reduces to '/' (or repeated slashes) is rejected with a ToolError before any globbing happens.","triggerScenarios":"Calling the glob/find tool with path '/' or '//' (after alias resolution, root-path aliases map bare '/' to '.' only when #rootPathAlias is set; otherwise it throws).","commonSituations":"Default-empty path variable rendering as '/'; users expecting '/'-rooted search to be sandboxed to the workspace; config that sets searchPath to os.tmpdir-like '/' fallbacks.","solutions":["Search from the project root instead: omit path or use '.' / the workspace directory","Scope the search to a subdirectory, e.g. 'src'","Do not pass a bare '/' path entry; expand it to a concrete directory"],"exampleFix":"// before\nawait globTool({ pattern: \"*.ts\", path: \"/\" });\n// after\nawait globTool({ pattern: \"*.ts\", path: \"src\" });","handlingStrategy":"validation","validationCode":"const entries = Array.isArray(path) ? path : [path];\nif (entries.some(e => /^\\/+$/.test(e))) throw new Error(\"refusing to search from root '/'; pass '.' or a scoped directory\");","typeGuard":"const isScopedSearchPath = (p: string): boolean => p.length > 0 && !/^\\/+$/.test(p);","tryCatchPattern":"try {\n  await globTool({ pattern, path });\n} catch (err) {\n  if (err instanceof ToolError && /root directory/.test(err.message)) {\n    await globTool({ pattern, path: \".\" });\n  } else throw err;\n}","preventionTips":["Default empty path config to '.' (cwd), never '/'","Coerce '/' to '.' in your own input normalization layer","Scope searches to the workspace/project directory"],"tags":["path","security","input-validation"],"backgroundTag":"root-directory-search-forbidden","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}