charmbracelet/crush · error

ripgrep: %w

Error message

ripgrep: %w

What it means

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.

Source

Thrown at internal/agent/tools/glob.go:138

	return fsext.GlobGitignoreAwareCtx(ctx, walkPattern, walkRoot, limit)
}

func runRipgrep(cmd *exec.Cmd, searchRoot string, limit int) ([]string, error) {
	// Stream ripgrep's stdout instead of buffering the whole file list.
	// Over a huge root (e.g. $HOME) the full --files listing can be
	// hundreds of MB; reading it all at once and then sorting allocated
	// gigabytes. We read incrementally and stop once we have a bounded
	// pool of candidates.
	//
	// We collect more than `limit` so the shortest-path preference below
	// still has something to choose from, but the pool is capped so memory
	// stays small (a few thousand paths) no matter how large the tree is.
	candidatePool := max(limit*20, 1000)

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, fmt.Errorf("ripgrep: %w", err)
	}
	var stderr bytes.Buffer
	cmd.Stderr = &stderr

	if err := cmd.Start(); err != nil {
		return nil, fmt.Errorf("ripgrep: %w", err)
	}

	var matches []string
	reader := bufio.NewReader(stdout)
	for {
		path, err := reader.ReadString(0)
		if len(path) > 0 {
			path = strings.TrimRight(path, "\x00")
			if path != "" {
				absPath := filepathext.SmartJoin(searchRoot, path)
				if !fsext.SkipHidden(absPath) {
					matches = append(matches, absPath)

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Check and raise the process file-descriptor limit (`ulimit -n`)
  2. Ensure runRipgrep is not reusing an exec.Cmd that already had Start() called
  3. Re-run the glob operation — this failure is typically transient
  4. Verify ripgrep is installed and functioning (`rg --version`)

Example fix

// before
stdout, err := cmd.StdoutPipe()
if err != nil {
    return nil, fmt.Errorf("ripgrep: %w", err)
}
// after
stdout, err := cmd.StdoutPipe()
if err != nil {
    return nil, fmt.Errorf("ripgrep: failed to create stdout pipe: %w", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

if runtime.GOOS != "windows" {
    var lim syscall.Rlimit
    if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err == nil && lim.Cur < 1024 {
        lim.Cur = 4096
        _ = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &lim)
    }
}

Type guard

null

Try / catch

stdout, err := cmd.StdoutPipe()
if err != nil {
    return nil, fmt.Errorf("ripgrep: %w", err)
} // then cmd.Start(); never reuse the cmd after Wait

Prevention

When it happens

Trigger: 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).

Common situations: File descriptor limits (ulimit -n) exhausted under heavy parallel tool usage; reusing an already-started exec.Cmd; extremely constrained container environments.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/19e12d6b0bd4b2ab. Report an issue: GitHub.