{"record":{"id":"a14da7017bf0b427","repo":"multica-ai/multica","slug":"chmod-temp-file-w","errorCode":null,"errorMessage":"chmod temp file: %w","messagePattern":"chmod temp file: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/internal/cli/update.go","lineNumber":468,"sourceCode":"\t}\n\ttmpPath := tmpFile.Name()\n\n\tif _, err := tmpFile.Write(binaryData); err != nil {\n\t\ttmpFile.Close()\n\t\tos.Remove(tmpPath)\n\t\treturn \"\", fmt.Errorf(\"write temp file: %w\", err)\n\t}\n\ttmpFile.Close()\n\n\t// Preserve original file permissions.\n\tinfo, err := os.Stat(exePath)\n\tif err != nil {\n\t\tos.Remove(tmpPath)\n\t\treturn \"\", fmt.Errorf(\"stat original binary: %w\", err)\n\t}\n\tif err := os.Chmod(tmpPath, info.Mode()); err != nil {\n\t\tos.Remove(tmpPath)\n\t\treturn \"\", fmt.Errorf(\"chmod temp file: %w\", err)\n\t}\n\n\t// Replace the original binary. On Windows this moves the running executable\n\t// aside first; on Unix a plain rename over the running inode is fine.\n\tif err := replaceBinary(tmpPath, exePath); err != nil {\n\t\tos.Remove(tmpPath)\n\t\treturn \"\", fmt.Errorf(\"replace binary: %w\", err)\n\t}\n\n\treturn fmt.Sprintf(\"Downloaded %s and replaced %s\", assetName, exePath), nil\n}\n\n// extractBinaryFromTarGz reads a .tar.gz stream and returns the contents of the\n// named file entry.\nfunc extractBinaryFromTarGz(r io.Reader, name string) ([]byte, error) {\n\tgz, err := gzip.NewReader(r)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"gzip reader: %w\", err)","sourceCodeStart":450,"sourceCodeEnd":486,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/cli/update.go#L450-L486","documentation":"The temp file is chmod-ed to the original binary's mode so the replaced executable stays executable; failure is wrapped as 'chmod temp file: %w' and the temp file removed. Because CreateTemp makes 0600 files, skipping this step would break the rename, so the update aborts instead.","triggerScenarios":"Filesystems that do not support chmod (some FAT/exFAT mounts, certain network filesystems, Windows ACL edge cases); the user losing ownership of the temp file mid-update; SELinux/AppArmor denying chmod on the path.","commonSituations":"Running the binary off an exFAT USB drive or a permissive NFS mount; hardened Linux with MAC policies on the install directory; Windows directories with restrictive inherited ACLs.","solutions":["Move the binary to a POSIX-capable local filesystem (ext4, APFS, NTFS with Go-supported ACLs).","Adjust SELinux/AppArmor policy or the directory ACLs to allow chmod by the running user.","Ensure the process owns the files it creates in the install directory.","Check the wrapped error path — it names the temp file that failed."],"exampleFix":"null","handlingStrategy":"validation","validationCode":"if info, err := os.Stat(exePath); err == nil {\n    if info.Mode().Perm()&0111 == 0 {\n        // original not executable; chmod before update to keep replace sane\n    }\n}","typeGuard":null,"tryCatchPattern":"out, err := cli.UpdateViaDownload(ver)\nif err != nil && strings.HasPrefix(err.Error(), \"chmod temp file\") {\n    // filesystem or MAC policy denies chmod: relocate binary to a POSIX fs\n}","preventionTips":["Keep the binary on a POSIX-capable local filesystem","Whitelist the install directory in SELinux/AppArmor policies","Avoid exFAT/FAT install locations for executables"],"tags":["filesystem","permissions","chmod"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}