charmbracelet/crush · error

failed to create parent directories: %w

Error message

failed to create parent directories: %w

What it means

os.MkdirAll failed while creating the parent directories of the destination file path before writing the download. The wrapped error contains the OS-level reason (permission denied, path exists as a file, read-only filesystem, etc.).

Source

Thrown at internal/agent/tools/download.go:139

			if err != nil {
				return fantasy.ToolResponse{}, fmt.Errorf("failed to create request: %w", err)
			}

			req.Header.Set("User-Agent", "crush/1.0")

			resp, err := client.Do(req)
			if err != nil {
				return fantasy.ToolResponse{}, fmt.Errorf("failed to download from URL: %w", err)
			}
			defer resp.Body.Close()

			if resp.StatusCode != http.StatusOK {
				return fantasy.NewTextErrorResponse(fmt.Sprintf("Request failed with status code: %d", resp.StatusCode)), nil
			}

			// Create parent directories if they don't exist
			if err := os.MkdirAll(filepath.Dir(filePath), 0o755); err != nil {
				return fantasy.ToolResponse{}, fmt.Errorf("failed to create parent directories: %w", err)
			}

			// Create the output file
			outFile, err := os.Create(filePath)
			if err != nil {
				return fantasy.ToolResponse{}, fmt.Errorf("failed to create output file: %w", err)
			}
			defer outFile.Close()

			// Copy data without an explicit size limit.
			// The overall download is still constrained by the HTTP client's timeout
			// and any upstream server limits.
			bytesWritten, err := io.Copy(outFile, resp.Body)
			if err != nil {
				return fantasy.ToolResponse{}, fmt.Errorf("failed to write file: %w", err)
			}

			contentType := resp.Header.Get("Content-Type")

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Check the wrapped error: EEXIST on a path component means a file occupies a directory slot — choose a different file_path
  2. Verify write permissions on the destination directory (ls -ld)
  3. Ensure the download target is inside the writable working directory
  4. Free disk space or remount read-only filesystems as writable

Example fix

// before
file_path: "out.txt/data.zip"   // out.txt is an existing file
// after
file_path: "downloads/data.zip"
Defensive patterns

Strategy: validation

Validate before calling

parent := filepath.Dir(target)
if fi, err := os.Stat(parent); err == nil && !fi.IsDir() {
    return fmt.Errorf("%s exists and is not a directory", parent)
}
if err := unix.Access(filepath.Dir(parent), unix.W_OK); err != nil {
    return fmt.Errorf("no write permission for %s: %v", parent, err)
}

Try / catch

if err := os.MkdirAll(dir, 0o755); err != nil {
    if errors.Is(err, fs.ErrExist) {
        return fmt.Errorf("a file occupies a directory path component: %s", dir)
    }
    if errors.Is(err, fs.ErrPermission) {
        return fmt.Errorf("cannot write to %s: %w", dir, err)
    }
    return err
}

Prevention

When it happens

Trigger: The directory portion of workingDir + params.FilePath cannot be created: a parent path component is an existing regular file, the process lacks write permission, or the filesystem is read-only/full.

Common situations: file_path points under a path where a file already occupies a directory name (e.g. /tmp/out.txt/sub/dir); downloading into a read-only mount or container layer; destination inside a directory owned by another user.

Related errors


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