gastownhall/beads · error
failed to open interactions log: %w
Error message
failed to open interactions log: %w
What it means
Append opens .beads/interactions.jsonl with os.OpenFile(O_CREATE|O_WRONLY|O_APPEND) after EnsureFile has created it; if the open fails the error is wrapped as "failed to open interactions log". This happens after validation, so it indicates a filesystem-level problem between creation and open — typically permissions, a race removing the file, or the path becoming invalid. The underlying OS error is preserved via %w.
Source
Thrown at internal/audit/audit.go:137
if err != nil {
return "", err
}
if e.ID == "" {
e.ID, err = newID()
if err != nil {
return "", err
}
}
if e.CreatedAt.IsZero() {
e.CreatedAt = time.Now().UTC()
} else {
e.CreatedAt = e.CreatedAt.UTC()
}
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
}View on GitHub (pinned to 71377f2769)
Solutions
- Check permissions and ownership of .beads/interactions.jsonl and its directory.
- Re-run the command — a transient concurrent-rotation race usually resolves on retry.
- Verify the path is a regular file, not a directory or broken symlink.
- Inspect the wrapped OS error with errors.Is(err, fs.ErrPermission) etc. to pinpoint the cause.
- Recreate the audit log: remove the broken file and let EnsureFile create a fresh one.
Example fix
// before
if _, err := audit.AppendIfEnabled(e); err != nil {
return err // "failed to open interactions log: ... is a directory"
}
// after
if fi, statErr := os.Stat(".beads/interactions.jsonl"); statErr == nil && fi.IsDir() {
os.RemoveAll(".beads/interactions.jsonl") // let bd recreate it
} Defensive patterns
Strategy: retry
Validate before calling
if fi, err := os.Stat(".beads/interactions.jsonl"); err == nil && !fi.Mode().IsRegular() {
return fmt.Errorf("interactions.jsonl is not a regular file")
} Try / catch
if _, err := audit.AppendIfEnabled(e); err != nil {
if errors.Is(err, fs.ErrPermission) || strings.Contains(err.Error(), "failed to open interactions log") {
time.Sleep(50 * time.Millisecond)
_, err = audit.AppendIfEnabled(e) // one retry for rotation races
}
return err
} Prevention
- Don't rotate/delete interactions.jsonl while bd processes are running.
- Keep file ownership and ACLs stable on .beads/.
- Avoid pointing BEADS_DIR at symlinked or network paths for audit logging.
When it happens
Trigger: File permissions changed between EnsureFile and Append; another process deleted or replaced interactions.jsonl with a directory; the .beads path no longer exists; O_APPEND open blocked by ACLs or immutable flags on the file.
Common situations: Concurrent bd processes cleaning/rotating the audit log while another appends; running with a different uid in a shared clone; misconfigured BEADS_DIR; symlinks pointing to read-only locations.
Related errors
- failed to create interactions log: %w
- failed to write interactions log entry: %w
- dolt path is not executable
- server: NewDoltServer: failed to determine absolute path of
- server: NewDoltServer: failed to determine absolute path of
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/ec90672a71e71de6.
Report an issue: GitHub.