gastownhall/beads · error

applyGraph: node %q: %w

Error message

applyGraph: node %q: %w

What it means

Wraps any error from the per-node create call during applyGraph pass 1 (inserting each node's issue), annotated with the node's key. Causes are the same as ordinary create failures: validation, prefix mismatch, dependency insert failures, storage errors. IDs minted for previously created nodes are tracked in keyToID, but the apply aborts on the first node failure.

Source

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

		// callers cannot route wisp-flagged issues into the durable table.
		if nodeWisp := node.Issue.Ephemeral || node.Issue.NoHistory; nodeWisp != useWisp {
			return GraphApplyResult{}, fmt.Errorf("applyGraph: node %q storage class (ephemeral=%t, no_history=%t) does not match plan routing (wisp=%t)", node.Key, node.Issue.Ephemeral, node.Issue.NoHistory, useWisp)
		}

		if node.AssignAfterCreate {
			pendingAssignees[i] = node.Assignee
			node.Issue.Assignee = ""
		} else if node.Assignee != "" {
			node.Issue.Assignee = node.Assignee
		}

		params := CreateIssueParams{
			Issue:  node.Issue,
			Labels: node.Labels,
		}
		r, err := u.create(ctx, params, actor, useWisp)
		if err != nil {
			return GraphApplyResult{}, fmt.Errorf("applyGraph: node %q: %w", node.Key, err)
		}
		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 {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the node key and the wrapped inner error to pinpoint the failing issue payload
  2. Fix the offending node's issue data (title, IDs, flags) and re-apply the plan
  3. Make nodes idempotent/skippable if they already exist before re-running apply
  4. Check storage connectivity/constraints if the wrapped error is driver-level

Example fix

// before: node issue missing required title
plan.Nodes[2].Issue = &types.Issue{}
// after: complete payload before apply
plan.Nodes[2].Issue = &types.Issue{Title: "child task", Status: "open"}
Defensive patterns

Strategy: validation

Validate before calling

for _, n := range plan.Nodes {
    if n.Issue.Title == "" { return fmt.Errorf("node %q missing title", n.Key) }
    for _, dep := range n.Dependencies {
        if !planHasKey(plan, dep.DependsOnKey) { return fmt.Errorf("node %q refs unknown key %q", n.Key, dep.DependsOnKey) }
    }
}
// also dedupe against existing issues before apply

Try / catch

result, err := uc.ApplyGraph(ctx, plan, actor)
if err != nil {
    var key string
    if _, e := fmt.Sscanf(err.Error(), "applyGraph: node %q", &key); e == nil {
        log.Printf("graph apply failed at node %s", key)
        // inspect and fix that node, then re-apply idempotently
    }
}

Prevention

When it happens

Trigger: Calling ApplyGraph where u.create fails for a node — invalid issue fields, duplicate ID, bad dependency/label references, or a storage/driver error during insert.

Common situations: Graph files with malformed issue payloads; duplicates of already-existing issues; dependency edges referencing keys that fail to create; transient DB failures mid-apply.

Related errors


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