gastownhall/beads · error
not a well-formed JSON value: %w
Error message
not a well-formed JSON value: %w
What it means
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.
Source
Thrown at internal/storage/metadata_cas.go:116
// ...992, 1.0 as 1, -0.0 as 0, 1e300 as three hundred and one digits. So the
// substrate's own fidelity, not this rule, is what bounds a numeric value.
//
// What the literal rule buys is that this function stays a pure statement about
// JSON rather than a copy of one engine's number handling — a copy that would
// silently equate two values a TEXT-column backend can hold apart, on the one
// comparison a compare-and-set exists to make. What it COSTS is that a caller
// composing an expectation from its own spelling of a number can disagree with
// the row; the role answers that by making Current the value the ROW holds, so
// the documented loop converges. See issueops.CompareAndSetKeyRequest.Expected.
func CanonicalMetadataValue(raw json.RawMessage) (json.RawMessage, error) {
if !json.Valid(raw) {
return nil, fmt.Errorf("not a well-formed JSON value: %q", truncateMetadataValue(raw))
}
dec := json.NewDecoder(bytes.NewReader(raw))
dec.UseNumber()
var value any
if err := dec.Decode(&value); err != nil {
return nil, fmt.Errorf("not a well-formed JSON value: %w", err)
}
var buf bytes.Buffer
if err := writeCanonicalMetadataJSON(&buf, value); err != nil {
return nil, err
}
return json.RawMessage(buf.Bytes()), nil
}
// MetadataValuesEqual reports whether two optional metadata values are the same
// value under the canonical rule, with nil meaning ABSENT on either side. An
// absent key equals only an absent key: a key stored holding JSON null is
// present, and the metadata object can show the difference.
//
// Both sides are canonicalized here rather than assumed canonical, so it
// answers for raw caller input as well as for stored bytes.
func MetadataValuesEqual(a, b *json.RawMessage) (bool, error) {
if a == nil || b == nil {
return a == nil && b == nil, nilView on GitHub (pinned to 71377f2769)
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.
Example fix
// before
canonical, err := storage.CanonicalMetadataValue(raw) // opaque decoder failure
// after
var probe any
if err := json.Unmarshal(raw, &probe); err != nil {
return fmt.Errorf("metadata value undecodable: %w", err)
}
normalized, err := json.Marshal(probe)
if err != nil { return err }
canonical, err := storage.CanonicalMetadataValue(normalized) Defensive patterns
Strategy: try-catch
Validate before calling
var probe any
if err := json.Unmarshal(raw, &probe); err != nil {
return fmt.Errorf("metadata value undecodable: %w", err)
} Try / catch
canonical, err := storage.CanonicalMetadataValue(raw)
if err != nil {
var decErr *json.UnmarshalTypeError
if errors.As(err, &decErr) {
return fmt.Errorf("metadata value rejected at offset %d: %v", decErr.Offset, decErr)
}
return fmt.Errorf("canonicalizing metadata value: %w", err)
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- not a well-formed JSON value: %q
- ExternalDoltConfig: must set Socket or (Host, Port)
- metadata must be a JSON object: %w
- metadata.repo: %w
- metadata.repo must not be null
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/afd60e8d94bca4be.
Report an issue: GitHub.