netbirdio/netbird · error

failed to write output files: %w

Error message

failed to write output files: %w

What it means

Returned when writeOutputFiles cannot persist the freshly created revocation list and its .sig sidecar (client/cmd/signer/revocation.go:212-219). It calls os.WriteFile with 0600 permissions on the --revocation-list-file path and on that path plus ".sig". The error wraps an *os.PathError whose errno pinpoints the cause: ENOENT (parent directory missing), EACCES (no write permission), EISDIR (path names a directory), ENOSPC (disk full), EROFS (read-only filesystem).

Source

Thrown at client/cmd/signer/revocation.go:113

func handleCreateRevocationList(cmd *cobra.Command, revocationListFile string, privateRootKeyFile string) error {
	privKeyPEM, err := os.ReadFile(privateRootKeyFile)
	if err != nil {
		return fmt.Errorf("failed to read private root key file: %w", err)
	}

	privateRootKey, err := reposign.ParseRootKey(privKeyPEM)
	if err != nil {
		return fmt.Errorf("failed to parse private root key: %w", err)
	}

	rlBytes, sigBytes, err := reposign.CreateRevocationList(*privateRootKey, expirationDuration)
	if err != nil {
		return fmt.Errorf("failed to create revocation list: %w", err)
	}

	if err := writeOutputFiles(revocationListFile, revocationListFile+".sig", rlBytes, sigBytes); err != nil {
		return fmt.Errorf("failed to write output files: %w", err)
	}

	cmd.Println("✅ Revocation list created successfully")
	return nil
}

func handleExtendRevocationList(cmd *cobra.Command, keyID, revocationListFile, privateRootKeyFile string) error {
	privKeyPEM, err := os.ReadFile(privateRootKeyFile)
	if err != nil {
		return fmt.Errorf("failed to read private root key file: %w", err)
	}

	privateRootKey, err := reposign.ParseRootKey(privKeyPEM)
	if err != nil {
		return fmt.Errorf("failed to parse private root key: %w", err)
	}

	rlBytes, err := os.ReadFile(revocationListFile)

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Create the parent directory: mkdir -p $(dirname <list-path>)
  2. Check write permission on that directory and fix ownership, or run under an account that has access
  3. Verify the path names a file, not a directory, and the filesystem is mounted read-write
  4. Free space or extend the volume when the PathError shows 'no space left on device'

Example fix

# before
signer create-revocation-list --revocation-list-file /etc/netbird/revocation.list --private-root-key root.pem
# error: failed to write output files: open /etc/netbird/revocation.list: no such file or directory

# after
mkdir -p /etc/netbird
signer create-revocation-list --revocation-list-file /etc/netbird/revocation.list --private-root-key root.pem
Defensive patterns

Strategy: validation

Validate before calling

func ensureWritableDir(path string) error {
    dir := filepath.Dir(path)
    info, err := os.Stat(dir)
    if err != nil {
        return fmt.Errorf("stat %s: %w", dir, err)
    }
    if !info.IsDir() {
        return fmt.Errorf("%s is not a directory", dir)
    }
    f, err := os.CreateTemp(dir, ".writecheck-*")
    if err != nil {
        return fmt.Errorf("%s not writable: %w", dir, err)
    }
    f.Close()
    os.Remove(f.Name())
    return nil
}

// before running create-revocation-list:
// err := ensureWritableDir(revocationListFile)

Prevention

When it happens

Trigger: create-revocation-list with a --revocation-list-file whose parent directory does not exist, is not writable by the invoking user, names a directory, or sits on a full or read-only filesystem.

Common situations: Typing an output path into a directory that was never created; running as a non-root user against /etc or a release directory owned by root; CI containers with a small tmpfs; a stale read-only mount.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/005286371fa02359. Report an issue: GitHub.