{"record":{"id":"9b096beba3a286ad","repo":"JuliusBrussee/caveman","slug":"caveman-code-not-a-file-input-path","errorCode":null,"errorMessage":"caveman-code: not a file: ${input.path}","messagePattern":"caveman-code: not a file: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"packages/agent/src/code.ts","lineNumber":242,"sourceCode":"    containedPath(await workspaceRoot(), candidate);\n\n  const readFileTool = tool({\n    name: \"read_file\",\n    description:\n      \"Read a UTF-8 file from the workspace. Optional offset/limit read a line range. \" +\n      `Output is capped at ${caps.read_file} bytes.`,\n    input: schema.object({\n      path: schema.string(),\n      offset: schema.optional(schema.integer()),\n      limit: schema.optional(schema.integer()),\n    }),\n    effect: \"read\",\n    result: \"inline\",\n    timeoutMs: READ_TIMEOUT_MS,\n    async execute(input) {\n      const target = await contained(input.path);\n      const info = await stat(target);\n      if (!info.isFile()) throw new Error(`caveman-code: not a file: ${input.path}`);\n      const content = await readFile(target, \"utf8\");\n      const lines = content.split(\"\\n\");\n      const offset = Math.max(1, input.offset ?? 1);\n      const limit = input.limit === undefined ? lines.length : Math.max(1, input.limit);\n      const selected = lines.slice(offset - 1, offset - 1 + limit);\n      const numbered = selected\n        .map((line, index) => `${offset + index}\\t${line}`)\n        .join(\"\\n\");\n      const text = capOutput(numbered, caps.read_file);\n      record(`read_file:${input.path}`, text);\n      return text;\n    },\n  });\n\n  const grepTool = tool({\n    name: \"grep\",\n    description:\n      \"Search the workspace for a regular expression with ripgrep (grep fallback). \" +","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/code.ts#L224-L260","documentation":"Thrown by the caveman-code `read_file` tool. The requested path was first checked for workspace containment (realpath-based), then `stat`ed; `info.isFile()` is false, meaning the path exists (or a contained symlink resolves) to a directory, FIFO, socket, device, or other non-regular file. Only regular files can be read, so the tool fails with the model-supplied path echoed back.","triggerScenarios":"A coding-agent model calls `read_file` with a directory path (e.g. `\"src\"` instead of `\"src/index.ts\"`), or with `/dev/null`, a named pipe, or a socket path inside the workspace. Containment already passed, so this is purely a not-a-regular-file rejection, not a security error.","commonSituations":"Models exploring an unfamiliar tree guessing file names and hitting directories; attempts to read `/proc` or `/dev` entries that happen to be symlinked into the workspace; using read_file where list/grep was intended.","solutions":["Instruct the model (system prompt/tool docs) to use the listing/grep tool for directories and read_file only for regular files.","Pass an explicit file path with its extension (`src/index.ts`, not `src`).","If a tool call must not fail the turn, catch this error in the turn loop and let the model retry with a corrected path."],"exampleFix":"// before: model calls the tool with a directory\ntools.read_file({ path: \"src\" });\n\n// after\ntools.read_file({ path: \"src/index.ts\", offset: 1, limit: 50 });","handlingStrategy":"type-guard","validationCode":"import { stat } from \"node:fs/promises\";\n\nasync function isRegularFile(path: string): Promise<boolean> {\n  try { return (await stat(path)).isFile(); } catch { return false; }\n}\n// model-side guard before calling read_file: await isRegularFile(resolvedPath)","typeGuard":"import type { Stats } from \"node:fs\";\nfunction isRegularFile(info: Stats): boolean {\n  return info.isFile();\n}","tryCatchPattern":"// inside a turn loop driving the coding agent:\ntry {\n  return await tools.read_file.execute({ path });\n} catch (error) {\n  if (error instanceof Error && error.message.startsWith(\"caveman-code: not a file:\")) {\n    return `Error: ${path} is not a regular file. Use the listing or grep tool for directories.`; // let the model correct itself\n  }\n  throw error;\n}","preventionTips":["Tool descriptions should tell the model read_file accepts regular files only.","Offer a list/grep tool so directories are never probed with read_file.","Treat this as a recoverable model mistake in the turn loop, not a crash."],"tags":["tools","coding-agent","filesystem","model-input"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}