{"record":{"id":"09dc660b71472d0f","repo":"JuliusBrussee/caveman","slug":"caveman-code-neither-rg-nor-grep-is-available","errorCode":null,"errorMessage":"caveman-code: neither rg nor grep is available","messagePattern":"caveman-code: neither rg nor grep is available","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/code.ts","lineNumber":286,"sourceCode":"    result: \"inline\",\n    timeoutMs: READ_TIMEOUT_MS,\n    async execute(input, signal) {\n      const root = await workspaceRoot();\n      const scope = input.path === undefined ? root : await contained(input.path);\n      const relativeScope = scope === root ? \".\" : relative(root, scope);\n      const ripgrep = [\n        \"--line-number\", \"--no-heading\", \"--color\", \"never\",\n        \"--max-count\", String(GREP_MAX_MATCHES),\n        ...(input.glob === undefined ? [] : [\"--glob\", input.glob]),\n        \"--regexp\", input.pattern, \"--\", relativeScope,\n      ];\n      let run = await runProcess(\"rg\", ripgrep, root, READ_TIMEOUT_MS, signal);\n      if (run.spawnFailed) {\n        run = await runProcess(\"grep\", [\n          \"-rnI\", \"-m\", String(GREP_MAX_MATCHES), \"-E\", \"-e\", input.pattern, \"--\", relativeScope,\n        ], root, READ_TIMEOUT_MS, signal);\n      }\n      if (run.spawnFailed) throw new Error(\"caveman-code: neither rg nor grep is available\");\n      const body = run.output.trim() === \"\" ? \"no matches\" : run.output;\n      const text = capOutput(body, caps.grep);\n      record(`grep:${input.pattern}`, text);\n      return text;\n    },\n  });\n\n  const bashTool = tool({\n    name: \"bash\",\n    description:\n      \"Run one shell command in the workspace and return its combined stdout and stderr. \" +\n      `Times out after ${BASH_TIMEOUT_MS} ms; output is capped at ${caps.bash} bytes.`,\n    input: schema.object({\n      command: schema.string(),\n      timeoutMs: schema.optional(schema.integer()),\n    }),\n    effect: \"external\",\n    result: \"inline\",","sourceCodeStart":268,"sourceCodeEnd":304,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/code.ts#L268-L304","documentation":"The grep tool shells out to ripgrep (`rg`) and falls back to GNU grep when rg fails to spawn. If BOTH binaries fail to spawn (missing from PATH or not installed), this error is thrown. The library performs no in-JS text search, so search availability is entirely a host-environment property.","triggerScenarios":"Calling the `grep` tool in an environment where neither `rg` nor `grep` resolves on PATH: distroless/minimal Docker images, hardened sandboxes that strip /usr/bin, PATH sanitized by the process env builder, or Windows systems without ripgrep installed and no grep shim.","commonSituations":"Running the agent inside a slim container (node:slim, distroless) that omits coreutils; CI runners with restricted PATH; Windows dev machines where `rg` was never installed; sandbox profiles that deny executing search binaries.","solutions":["Install ripgrep (e.g. `apt-get install -y ripgrep` or `cargo install ripgrep`) in the environment the agent runs in — it is the preferred backend","If rg cannot be shipped, ensure GNU grep exists at a PATH location visible to the agent process (check the sanitized env built by buildCodingProcessEnv)","Verify with `which rg || which grep` inside the same container/PATH the agent uses, not your interactive shell","As a last resort in stripped images, base the image on one that includes coreutils (e.g. debian-slim instead of distroless)"],"exampleFix":"# before: distroless image, no rg/grep\nFROM gcr.io/distroless/nodejs22\n\n# after: add ripgrep\nFROM node:22-slim\nRUN apt-get update && apt-get install -y --no-install-recommends ripgrep && rm -rf /var/lib/apt/lists/*","handlingStrategy":"validation","validationCode":"import { spawnSync } from \"node:child_process\";\n\nfunction hasSearchBackend(): boolean {\n  for (const bin of [\"rg\", \"grep\"]) {\n    const probe = spawnSync(bin, [\"--version\"], { stdio: \"ignore\" });\n    if (probe.error === undefined) return true;\n  }\n  return false;\n}\n\nif (!hasSearchBackend()) throw new Error(\"install ripgrep or grep before enabling the grep tool\");","typeGuard":null,"tryCatchPattern":"try {\n  const out = await grepTool.execute({ pattern: \"TODO\", scope: \"src\" }, signal);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"neither rg nor grep\")) {\n    // environment capability problem: surface to operator, do not retry blindly\n    throw new Error(`search unavailable in this environment: ${err.message}`);\n  }\n  throw err;\n}","preventionTips":["Include ripgrep in the container image / machine provisioning for any host that runs the agent","Add a startup capability probe that fails fast when neither rg nor grep spawns","Test in the same minimal environment you deploy to, not just on a dev workstation"],"tags":["environment","tooling","spawn-failure","search"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}