gastownhall/beads · error

edge %d: must specify from_key or from_id

Error message

edge %d: must specify from_key or from_id

What it means

Every plan edge must identify its source, either via from_key or from_id. An edge with both empty cannot be anchored and validation rejects it with the edge index.

Source

Thrown at cmd/bd/graph_apply.go:600

			}
			if dep.Type != "" {
				dt := types.DependencyType(dep.Type)
				if !dt.IsValid() {
					return fmt.Errorf("node %q: dep %d: invalid dependency type %q", node.Key, j, dep.Type)
				}
			}
		}
	}

	for i, edge := range plan.Edges {
		if edge.FromKey != "" && !seenKeys[edge.FromKey] {
			return fmt.Errorf("edge %d: from key %q not found in plan", i, edge.FromKey)
		}
		if edge.ToKey != "" && !seenKeys[edge.ToKey] {
			return fmt.Errorf("edge %d: to key %q not found in plan", i, edge.ToKey)
		}
		if edge.FromKey == "" && edge.FromID == "" {
			return fmt.Errorf("edge %d: must specify from_key or from_id", i)
		}
		if edge.ToKey == "" && edge.ToID == "" {
			return fmt.Errorf("edge %d: must specify to_key or to_id", i)
		}
		if edge.Type != "" {
			dt := types.DependencyType(edge.Type)
			if !dt.IsValid() {
				return fmt.Errorf("edge %d: invalid dependency type %q", i, edge.Type)
			}
		}
		if edge.Gate != "" || edge.SpawnerKey != "" || edge.SpawnerID != "" {
			if graphApplyDependencyType(edge.Type) != types.DepWaitsFor {
				return fmt.Errorf("edge %d: gate/spawner fields require type %q", i, types.DepWaitsFor)
			}
			if edge.Gate != "" && !types.IsValidWaitsForGate(edge.Gate) {
				return fmt.Errorf("edge %d: invalid gate %q (valid: %s, %s)", i, edge.Gate, types.WaitsForAllChildren, types.WaitsForAnyChildren)
			}
			if edge.SpawnerKey != "" && edge.SpawnerID != "" {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Set from_key to the source node's key in the plan
  2. Alternatively set from_id to an explicit issue id
  3. Remove the edge if it is spurious

Example fix

// before
{"edges": [{"from_key": "", "from_id": "", "to_key": "deploy"}]}
// after
{"edges": [{"from_key": "build", "to_key": "deploy"}]}
Defensive patterns

Strategy: validation

Validate before calling

for i, e := range plan.Edges {
	if e.FromKey == "" && e.FromID == "" { return fmt.Errorf("edge %d: missing source", i) }
	if e.ToKey == "" && e.ToID == "" { return fmt.Errorf("edge %d: missing target", i) }
}

Try / catch

if err := bd.GraphApply(ctx, plan, opts); err != nil {
	if strings.Contains(err.Error(), "must specify from_key or from_id") {
		// add a source to the edge and retry
	}
}

Prevention

When it happens

Trigger: `bd graph apply` with plan.edges[i] that has neither from_key nor from_id set.

Common situations: Generated edge entries with placeholder/empty source fields; hand-written edges copying only the target side; destructuring errors in scripts building edges.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


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