gastownhall/beads · error
edge metadata is not well-formed JSON
Error message
edge metadata is not well-formed JSON
What it means
normalizeApplyEdgeMetadata validates the metadata string attached to a dependency edge. For non waits-for dependency types, if metadata is non-empty it must be well-formed JSON; this error means json.Valid failed on the trimmed string.
Source
Thrown at internal/storage/batch_apply.go:370
// A WAITS-FOR ROW MUST BE SELF-DESCRIBING. Readers that predate the gate's
// introduction do not default a missing one, so an absent, blank or `{}`
// metadata is written as {"gate":"all-children"} — the rule
// types.NewGraphEdgeDependency already applies on every other path that writes
// one, reached here so this role cannot drift from it. A metadata that names a
// gate keeps it, along with the spawner and also-blocks members a caller may
// carry; a gate that is neither known value is refused.
//
// Every other edge type's metadata is passed through unchanged, checked only
// for being well-formed JSON when it is present at all: the blob is
// type-specific and this role does not know the types.
func normalizeApplyEdgeMetadata(depType types.DependencyType, metadata string) (string, error) {
trimmed := strings.TrimSpace(metadata)
if depType != types.DepWaitsFor {
if trimmed == "" {
return "", nil
}
if !json.Valid([]byte(trimmed)) {
return "", fmt.Errorf("edge metadata is not well-formed JSON")
}
return metadata, nil
}
meta := types.WaitsForMeta{}
if trimmed != "" && trimmed != "{}" {
if err := json.Unmarshal([]byte(trimmed), &meta); err != nil {
return "", fmt.Errorf("waits-for metadata is not a well-formed gate object: %w", err)
}
}
if meta.Gate == "" {
meta.Gate = types.WaitsForAllChildren
}
if !types.IsValidWaitsForGate(meta.Gate) {
return "", fmt.Errorf("waits-for gate %q is neither %q nor %q",
meta.Gate, types.WaitsForAllChildren, types.WaitsForAnyChildren)
}
raw, err := json.Marshal(meta)
if err != nil {View on GitHub (pinned to 71377f2769)
Solutions
- Run the metadata through json.Marshal or a JSON linter before passing it
- Fix quoting when passing JSON via shell (single quotes inside, or heredoc)
- Pass empty string if no metadata is needed — empty is allowed
Example fix
// before
meta := "{reason: blocked by infra}"
// after
meta := `{"reason":"blocked by infra"}` Defensive patterns
Strategy: validation
Validate before calling
if strings.TrimSpace(meta) != "" && !json.Valid([]byte(meta)) {
return fmt.Errorf("metadata is not valid JSON: %q", meta)
} Try / catch
var e *fmt.WrapError
if err != nil && strings.Contains(err.Error(), "edge metadata is not well-formed JSON") {
// fall back to empty metadata or fix and re-submit
} Prevention
- Always build metadata with json.Marshal, never string concatenation
- Mind shell quoting when passing JSON on the command line
- Leave metadata empty when unused — empty is accepted
When it happens
Trigger: Calling a batch dep-add with DepType other than waits-for and a metadata string that is not parseable JSON (e.g. "open", "{key: 1}", trailing comma, or plain text).
Common situations: Hand-written metadata in a CLI flag or YAML config; single-quoted JSON copied from shell; concatenating strings into metadata; template output that never got JSON-encoded.
Related errors
- ExternalDoltConfig: must set Socket or (Host, Port)
- metadata.repo: %w
- metadata.repo must not be null
- loading config for gate resolution: %w
- invalid metadata: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/d4091523534b1170.
Report an issue: GitHub.