gastownhall/beads · error

failed to write interactions log entry: %w

Error message

failed to write interactions log entry: %w

What it means

Append writes the fully encoded JSON line in a single f.Write call (deliberately avoiding bufio to keep O_APPEND writes atomic), and wraps any write failure as "failed to write interactions log entry". This indicates the file was opened successfully but the data could not be written — typically disk full, I/O errors, or the file descriptor going bad. A partial write here can corrupt a JSONL line, so the error is not recoverable within the same call.

Source

Thrown at internal/audit/audit.go:151

	}

	f, err := os.OpenFile(p, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) // nolint:gosec // intended permissions
	if err != nil {
		return "", fmt.Errorf("failed to open interactions log: %w", err)
	}
	defer func() { _ = f.Close() }() // Best effort: file close in defer after flush

	// Marshal to a single byte slice and write atomically.
	// Using bufio.NewWriter could split into multiple write() syscalls,
	// which interleave under concurrent O_APPEND and corrupt lines.
	var buf bytes.Buffer
	enc := json.NewEncoder(&buf)
	enc.SetEscapeHTML(false)
	if err := enc.Encode(e); err != nil {
		return "", fmt.Errorf("failed to marshal interactions log entry: %w", err)
	}
	if _, err := f.Write(buf.Bytes()); err != nil {
		return "", fmt.Errorf("failed to write interactions log entry: %w", err)
	}

	return e.ID, nil
}

// AppendIfEnabled appends only when the optional JSONL sidecar is enabled.
func AppendIfEnabled(e *Entry) (string, error) {
	if !Enabled() {
		return "", fmt.Errorf("audit JSONL sidecar is disabled; set audit.enabled=true or BD_AUDIT_ENABLED=1 to write %s", FileName)
	}
	return Append(e)
}

// LogFieldChange logs a field change (status, assignee, priority, etc.) to the
// optional JSONL sidecar when it is enabled. First-class issue history is
// recorded separately in the database events tables. Best-effort: errors are
// silently ignored so sidecar logging never blocks operations.
// Optional reason is included when non-empty (e.g., close reason, cleanup rule).

View on GitHub (pinned to 71377f2769)

Solutions

  1. Free disk space or increase the quota on the volume holding .beads/.
  2. Check the filesystem for errors (dmesg / SMART) if EIO is reported.
  3. Rotate or truncate an oversized interactions.jsonl.
  4. Retry the append once — transient NFS hiccups may clear.
  5. Verify RLIMIT_FSIZE is not limiting the process (ulimit -f).

Example fix

// before
if _, err := audit.AppendIfEnabled(e); err != nil {
    log.Fatal(err) // "failed to write interactions log entry: no space left on device"
}
// after
if _, err := audit.AppendIfEnabled(e); err != nil {
    if strings.Contains(err.Error(), "no space left") {
        pruneAuditLog(".beads/interactions.jsonl") // rotate/truncate
    }
    return err
}
Defensive patterns

Strategy: retry

Validate before calling

var st syscall.Statfs_t
if err := syscall.Statfs(".beads", &st); err == nil && st.Bavail*uint64(st.Bsize) < 1<<20 {
    return fmt.Errorf("low disk space for audit log")
}

Try / catch

if _, err := audit.AppendIfEnabled(e); err != nil {
    if strings.Contains(err.Error(), "failed to write interactions log entry") {
        log.Printf("audit write failed (disk full?): %v", err)
    }
    return err
}

Prevention

When it happens

Trigger: Disk quota or filesystem full when appending; EIO from failing hardware or network filesystems (NFS); file removed/unlinked while open in exotic setups; RLIMIT_FSIZE reached.

Common situations: CI runners with small ephemeral disks accumulating large audit logs; home directories on full NFS mounts; containers hitting their writable-layer size limit.

Related errors


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