gastownhall/beads · error

failed to create interactions log: %w

Error message

failed to create interactions log: %w

What it means

EnsureFile creates .beads/interactions.jsonl and returns an error wrapped as "failed to create interactions log" when os.OpenFile with O_CREATE|O_EXCL fails with an error other than os.ErrExist. This guards the create-only race in the audit sidecar: a concurrent creator winning the O_EXCL race is treated as success, any other open failure is surfaced here. The error is always a wrapped underlying OS error.

Source

Thrown at internal/audit/audit.go:103

	p, err := Path()
	if err != nil {
		return "", err
	}
	if err := os.MkdirAll(filepath.Dir(p), 0700); err != nil {
		return "", fmt.Errorf("failed to create .beads directory: %w", err)
	}
	if ensureFileBeforeCreateHook != nil {
		ensureFileBeforeCreateHook(p)
	}
	f, err := os.OpenFile(p, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644) // nolint:gosec // JSONL is intended to be shared via git across clones/tools.
	if err == nil {
		if closeErr := f.Close(); closeErr != nil {
			return "", fmt.Errorf("failed to close interactions log: %w", closeErr)
		}
		return p, nil
	}
	if !errors.Is(err, os.ErrExist) {
		return "", fmt.Errorf("failed to create interactions log: %w", err)
	}
	return p, nil
}

// Append appends an event to .beads/interactions.jsonl as a single JSON line.
// This is intentionally append-only: callers must not mutate existing lines.
func Append(e *Entry) (string, error) {
	if e == nil {
		return "", fmt.Errorf("nil entry")
	}
	if e.Kind == "" {
		return "", fmt.Errorf("kind is required")
	}

	p, err := EnsureFile()
	if err != nil {
		return "", err
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Fix filesystem permissions on the .beads directory so the current user can create files (chmod/chown).
  2. Check that the target path is on a writable filesystem, not a read-only mount or read-only container layer.
  3. Verify BEADS_DIR points to an existing writable directory.
  4. Inspect the wrapped %w cause with errors.Is/errors.As to identify the exact OS error.
  5. If you need audit logging, run bd from a checkout that has (or can create) a .beads directory.

Example fix

// before
p, err := audit.AppendIfEnabled(entry) // fails: permission denied in shared clone
// after
if err := os.Chmod(".beads", 0o700); err != nil { log.Fatal(err) } // or chown the checkout
p, err := audit.AppendIfEnabled(entry)
Defensive patterns

Strategy: try-catch

Validate before calling

if _, statErr := os.Stat(".beads"); statErr != nil && os.IsPermission(statErr) {
    return fmt.Errorf("cannot write .beads: %w", statErr)
}

Try / catch

if _, err := audit.AppendIfEnabled(entry); err != nil {
    if errors.Is(err, fs.ErrPermission) || errors.Is(err, syscall.EROFS) {
        log.Printf("audit log not writable: %v", err)
        return nil // degrade gracefully
    }
    return err
}

Prevention

When it happens

Trigger: Calling Append or AppendIfEnabled (which call EnsureFile) when Path() fails, os.MkdirAll of .beads fails (wrapped differently), or OpenFile(O_EXCL) fails with permission denied, a read-only filesystem, or a non-ErrExist error like EACCES/ENOTDIR on the interactions.jsonl path.

Common situations: Running bd in a repo checkout where .beads/ or the file is owned by another user; CI containers with read-only workspaces; BEADS_DIR pointing into a directory the current user cannot write; antivirus or filesystem restrictions blocking creation.

Related errors


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