gastownhall/beads · error

applyGraph: node %q: updating metadata refs: %w

Error message

applyGraph: node %q: updating metadata refs: %w

What it means

Wraps a failure from issueRepo.Update when persisting a node's merged metadata JSON back to the store during applyGraph pass 2. The refs were resolved successfully but the metadata update write failed. This is a storage-layer error: record not found, constraint violation, or driver/connection failure.

Source

Thrown at internal/storage/domain/issue.go:1157

		}
		keyToID[node.Key] = r.Issue.ID
	}

	// Pass 2 — resolve MetadataRefs now that every node has a minted ID.
	// Merges the resolved IDs into the issue's existing metadata JSON and
	// writes the result back via Update. Kept inside applyGraph so the CLI
	// cannot bypass this step; the proxied caller used to do it post-call.
	for _, node := range plan.Nodes {
		if len(node.MetadataRefs) == 0 {
			continue
		}
		metaJSON, err := types.MergeMetadataRefs(node.Issue.Metadata, node.MetadataRefs, keyToID)
		if err != nil {
			return GraphApplyResult{}, fmt.Errorf("applyGraph: node %q: %w", node.Key, err)
		}
		updates := map[string]any{"metadata": metaJSON}
		if err := u.issueRepo.Update(ctx, keyToID[node.Key], updates, actor, IssueTableOpts{UseWispsTable: useWisp}); err != nil {
			return GraphApplyResult{}, fmt.Errorf("applyGraph: node %q: updating metadata refs: %w", node.Key, err)
		}
	}

	// Build the (childID, parentID) pair set and validate that any planned
	// parent-child link does not close a cycle through planned edges or
	// already-existing dependencies in the store. This must run before any
	// dep inserts to catch the violation before we've written anything.
	parentDepPairs := graphParentDepPairs(plan.Nodes, keyToID)
	newSchedulingEdges := make([][2]string, 0, len(plan.Nodes)+len(plan.Edges))
	if err := u.validatePlannedBlockingPaths(ctx, plan, keyToID, parentDepPairs); err != nil {
		return GraphApplyResult{}, err
	}
	if err := u.validatePlannedBlockingCycles(ctx, plan, keyToID); err != nil {
		return GraphApplyResult{}, err
	}
	// Preserve failure-before-write for explicit edges that conflict directly
	// with an implicit node parent relationship. Parent-first mutation below is
	// for transitive hierarchy visibility, not for deferring structural errors.

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped inner error for the driver-level cause
  2. Verify keyToID[node.Key] resolved to a real persisted issue ID
  3. Confirm the update uses the same wisp tier (UseWispsTable) as the create pass
  4. Retry transient failures; verify schema/migrations if persistent
Defensive patterns

Strategy: retry

Validate before calling

// verify nodes exist before pass-2 update happens implicitly; for manual retries:
for _, n := range plan.Nodes {
    if _, err := uc.GetIssue(ctx, keyToID[n.Key]); err != nil {
        return fmt.Errorf("node %q issue %s not persisted", n.Key, keyToID[n.Key])
    }
}

Try / catch

result, err := uc.ApplyGraph(ctx, plan, actor)
if err != nil && strings.Contains(err.Error(), "updating metadata refs") {
    if isTransientStorageError(err) {
        time.Sleep(backoff)
        result, err = uc.ApplyGraph(ctx, plan, actor) // must be idempotent
    }
}

Prevention

When it happens

Trigger: Calling ApplyGraph where issueRepo.Update (writing {"metadata": metaJSON}) fails for a node — issue row missing, table tier mismatch, or DB error.

Common situations: Issue deleted between create and update phases; wisp/durable routing mismatch; transient Dolt/storage failure; schema drift on the metadata column.

Related errors


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