{"record":{"id":"ec90672a71e71de6","repo":"gastownhall/beads","slug":"failed-to-open-interactions-log-w","errorCode":null,"errorMessage":"failed to open interactions log: %w","messagePattern":"failed to open interactions log: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/audit/audit.go","lineNumber":137,"sourceCode":"\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif e.ID == \"\" {\n\t\te.ID, err = newID()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\tif e.CreatedAt.IsZero() {\n\t\te.CreatedAt = time.Now().UTC()\n\t} else {\n\t\te.CreatedAt = e.CreatedAt.UTC()\n\t}\n\n\tf, err := os.OpenFile(p, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) // nolint:gosec // intended permissions\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to open interactions log: %w\", err)\n\t}\n\tdefer func() { _ = f.Close() }() // Best effort: file close in defer after flush\n\n\t// Marshal to a single byte slice and write atomically.\n\t// Using bufio.NewWriter could split into multiple write() syscalls,\n\t// which interleave under concurrent O_APPEND and corrupt lines.\n\tvar buf bytes.Buffer\n\tenc := json.NewEncoder(&buf)\n\tenc.SetEscapeHTML(false)\n\tif err := enc.Encode(e); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to marshal interactions log entry: %w\", err)\n\t}\n\tif _, err := f.Write(buf.Bytes()); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to write interactions log entry: %w\", err)\n\t}\n\n\treturn e.ID, nil\n}","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/audit/audit.go#L119-L155","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nif _, err := audit.AppendIfEnabled(e); err != nil {\n    return err // \"failed to open interactions log: ... is a directory\"\n}\n// after\nif fi, statErr := os.Stat(\".beads/interactions.jsonl\"); statErr == nil && fi.IsDir() {\n    os.RemoveAll(\".beads/interactions.jsonl\") // let bd recreate it\n}","handlingStrategy":"retry","validationCode":"if fi, err := os.Stat(\".beads/interactions.jsonl\"); err == nil && !fi.Mode().IsRegular() {\n    return fmt.Errorf(\"interactions.jsonl is not a regular file\")\n}","typeGuard":null,"tryCatchPattern":"if _, err := audit.AppendIfEnabled(e); err != nil {\n    if errors.Is(err, fs.ErrPermission) || strings.Contains(err.Error(), \"failed to open interactions log\") {\n        time.Sleep(50 * time.Millisecond)\n        _, err = audit.AppendIfEnabled(e) // one retry for rotation races\n    }\n    return err\n}","preventionTips":["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."],"tags":["go","filesystem","audit-log","file-open"],"backgroundTag":"file-open-failure","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}