charmbracelet/crush · error

failed to write file: %w

Error message

failed to write file: %w

What it means

io.Copy failed while streaming the HTTP response body into the output file, so the download was aborted partway. Either reading from resp.Body failed (connection reset, context timeout, truncated response) or writing to the file failed (disk full, I/O error).

Source

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

			// 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), nil
		},
	)
}

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Check the wrapped error: context deadline exceeded → raise params.Timeout (max 600)
  2. Retry the download to overwrite the partial file
  3. Verify free disk space with df
  4. Check server-side limits/proxy idle timeouts for large files

Example fix

// before
{ "url": "https://host/big.iso", "file_path": "big.iso", "timeout": 30 }
// after
{ "url": "https://host/big.iso", "file_path": "big.iso", "timeout": 600 }
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight free space check
var st syscall.Statfs_t
syscall.Statfs(filepath.Dir(target), &st)
if st.Bavail*uint64(st.Bsize) < expectedSize { return fmt.Errorf("insufficient disk space") }

Try / catch

bytesWritten, err := io.Copy(outFile, resp.Body)
if err != nil {
    os.Remove(filePath) // drop the truncated partial file
    if errors.Is(err, context.DeadlineExceeded) {
        return retryWithLongerTimeout()
    }
    if isRetryableNetErr(err) {
        return retryWithBackoff()
    }
    return fmt.Errorf("download aborted after %d bytes: %w", bytesWritten, err)
}

Prevention

When it happens

Trigger: The request context deadline expired mid-transfer; the server closed the connection early; disk became full during the write; a partial file is left on disk at filePath.

Common situations: Large downloads exceeding the timeout on slow connections; flaky proxies dropping long-lived responses; insufficient disk space; network interruption mid-download. Note the tool leaves a truncated partial file behind.

Related errors


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