gastownhall/beads · error

atomicfile: close: %w

Error message

atomicfile: close: %w

What it means

After chmod and sync succeed, Writer.Close calls f.Close(). If closing the file descriptor reports an error (e.g. deferred write errors surfaced at close, or the fd was already closed), the temp file is removed and this wrapped error is returned; the target stays intact.

Source

Thrown at internal/atomicfile/atomicfile.go:90

	}
	w.done = true

	// Ensure permissions before rename — CreateTemp uses 0600 by default.
	if err := w.f.Chmod(w.perm); err != nil {
		_ = w.f.Close()
		_ = os.Remove(w.f.Name())
		return fmt.Errorf("atomicfile: chmod: %w", err)
	}

	if err := w.f.Sync(); err != nil {
		_ = w.f.Close()
		_ = os.Remove(w.f.Name())
		return fmt.Errorf("atomicfile: sync: %w", err)
	}

	if err := w.f.Close(); err != nil {
		_ = os.Remove(w.f.Name())
		return fmt.Errorf("atomicfile: close: %w", err)
	}

	if err := os.Rename(w.f.Name(), w.target); err != nil {
		_ = os.Remove(w.f.Name())
		return fmt.Errorf("atomicfile: rename: %w", err)
	}

	return nil
}

// Abort discards the temp file without renaming. The target is untouched.
// Safe to call multiple times or after Close.
func (w *Writer) Abort() error {
	if w.done {
		return nil
	}
	w.done = true
	_ = w.f.Close()

View on GitHub (pinned to 71377f2769)

Solutions

  1. Call Close exactly once per Writer (use Abort for the discard path instead)
  2. Check for 'file already closed' in the wrapped error to find a double-close bug
  3. If the error indicates an I/O problem, retry the whole Create/Close sequence

Example fix

// before
w.Abort()
w.Close() // double-close -> "atomicfile: close"
// after
if err := w.Close(); err != nil { /* handle */ } // only one finalizer path
Defensive patterns

Strategy: try-catch

Try / catch

if err := w.Close(); err != nil {
    if strings.Contains(err.Error(), "file already closed") {
        log.Printf("double Close detected: %v", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling Close twice on the same Writer, or the OS returning an error at close time (delayed I/O error, fd exhaustion artifacts).

Common situations: Control-flow bugs where Abort and Close are both called, or code paths that close the underlying file independently of the Writer.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/d6a66c8f95d54038. Report an issue: GitHub.