charmbracelet/crush · error
failed to create output file: %w
Error message
failed to create output file: %w
What it means
os.Create failed when opening/creating the destination file for the download. Parent directories were already created successfully, so this is specific to the final path component: the wrapped OS error gives the reason (permission denied, is-a-directory, too many open files, disk full at open time).
Source
Thrown at internal/agent/tools/download.go:145
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")
responseMsg := fmt.Sprintf("Successfully downloaded %d bytes to %s", bytesWritten, relPath)
if contentType != "" {
responseMsg += fmt.Sprintf(" (Content-Type: %s)", contentType)
}
return fantasy.NewTextResponse(responseMsg), nilView on GitHub (pinned to 7944b8e522)
Solutions
- Ensure file_path is not an existing directory and is a valid filename
- Check write permission on the target directory
- Close leaked file handles if fd limits are involved (ulimit -n)
- Check the wrapped error (EISDIR, EACCES, ENAMETOOLONG) and correct the path accordingly
Example fix
// before file_path: "dist" // dist is a directory // after file_path: "dist/app.tar.gz"
Defensive patterns
Strategy: validation
Validate before calling
if fi, err := os.Stat(target); err == nil && fi.IsDir() {
return fmt.Errorf("%s is a directory; pick a file name", target)
}
if f, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY, 0o644); err != nil {
return fmt.Errorf("cannot create %s: %v", target, err)
} else { f.Close() } Try / catch
outFile, err := os.Create(filePath)
if err != nil {
switch {
case errors.Is(err, fs.ErrPermission):
return fmt.Errorf("no permission to create %s", filePath)
case errors.Is(err, syscall.EISDIR):
return fmt.Errorf("%s is a directory", filePath)
default:
return err
}
} Prevention
- Don't pass an existing directory as file_path
- Use valid, reasonably short filenames
- Watch fd limits when creating many files concurrently
- Ensure the target directory grants write permission
When it happens
Trigger: filePath itself is an existing directory, the process lacks write permission on the containing directory, the path exceeds NAME_MAX, or the process hit its file-descriptor limit.
Common situations: file_path names an existing directory; downloading into a directory without write access; downloading many files concurrently exhausting ulimit -n; filename with illegal characters (e.g. '/' embedded, NUL).
Related errors
- failed to create parent directories: %w
- failed to access file: %w
- failed to create parent directories: %w
- session ID is required for accessing directories outside wor
- error listing directory: %w
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/e9b4fb618df098b7.
Report an issue: GitHub.