{"record":{"id":"19e12d6b0bd4b2ab","repo":"charmbracelet/crush","slug":"ripgrep-w","errorCode":null,"errorMessage":"ripgrep: %w","messagePattern":"ripgrep: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/agent/tools/glob.go","lineNumber":138,"sourceCode":"\n\treturn fsext.GlobGitignoreAwareCtx(ctx, walkPattern, walkRoot, limit)\n}\n\nfunc runRipgrep(cmd *exec.Cmd, searchRoot string, limit int) ([]string, error) {\n\t// Stream ripgrep's stdout instead of buffering the whole file list.\n\t// Over a huge root (e.g. $HOME) the full --files listing can be\n\t// hundreds of MB; reading it all at once and then sorting allocated\n\t// gigabytes. We read incrementally and stop once we have a bounded\n\t// pool of candidates.\n\t//\n\t// We collect more than `limit` so the shortest-path preference below\n\t// still has something to choose from, but the pool is capped so memory\n\t// stays small (a few thousand paths) no matter how large the tree is.\n\tcandidatePool := max(limit*20, 1000)\n\n\tstdout, err := cmd.StdoutPipe()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"ripgrep: %w\", err)\n\t}\n\tvar stderr bytes.Buffer\n\tcmd.Stderr = &stderr\n\n\tif err := cmd.Start(); err != nil {\n\t\treturn nil, fmt.Errorf(\"ripgrep: %w\", err)\n\t}\n\n\tvar matches []string\n\treader := bufio.NewReader(stdout)\n\tfor {\n\t\tpath, err := reader.ReadString(0)\n\t\tif len(path) > 0 {\n\t\t\tpath = strings.TrimRight(path, \"\\x00\")\n\t\t\tif path != \"\" {\n\t\t\t\tabsPath := filepathext.SmartJoin(searchRoot, path)\n\t\t\t\tif !fsext.SkipHidden(absPath) {\n\t\t\t\t\tmatches = append(matches, absPath)","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/agent/tools/glob.go#L120-L156","documentation":"runRipgrep failed to obtain a stdout pipe from the ripgrep exec.Command. This is a rare process-setup failure that occurs before ripgrep itself runs, meaning the command could not even be wired for output capture.","triggerScenarios":"cmd.StdoutPipe() returns an error — almost always because the pipe could not be created (e.g. the command has already started, or an OS-level file-descriptor exhaustion).","commonSituations":"File descriptor limits (ulimit -n) exhausted under heavy parallel tool usage; reusing an already-started exec.Cmd; extremely constrained container environments.","solutions":["Check and raise the process file-descriptor limit (`ulimit -n`)","Ensure runRipgrep is not reusing an exec.Cmd that already had Start() called","Re-run the glob operation — this failure is typically transient","Verify ripgrep is installed and functioning (`rg --version`)"],"exampleFix":"// before\nstdout, err := cmd.StdoutPipe()\nif err != nil {\n    return nil, fmt.Errorf(\"ripgrep: %w\", err)\n}\n// after\nstdout, err := cmd.StdoutPipe()\nif err != nil {\n    return nil, fmt.Errorf(\"ripgrep: failed to create stdout pipe: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":"if runtime.GOOS != \"windows\" {\n    var lim syscall.Rlimit\n    if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err == nil && lim.Cur < 1024 {\n        lim.Cur = 4096\n        _ = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &lim)\n    }\n}","typeGuard":"null","tryCatchPattern":"stdout, err := cmd.StdoutPipe()\nif err != nil {\n    return nil, fmt.Errorf(\"ripgrep: %w\", err)\n} // then cmd.Start(); never reuse the cmd after Wait","preventionTips":["Never reuse an exec.Cmd that has already started","Raise RLIMIT_NOFILE in environments with many concurrent processes","Create the pipe exactly once per command, before Start","Treat as transient and retry once on failure"],"tags":["ripgrep","exec","process"],"backgroundTag":"process-spawn-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}