{"record":{"id":"6e7d0bccf3062447","repo":"charmbracelet/crush","slug":"failed-to-write-file-w","errorCode":null,"errorMessage":"failed to write file: %w","messagePattern":"failed to write file: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/agent/tools/download.go","lineNumber":154,"sourceCode":"\n\t\t\t// Create parent directories if they don't exist\n\t\t\tif err := os.MkdirAll(filepath.Dir(filePath), 0o755); err != nil {\n\t\t\t\treturn fantasy.ToolResponse{}, fmt.Errorf(\"failed to create parent directories: %w\", err)\n\t\t\t}\n\n\t\t\t// Create the output file\n\t\t\toutFile, err := os.Create(filePath)\n\t\t\tif err != nil {\n\t\t\t\treturn fantasy.ToolResponse{}, fmt.Errorf(\"failed to create output file: %w\", err)\n\t\t\t}\n\t\t\tdefer outFile.Close()\n\n\t\t\t// Copy data without an explicit size limit.\n\t\t\t// The overall download is still constrained by the HTTP client's timeout\n\t\t\t// and any upstream server limits.\n\t\t\tbytesWritten, err := io.Copy(outFile, resp.Body)\n\t\t\tif err != nil {\n\t\t\t\treturn fantasy.ToolResponse{}, fmt.Errorf(\"failed to write file: %w\", err)\n\t\t\t}\n\n\t\t\tcontentType := resp.Header.Get(\"Content-Type\")\n\t\t\tresponseMsg := fmt.Sprintf(\"Successfully downloaded %d bytes to %s\", bytesWritten, relPath)\n\t\t\tif contentType != \"\" {\n\t\t\t\tresponseMsg += fmt.Sprintf(\" (Content-Type: %s)\", contentType)\n\t\t\t}\n\n\t\t\treturn fantasy.NewTextResponse(responseMsg), nil\n\t\t},\n\t)\n}\n","sourceCodeStart":136,"sourceCodeEnd":167,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/agent/tools/download.go#L136-L167","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check the wrapped error: context deadline exceeded → raise params.Timeout (max 600)","Retry the download to overwrite the partial file","Verify free disk space with df","Check server-side limits/proxy idle timeouts for large files"],"exampleFix":"// before\n{ \"url\": \"https://host/big.iso\", \"file_path\": \"big.iso\", \"timeout\": 30 }\n// after\n{ \"url\": \"https://host/big.iso\", \"file_path\": \"big.iso\", \"timeout\": 600 }","handlingStrategy":"retry","validationCode":"// pre-flight free space check\nvar st syscall.Statfs_t\nsyscall.Statfs(filepath.Dir(target), &st)\nif st.Bavail*uint64(st.Bsize) < expectedSize { return fmt.Errorf(\"insufficient disk space\") }","typeGuard":null,"tryCatchPattern":"bytesWritten, err := io.Copy(outFile, resp.Body)\nif err != nil {\n    os.Remove(filePath) // drop the truncated partial file\n    if errors.Is(err, context.DeadlineExceeded) {\n        return retryWithLongerTimeout()\n    }\n    if isRetryableNetErr(err) {\n        return retryWithBackoff()\n    }\n    return fmt.Errorf(\"download aborted after %d bytes: %w\", bytesWritten, err)\n}","preventionTips":["Increase timeout for large downloads (max 600s)","Ensure sufficient free disk space before big downloads","Retry interrupted downloads; the tool leaves partial files behind","Watch for proxies/servers that drop long-running connections"],"tags":["io","network","download","partial-write"],"backgroundTag":"download-interrupted","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}