{"record":{"id":"a950980370727308","repo":"netbirdio/netbird","slug":"failed-to-write-signature-file-w","errorCode":null,"errorMessage":"failed to write signature file: %w","messagePattern":"failed to write signature file: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/cmd/signer/revocation.go","lineNumber":217,"sourceCode":"\tcmd.Printf(\"Expires At: %s\\n\", rl.ExpiresAt.Format(time.RFC3339))\n\tcmd.Printf(\"Number of revoked keys: %d\\n\", len(rl.Revoked))\n\n\tif len(rl.Revoked) > 0 {\n\t\tcmd.Println(\"\\nRevoked Keys:\")\n\t\tfor keyID, revokedTime := range rl.Revoked {\n\t\t\tcmd.Printf(\"  - %s (revoked at: %s)\\n\", keyID, revokedTime.Format(time.RFC3339))\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc writeOutputFiles(rlPath, sigPath string, rlBytes, sigBytes []byte) error {\n\tif err := os.WriteFile(rlPath, rlBytes, 0o600); err != nil {\n\t\treturn fmt.Errorf(\"failed to write revocation list file: %w\", err)\n\t}\n\tif err := os.WriteFile(sigPath, sigBytes, 0o600); err != nil {\n\t\treturn fmt.Errorf(\"failed to write signature file: %w\", err)\n\t}\n\treturn nil\n}\n","sourceCodeStart":199,"sourceCodeEnd":221,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/cmd/signer/revocation.go#L199-L221","documentation":"The second os.WriteFile inside writeOutputFiles failed (revocation.go:216): the signature bytes could not be written to the derived path rlPath + \".sig\" with 0600 permissions. Because the list file is written first, hitting this error leaves a replaced list paired with a stale or missing signature — an inconsistent pair that fails verify-revocation-list until both are regenerated together.","triggerScenarios":"The <list>.sig path is unwritable (an old .sig owned by another user with 0600 perms is the classic case), the directory went read-only between the two writes, or the disk filled exactly before the second write.","commonSituations":"A .sig created by root in an earlier run, then extend run as a non-root user; permission hardening applied between runs; ENOSPC on nearly-full volumes.","solutions":["Fix permissions on the stale .sig (chown/chmod) or remove it so it can be recreated","Free space or remount read-write, then re-run the same create/extend command so list and signature are written as a matched pair","Always verify the pair afterwards: signer verify-revocation-list ... before publishing","Treat the on-disk pair as untrusted after this error until regenerated — the list may be newer than the signature"],"exampleFix":"# before: extend fails with 'failed to write signature file: open rl.json.sig: permission denied'\n# after\nchmod u+w rl.json.sig   # or: rm rl.json.sig\nsigner extend-revocation-list --key-id 1a2b3c4d5e6f7080 --revocation-list-file rl.json --private-root-key root.pem\nsigner verify-revocation-list --revocation-list-file rl.json --signature-file rl.json.sig --public-root-key root-public.pem","handlingStrategy":"validation","validationCode":"func preflightWrites(paths ...string) error {\n    for _, p := range paths {\n        if err := ensureWritableDir(p); err != nil {\n            return err\n        }\n        if info, err := os.Stat(p); err == nil {\n            if info.Mode().Perm()&0o200 == 0 {\n                return fmt.Errorf(\"%s not writable by uid %d\", p, os.Getuid())\n            }\n        }\n    }\n    return nil\n}\n\n// covers both rlPath and rlPath+\".sig\" — the second write is the easy one to miss","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Preflight BOTH target paths; a stale root-owned .sig is the classic second-write failure","After any failed write, re-run the sign step so list and signature stay a matched pair","Always verify with verify-revocation-list after recovery before publishing"],"tags":["go","cli","file-io","permissions","signature"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}