{"record":{"id":"05f67992d4f9f576","repo":"henrygd/beszel","slug":"open-tmp-w","errorCode":null,"errorMessage":"open tmp: %w","messagePattern":"open tmp: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/tools/fetchsmartctl/main.go","lineNumber":71,"sourceCode":"\tif err != nil {\n\t\treturn fmt.Errorf(\"new request: %w\", err)\n\t}\n\treq.Header.Set(\"User-Agent\", \"beszel-fetchsmartctl/1.0\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"http get: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode < 200 || resp.StatusCode >= 300 {\n\t\treturn fmt.Errorf(\"unexpected HTTP status: %s\", resp.Status)\n\t}\n\n\ttmp := dest + \".tmp\"\n\tf, err := os.OpenFile(tmp, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"open tmp: %w\", err)\n\t}\n\n\t// Determine hash algorithm based on length (SHA1=40, SHA256=64)\n\tvar hasher hash.Hash\n\tif shaHex := strings.TrimSpace(shaHex); shaHex != \"\" {\n\t\tcleanSha := strings.ToLower(strings.ReplaceAll(shaHex, \" \", \"\"))\n\t\tswitch len(cleanSha) {\n\t\tcase 40:\n\t\t\thasher = sha1.New()\n\t\tcase 64:\n\t\t\thasher = sha256.New()\n\t\tdefault:\n\t\t\tf.Close()\n\t\t\tos.Remove(tmp)\n\t\t\treturn fmt.Errorf(\"unsupported hash length: %d (expected 40 for SHA1 or 64 for SHA256)\", len(cleanSha))\n\t\t}\n\t}\n","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/henrygd/beszel/blob/b38fb7dafa60812cc22e6a84ce313e94f1ce0a32/agent/tools/fetchsmartctl/main.go#L53-L89","documentation":"Thrown by downloadFile when os.OpenFile fails to create/truncate the temporary file dest+\".tmp\" for writing the download. It wraps the OS-level error (permission denied, read-only filesystem, invalid path). Failure happens after a successful HTTP response.","triggerScenarios":"Opening the .tmp file fails because the destination directory isn't writable, the tmp path is a directory, or the filesystem rejects O_CREATE/O_TRUNC.","commonSituations":"Read-only CI checkout; destination name collides with an existing directory; disk quota exceeded; SELinux/AppArmor policy denying writes.","solutions":["Check the wrapped %w OS error for the exact reason","Verify the destination directory is writable and the tmp path isn't an existing directory","Free disk space / raise quota if ENOSPC","Run in a writable working directory (copy the tool's output outside a read-only tree)"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"tmpPath := dest + \".tmp\"\nif info, err := os.Stat(tmpPath); err == nil && info.IsDir() {\n\treturn fmt.Errorf(\"%s is a directory\", tmpPath)\n}\nif f, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_WRONLY, 0o644); err != nil {\n\treturn err\n} else {\n\tf.Close()\n\tos.Remove(tmpPath)\n}","typeGuard":null,"tryCatchPattern":"if err := downloadFile(url, dest, sha); err != nil {\n\tvar perr *fs.PathError\n\tif errors.As(err, &perr) && perr.Op == \"open\" {\n\t\tfmt.Printf(\"cannot write %s: %v — check permissions/space\\n\", perr.Path, perr.Err)\n\t}\n\tos.Exit(1)\n}","preventionTips":["Ensure the download directory is writable by the running user","Remove stray .tmp files/dirs with the same name before running","Check SELinux/AppArmor policies on build machines","Monitor free space and inode/quota limits"],"tags":["filesystem","download","build-tool"],"backgroundTag":"file-write-permission-denied","analyzedSha":"b38fb7dafa60812cc22e6a84ce313e94f1ce0a32","analyzedAt":"2026-08-31T15:10:10.149Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}