{"record":{"id":"afd60e8d94bca4be","repo":"gastownhall/beads","slug":"not-a-well-formed-json-value-w","errorCode":null,"errorMessage":"not a well-formed JSON value: %w","messagePattern":"not a well-formed JSON value: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/metadata_cas.go","lineNumber":116,"sourceCode":"// ...992, 1.0 as 1, -0.0 as 0, 1e300 as three hundred and one digits. So the\n// substrate's own fidelity, not this rule, is what bounds a numeric value.\n//\n// What the literal rule buys is that this function stays a pure statement about\n// JSON rather than a copy of one engine's number handling — a copy that would\n// silently equate two values a TEXT-column backend can hold apart, on the one\n// comparison a compare-and-set exists to make. What it COSTS is that a caller\n// composing an expectation from its own spelling of a number can disagree with\n// the row; the role answers that by making Current the value the ROW holds, so\n// the documented loop converges. See issueops.CompareAndSetKeyRequest.Expected.\nfunc CanonicalMetadataValue(raw json.RawMessage) (json.RawMessage, error) {\n\tif !json.Valid(raw) {\n\t\treturn nil, fmt.Errorf(\"not a well-formed JSON value: %q\", truncateMetadataValue(raw))\n\t}\n\tdec := json.NewDecoder(bytes.NewReader(raw))\n\tdec.UseNumber()\n\tvar value any\n\tif err := dec.Decode(&value); err != nil {\n\t\treturn nil, fmt.Errorf(\"not a well-formed JSON value: %w\", err)\n\t}\n\tvar buf bytes.Buffer\n\tif err := writeCanonicalMetadataJSON(&buf, value); err != nil {\n\t\treturn nil, err\n\t}\n\treturn json.RawMessage(buf.Bytes()), nil\n}\n\n// MetadataValuesEqual reports whether two optional metadata values are the same\n// value under the canonical rule, with nil meaning ABSENT on either side. An\n// absent key equals only an absent key: a key stored holding JSON null is\n// present, and the metadata object can show the difference.\n//\n// Both sides are canonicalized here rather than assumed canonical, so it\n// answers for raw caller input as well as for stored bytes.\nfunc MetadataValuesEqual(a, b *json.RawMessage) (bool, error) {\n\tif a == nil || b == nil {\n\t\treturn a == nil && b == nil, nil","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/metadata_cas.go#L98-L134","documentation":"After the json.Valid fast path passes, CanonicalMetadataValue decodes the value with a decoder using UseNumber so numbers keep their source literal. If json.Decoder.Decode still fails (a rare race: input valid per json.Valid but rejected during decode, e.g. trailing garbage handling or decoder-level limits), this error wraps the underlying decoder error so the root cause is preserved via errors.Is/As.","triggerScenarios":"Calling CanonicalMetadataValue / MetadataValuesEqual / CanonicalMetadataPointer (which backs PlanCompareAndSetKey) with bytes that pass json.Valid but fail Decode — practically: decoder-internal failure on pathological inputs; in practice this fires when json.Valid and the decoder disagree about the input.","commonSituations":"Exotic or corrupted byte sequences from a damaged store or a non-JSON-producing intermediary; a custom json.RawMessage source that mutated bytes between validation and decode; Go standard-library version differences in JSON acceptance.","solutions":["Unwrap with errors.Is / %v inspection — the wrapped json.DecodeError names the exact offset and syntax problem.","Re-marshal the value through json.Marshal or a fresh json.Decoder to normalize the bytes before canonicalizing.","Round-trip: decode into any and re-marshal into json.RawMessage, dropping anything non-representable.","If reproducible, report/inspect the input with truncateMetadataValue-style bounding and harden the producer."],"exampleFix":"// before\ncanonical, err := storage.CanonicalMetadataValue(raw) // opaque decoder failure\n\n// after\nvar probe any\nif err := json.Unmarshal(raw, &probe); err != nil {\n    return fmt.Errorf(\"metadata value undecodable: %w\", err)\n}\nnormalized, err := json.Marshal(probe)\nif err != nil { return err }\ncanonical, err := storage.CanonicalMetadataValue(normalized)","handlingStrategy":"try-catch","validationCode":"var probe any\nif err := json.Unmarshal(raw, &probe); err != nil {\n    return fmt.Errorf(\"metadata value undecodable: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"canonical, err := storage.CanonicalMetadataValue(raw)\nif err != nil {\n    var decErr *json.UnmarshalTypeError\n    if errors.As(err, &decErr) {\n        return fmt.Errorf(\"metadata value rejected at offset %d: %v\", decErr.Offset, decErr)\n    }\n    return fmt.Errorf(\"canonicalizing metadata value: %w\", err)\n}","preventionTips":["Normalize third-party JSON with a decode/re-marshal round-trip before canonical comparison.","Keep the Go toolchain current so decoder behavior matches json.Valid.","Log a bounded prefix of the offending bytes (as the error does) when the failure is reproducible.","Do not mutate json.RawMessage bytes between validation and use."],"tags":["json","decoder","metadata","canonicalization"],"backgroundTag":"invalid-json-metadata-value","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}