{"record":{"id":"051392ec22b0342d","repo":"abiosoft/colima","slug":"cannot-truncate-file-for-fresh-download-w","errorCode":null,"errorMessage":"cannot truncate file for fresh download: %w","messagePattern":"cannot truncate file for fresh download: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/downloader/http.go","lineNumber":160,"sourceCode":"\n\t// execute request\n\tresp, err := h.client.Do(req)\n\tif err != nil {\n\t\treturn nil, &NetworkError{Op: \"download\", URL: opts.URL, Err: err}\n\t}\n\tdefer func() { _ = resp.Body.Close() }()\n\n\t// store final URL after redirects\n\tresult.FinalURL = resp.Request.URL.String()\n\tresult.ETag = resp.Header.Get(\"ETag\")\n\n\t// handle response status\n\tswitch resp.StatusCode {\n\tcase http.StatusOK: // 200 - Full content (resume not supported or If-Range failed)\n\t\tif existingSize > 0 {\n\t\t\t// server sent full content, need to truncate and start over\n\t\t\tif err := file.Truncate(0); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"cannot truncate file for fresh download: %w\", err)\n\t\t\t}\n\t\t\tif _, err := file.Seek(0, 0); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"cannot seek to start of file: %w\", err)\n\t\t\t}\n\t\t\texistingSize = 0\n\t\t}\n\t\tresult.TotalBytes = resp.ContentLength\n\n\tcase http.StatusPartialContent: // 206 - Resume successful\n\t\tresult.WasResumed = true\n\t\t// Content-Range: bytes 21010-47021/47022\n\t\tcontentRange := resp.Header.Get(\"Content-Range\")\n\t\tif totalSize := parseContentRangeTotal(contentRange); totalSize > 0 {\n\t\t\tresult.TotalBytes = totalSize\n\t\t} else {\n\t\t\tresult.TotalBytes = existingSize + resp.ContentLength\n\t\t}\n","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/abiosoft/colima/blob/c3a5f9184d83a197184f897a9f07eb3c01b3bc88/util/downloader/http.go#L142-L178","documentation":"The server answered 200 (full body) while a partial file existed for resume (If-Range/ETag path failed or Range unsupported), so the code must restart from byte 0 and calls file.Truncate(0) on the .downloading partial — and the truncate failed. Reached only when existingSize > 0; the wrapped error is a filesystem failure on the partial (I/O error, storage dropped, file replaced concurrently).","triggerScenarios":"Resume attempt against a server that ignores Range headers; the partial file sits on a network/external volume that errors on truncate; another process deleted/replaced the partial between open and truncate.","commonSituations":"Cache stored on flaky external storage; two colima processes resuming the same URL; disk faults.","solutions":["Delete the .downloading partial and the .resume info, then retry for a clean fresh download","Move the cache dir onto reliable local storage","Avoid concurrent downloads sharing the same cache dir"],"exampleFix":"# before: resume hits a 200, truncate fails on the poisoned partial\n# after: force a fresh download\ncache=$(limactl ... ) # sha256-named entry under <cachedir>/caches\nrm -f \"${cache}.downloading\" \"${cache}.resume\"","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"err := fileDownloader.Download(req, dest)\nif err != nil && strings.Contains(err.Error(), \"cannot truncate file for fresh download\") {\n    base := strings.TrimSuffix(dest, \".downloading\")\n    _ = os.Remove(dest)                  // poisoned partial\n    _ = os.Remove(base + \".resume\")       // stale resume info\n    err = fileDownloader.Download(req, dest)\n}\nif err != nil {\n    return err\n}","preventionTips":["Keep the cache dir on reliable local storage, not network volumes","Do not run two resumable downloads against the same partial file","On unexplained truncate failures, suspect disk health before retrying endlessly"],"tags":["download","resume","filesystem"],"backgroundTag":null,"analyzedSha":"c3a5f9184d83a197184f897a9f07eb3c01b3bc88","analyzedAt":"2026-08-15T18:58:08.334Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}