{"record":{"id":"0305d29954c534e1","repo":"gastownhall/beads","slug":"failed-to-marshal-interactions-log-entry-w","errorCode":null,"errorMessage":"failed to marshal interactions log entry: %w","messagePattern":"failed to marshal interactions log entry: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/audit/audit.go","lineNumber":148,"sourceCode":"\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}\n\n// AppendIfEnabled appends only when the optional JSONL sidecar is enabled.\nfunc AppendIfEnabled(e *Entry) (string, error) {\n\tif !Enabled() {\n\t\treturn \"\", fmt.Errorf(\"audit JSONL sidecar is disabled; set audit.enabled=true or BD_AUDIT_ENABLED=1 to write %s\", FileName)\n\t}\n\treturn Append(e)\n}\n\n// LogFieldChange logs a field change (status, assignee, priority, etc.) to the\n// optional JSONL sidecar when it is enabled. First-class issue history is","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/audit/audit.go#L130-L166","documentation":"Append serializes the Entry with a json.Encoder (HTML escaping disabled) before writing, and wraps any Encode failure as \"failed to marshal interactions log entry\". In practice json.Encoder on a struct almost never fails — this only triggers for values json cannot represent, such as an Extra map containing unsupported types (channels, funcs, or cyclic data). The cause is always in caller-supplied Entry data.","triggerScenarios":"Populating Entry.Extra with map[string]any values containing channels, functions, complex numbers, or cyclic references; custom json.Marshaler on Extra values returning an error; NaN/Inf values in float fields inside Extra.","commonSituations":"Storing Go error values or callbacks in Extra for debugging; passing untyped config data straight into Extra; marshaling types from libraries without json tags that fail custom marshaling.","solutions":["Sanitize Entry.Extra: keep only JSON-safe scalars, strings, slices, and maps.","Convert unsupported values to strings (e.g. err.Error()) before putting them in Extra.","Use json.Marshal on Extra contents in a pre-check to detect problems early.","Inspect the wrapped %w error; it names the offending Go type."],"exampleFix":"// before\nentry.Extra = map[string]any{\"handler\": handlerFunc} // unencodable\naudit.Append(entry) // \"failed to marshal interactions log entry: json: unsupported type: func()\"\n// after\nentry.Extra = map[string]any{\"handler\": fmt.Sprintf(\"%T\", handlerFunc)}\naudit.Append(entry)","handlingStrategy":"validation","validationCode":"if _, err := json.Marshal(entry.Extra); err != nil {\n    entry.Extra = map[string]any{\"extra_error\": err.Error()}\n}","typeGuard":null,"tryCatchPattern":"if _, err := audit.Append(e); err != nil {\n    if strings.Contains(err.Error(), \"failed to marshal interactions log entry\") {\n        log.Printf(\"dropping unencodable audit entry: %v\", err)\n        return nil\n    }\n    return err\n}","preventionTips":["Only put JSON-encodable values (strings, numbers, bools, maps, slices) in Extra.","Convert errors/functions to strings with fmt.Sprintf before storing.","Round-trip Extra through json.Marshal in tests to catch bad values early."],"tags":["go","json","serialization","audit"],"backgroundTag":"json-marshallable","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}