gastownhall/beads · error

parsing waits-for metadata to set also_blocks: %w

Error message

parsing waits-for metadata to set also_blocks: %w

What it means

NewWaitsForBlockingDependency sets also_blocks=true by round-tripping the dependency's metadata through json.Unmarshal. If the metadata produced by NewWaitsForDependency is not valid JSON, unmarshaling into WaitsForMeta fails with this wrapper.

Source

Thrown at internal/types/types.go:1429

	return NewGraphEdgeDependency(issueID, spawnerID, DepWaitsFor, gate, "", "", "", nil)
}

// NewWaitsForBlockingDependency builds a waits-for dependency that also
// carries classic blocking semantics (GH#3783): set also_blocks in the
// metadata so waitsForGateBlockedSQL additionally blocks while the spawner
// itself is open, not only while it has an open parent-child child. Use this
// instead of NewWaitsForDependency exactly when the caller is collapsing a
// would-be DepBlocks edge (from needs/depends_on) into this waits-for edge
// because the two would otherwise collide on the same (source, target) pair
// — never for a plain waits_for with no matching needs/depends_on entry.
func NewWaitsForBlockingDependency(issueID, spawnerID, gate string) (*Dependency, error) {
	dep, err := NewWaitsForDependency(issueID, spawnerID, gate)
	if err != nil {
		return nil, err
	}
	var meta WaitsForMeta
	if err := json.Unmarshal([]byte(dep.Metadata), &meta); err != nil {
		return nil, fmt.Errorf("parsing waits-for metadata to set also_blocks: %w", err)
	}
	meta.AlsoBlocks = true
	raw, err := json.Marshal(meta)
	if err != nil {
		return nil, fmt.Errorf("serializing waits-for also_blocks metadata: %w", err)
	}
	dep.Metadata = string(raw)
	return dep, nil
}

// NewGraphNodeDependency builds the dependency record for a graph-plan node's
// inline dep, shared by the embedded and domain apply paths so their
// resolution semantics cannot drift: an empty type defaults to blocks, and
// the target resolves as a plan-local key first, then as a literal issue ID.
// Waits-for deps carry gate metadata like waits-for edges (all-children
// default, no explicit spawner).
func NewGraphNodeDependency(issueID string, depType DependencyType, target string, keyToID map[string]string) (*Dependency, error) {
	if depType == "" {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Rebuild against the current beads version so NewWaitsForDependency emits valid JSON metadata
  2. Verify dep.Metadata before calling by json.Valid([]byte(dep.Metadata))
  3. If working with pre-migration-0059 rows that store '{}' or empty metadata, migrate them first

Example fix

// before
dep, _ := NewWaitsForBlockingDependency(issueID, spawnerID, gate) // fails on legacy metadata
// after
// check first
if !json.Valid([]byte(dep.Metadata)) { /* repair metadata or recreate the dependency */ }
Defensive patterns

Strategy: validation

Validate before calling

func metadataIsJSON(s string) bool { return json.Valid([]byte(s)) }
// call after building the base dependency:
// if !metadataIsJSON(dep.Metadata) { /* repair or recreate before NewWaitsForBlockingDependency */ }

Type guard

func validWaitsForMeta(raw string) bool {
	var m types.WaitsForMeta
	return json.Unmarshal([]byte(raw), &m) == nil
}

Try / catch

dep, err := types.NewWaitsForBlockingDependency(issueID, spawnerID, gate)
if err != nil {
	var perr *json.SyntaxError
	if errors.As(err, &perr) {
		// metadata not parseable: recreate the dependency from scratch
	}
	return err
}

Prevention

When it happens

Trigger: json.Unmarshal([]byte(dep.Metadata), &meta) failing inside NewWaitsForBlockingDependency, i.e. dep.Metadata holds invalid or non-JSON content.

Common situations: Only possible if the underlying NewGraphEdgeDependency changed what it stores in metadata (e.g. a version where waits-for rows carry '{}' or plain text), or a fork/patch altered metadata format.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/4299df216b55d401. Report an issue: GitHub.