{"record":{"id":"a78ddf8fee1d41df","repo":"gastownhall/beads","slug":"invalid-metadata-w-a78ddf","errorCode":null,"errorMessage":"invalid metadata: %w","messagePattern":"invalid metadata: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/embeddeddolt/issues.go","lineNumber":64,"sourceCode":"// UnclaimIssueIfAssignee releases a claim only while the issue is still assigned\n// to expectedAssignee (compare-and-swap, the inverse of ClaimIssue). Returns\n// storage.ErrAssigneeMismatch, leaving the issue untouched, when the current\n// assignee differs. Delegates SQL work to issueops; EmbeddedDolt auto-commits\n// the transaction.\nfunc (s *EmbeddedDoltStore) UnclaimIssueIfAssignee(ctx context.Context, id string, actor string, expectedAssignee string) error {\n\treturn s.withConn(ctx, true, func(tx *sql.Tx) error {\n\t\treturn issueops.UnclaimIssueIfAssigneeInTx(ctx, tx, id, actor, expectedAssignee)\n\t})\n}\n\n// UpdateIssue updates fields on an issue.\n// Delegates SQL work to issueops; EmbeddedDolt auto-commits the transaction.\nfunc (s *EmbeddedDoltStore) UpdateIssue(ctx context.Context, id string, updates map[string]interface{}, actor string) error {\n\t// Validate metadata against schema before routing.\n\tif rawMeta, ok := updates[\"metadata\"]; ok {\n\t\tmetadataStr, err := storage.NormalizeMetadataValue(rawMeta)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid metadata: %w\", err)\n\t\t}\n\t\tif err := issueops.ValidateMetadataIfConfigured(json.RawMessage(metadataStr)); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn s.withConn(ctx, true, func(tx *sql.Tx) error {\n\t\t_, err := issueops.UpdateIssueInTx(ctx, tx, id, updates, actor)\n\t\treturn err\n\t})\n}\n\n// UpdateIssueChecked applies the update like UpdateIssue, adding optional\n// atomic preconditions: when opts.ExpectedVersion is non-nil the update\n// proceeds only if the issue's current RowVersion (row_lock) still equals\n// *opts.ExpectedVersion, else it refuses with storage.ErrVersionMismatch; when\n// opts.ExpectedAssignee/ExpectedStatus are non-nil the update proceeds only if\n// the issue's current assignee/status match, else it refuses with","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/embeddeddolt/issues.go#L46-L82","documentation":"UpdateIssue validates the 'metadata' update value before routing to SQL. storage.NormalizeMetadataValue coerces/normalizes the raw value to a JSON string; any failure (non-string, non-JSON-serializable value) is wrapped as 'invalid metadata'. This is a pre-flight input validation error; nothing was written.","triggerScenarios":"Calling store.UpdateIssue(ctx, id, map[string]interface{}{\"metadata\": <bad value>}, actor) where the metadata value is not a string and not JSON-marshalable — e.g. a struct with unexported fields, a channel, or a func.","commonSituations":"Callers passing a Go map/struct instead of a JSON string; hand-built JSON with a syntax error; middleware that mangled the value type; older clients sending plain text metadata.","solutions":["Marshal the metadata to valid JSON before passing it: json.Marshal(v) and pass the resulting string.","Validate the JSON parses (json.Valid) before calling UpdateIssue.","Check the API contract — metadata must normalize to a JSON object/string.","If validation still fails, run the value through issueops.ValidateMetadataIfConfigured locally to see the schema complaint."],"exampleFix":"// before: raw struct -> invalid metadata\nupdates := map[string]interface{}{\"metadata\": myMetaStruct}\nstore.UpdateIssue(ctx, id, updates, actor)\n// after: marshal to JSON string first\nb, _ := json.Marshal(myMetaStruct)\nupdates := map[string]interface{}{\"metadata\": string(b)}\nstore.UpdateIssue(ctx, id, updates, actor)","handlingStrategy":"validation","validationCode":"func validMetadata(v interface{}) (string, error) {\n    b, err := json.Marshal(v)\n    if err != nil { return \"\", err }\n    if !json.Valid(b) { return \"\", fmt.Errorf(\"metadata is not valid JSON\") }\n    return string(b), nil\n}","typeGuard":"func isMetadataString(v interface{}) (string, bool) {\n    s, ok := v.(string)\n    return s, ok && json.Valid([]byte(s))\n}","tryCatchPattern":"err := store.UpdateIssue(ctx, id, updates, actor)\nif err != nil && strings.Contains(err.Error(), \"invalid metadata\") {\n    // nothing was written; fix the value and retry\n    if b, merr := json.Marshal(updates[\"metadata\"]); merr == nil {\n        updates[\"metadata\"] = string(b)\n        err = store.UpdateIssue(ctx, id, updates, actor)\n    }\n}","preventionTips":["Always json.Marshal structured metadata before UpdateIssue.","Run json.Valid on metadata strings before writes.","Centralize metadata construction in one helper.","Keep metadata values as JSON objects, not free text."],"tags":["validation","metadata","json"],"backgroundTag":"invalid-metadata","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}