hashicorp/nomad · error

error opening writer at %s: %w

Error message

error opening writer at %s: %w

What it means

OpenWriter opens the FIFO itself for writing after successfully opening the parent directory via os.Root. This error wraps a failure of root.OpenFile(base, os.O_WRONLY) — the file exists check fails (ENOENT), permissions deny write (EACCES), or the O_NOFOLLOW-protected open rejects a symlink. Opening a FIFO for write also fails with ENXIO if no reader is listening.

Source

Thrown at client/lib/fifo/fifo_unix.go:64

	}
	return f, nil
}

// OpenWriter opens a fifo file for writer, assuming it already exists, returns io.WriteCloser
func OpenWriter(path string) (io.WriteCloser, error) {
	dir := filepath.Dir(path)
	base := filepath.Base(path)

	root, err := os.OpenRoot(dir)
	if err != nil {
		return nil, fmt.Errorf("error opening fifo parent directory %q: %w", dir, err)
	}
	defer root.Close()

	// also uses O_NOFOLLOW under the hood
	f, err := root.OpenFile(base, os.O_WRONLY, 0)
	if err != nil {
		return nil, fmt.Errorf("error opening writer at %s: %w", path, err)
	}
	return f, nil
}

// Remove a fifo that already exists at a given path
func Remove(path string) error {
	dir := filepath.Dir(path)
	base := filepath.Base(path)

	root, err := os.OpenRoot(dir)
	if err != nil {
		return fmt.Errorf("error opening root dir %q: %w", dir, err)
	}
	defer root.Close()

	return root.Remove(base)
}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Ensure the FIFO exists first: call fifo.Create(path, mode) or start the reader before the writer
  2. On ENXIO, verify a reader (OpenReader/CreateAndRead) is open on the FIFO before writing
  3. Remove and recreate stale or symlinked FIFOs with fifo.Remove + fifo.Create
  4. Check write permissions on the FIFO for the writing user

Example fix

// before
w, err := fifo.OpenWriter("/run/containerd/io/stdout")
// after
if err := fifo.Create("/run/containerd/io/stdout", 0o600); err != nil && !errors.Is(err, os.ErrExist) { return err }
w, err := fifo.OpenWriter("/run/containerd/io/stdout")
Defensive patterns

Strategy: retry

Validate before calling

fi, err := os.Lstat(path)
if err != nil || fi.Mode()&os.ModeNamedPipe == 0 {
	return fmt.Errorf("fifo %s not present", path)
}

Type guard

func isENXIO(err error) bool {
	return errors.Is(err, syscall.ENXIO)
}

Try / catch

w, err := fifo.OpenWriter(path)
if err != nil {
	switch {
	case errors.Is(err, fs.ErrNotExist):
		// create fifo then retry open
	case errors.Is(err, syscall.ENXIO):
		// no reader attached; wait for reader and retry
	}
	return err
}

Prevention

When it happens

Trigger: Calling fifo.OpenWriter(path) when the FIFO was not created (ENOENT), the process lacks write permission (EACCES), the target is a symlink (O_NOFOLLOW rejection), or no reader has the FIFO open (ENXIO).

Common situations: Writer started before the log-monitor created the FIFO; FIFO removed by cleanup between creation and open; path replaced by symlink after a previous insecure setup; ENXIO when the reader process died.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/f6007bca391f25c3. Report an issue: GitHub.