gastownhall/beads · error

applyGraph: edge %d: %w

Error message

applyGraph: edge %d: %w

What it means

After all structural checks pass, applyGraph builds the dependency via types.NewGraphEdgeDependency (validating type/gate/spawner/thread references against keyToID) and inserts it. Any failure from the constructor is wrapped as "applyGraph: edge %d: %w" — this identifies the constructor stage, distinct from the storage-insert wrapper that includes the from->to IDs.

Source

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

				depType = types.DepBlocks
			}
			if (depType == types.DepParentChild) != parentPhase {
				continue
			}

			if parentDepPairs[depPairKey(fromID, toID)] {
				if depType == types.DepParentChild {
					continue
				}
				return GraphApplyResult{}, fmt.Errorf("applyGraph: edge %d %s->%s duplicates a parent-child relationship with dependency type %q", i, fromID, toID, depType)
			}
			if parentDepPairs[depPairKey(toID, fromID)] && cycleRelevantDepType(depType) {
				return GraphApplyResult{}, fmt.Errorf("applyGraph: edge %d %s->%s creates a blocking reverse of a parent-child relationship", i, fromID, toID)
			}

			dep, err := types.NewGraphEdgeDependency(fromID, toID, depType, edge.Gate, edge.SpawnerKey, edge.SpawnerID, edge.ThreadID, keyToID)
			if err != nil {
				return GraphApplyResult{}, fmt.Errorf("applyGraph: edge %d: %w", i, err)
			}
			if err := u.depRepo.Insert(ctx, dep, actor, DepInsertOpts{UseWispsTable: useWisp}); err != nil {
				return GraphApplyResult{}, fmt.Errorf("applyGraph: edge %d (%s -> %s): %w", i, fromID, toID, err)
			}
			if types.IsSchedulingEdge(depType) {
				newSchedulingEdges = append(newSchedulingEdges, [2]string{fromID, toID})
			}
		}

		// Per-node inline deps in stable order for this phase, resolved by the
		// same shared builder as the embedded executeGraphApply (cmd/bd/graph_apply.go).
		for _, node := range plan.Nodes {
			for _, nd := range node.Deps {
				dep, err := types.NewGraphNodeDependency(keyToID[node.Key], nd.Type, nd.Target, keyToID)
				if err != nil {
					return GraphApplyResult{}, fmt.Errorf("applyGraph: node %q: %w", node.Key, err)
				}
				if (dep.Type == types.DepParentChild) != parentPhase {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped cause (%w) for the constructor's specific complaint and fix that edge field
  2. Validate gate syntax client-side before building the plan
  3. Ensure SpawnerKey/SpawnerID resolve (add a plan node or clear the field)
  4. Upgrade/downgrade the client so edge fields match the installed beads version

Example fix

// before
GraphEdge{FromKey: "bd-1", ToKey: "bd-2", Type: "conditional-blocks", Gate: "status == open &&"} // malformed gate
// after
GraphEdge{FromKey: "bd-1", ToKey: "bd-2", Type: "conditional-blocks", Gate: "status == open"}
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate edge extras before building the plan:
for i, e := range plan.Edges {
    if e.Gate != "" && !validGateSyntax(e.Gate) {
        return fmt.Errorf("edge %d: malformed gate %q", i, e.Gate)
    }
    if e.SpawnerKey != "" {
        if _, ok := keyToID[e.SpawnerKey]; !ok { return fmt.Errorf("edge %d: spawner %q unresolved", i, e.SpawnerKey) }
    }
}

Type guard

func edgeConstructible(e GraphEdge, keyToID map[string]string) bool {
    if e.Gate != "" && !validGateSyntax(e.Gate) { return false }
    if e.SpawnerKey != "" {
        if _, ok := keyToID[e.SpawnerKey]; !ok { return false }
    }
    return true
}

Try / catch

if err := applyGraphEdges(ctx, plan); err != nil {
    if strings.HasPrefix(err.Error(), "applyGraph: edge ") && !strings.Contains(err.Error(), "->") {
        // constructor-stage failure: fix the named edge's gate/spawner/thread fields
        idx := parseEdgeIndex(err.Error())
        log.Printf("edge %d rejected by NewGraphEdgeDependency: %v", idx, err)
    }
    return err
}

Prevention

When it happens

Trigger: NewGraphEdgeDependency returns an error for edge i: invalid gate spec, spawner key/id unresolvable, unsupported type/gate combination, or malformed thread reference in the plan edge.

Common situations: Hand-authored gate expressions with syntax errors; spawner key pointing at an issue not in the plan; a beads version mismatch where the client emits edge fields the constructor rejects; thread ID copied from another issue.

Related errors


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