gastownhall/beads · error

invalid graph plan: %w

Error message

invalid graph plan: %w

What it means

validateProxiedGraphPlan rejected the graph plan inside the transaction. The wrapped error says what is invalid: mixed storage classes, nonexistent dep targets, ID collisions, or malformed nodes. Nothing is committed — the transaction rolls back.

Source

Thrown at cmd/bd/create_proxied_server.go:345

	commitMsg := plan.CommitMessage
	if commitMsg == "" {
		commitMsg = fmt.Sprintf("bd: graph-apply %d nodes", len(plan.Nodes))
	}

	res, err := uow.RunTxResult(ctx, uowProvider, func(ctx context.Context, uw uow.UnitOfWork) (map[string]string, string, error) {
		cctx, err := uw.ConfigUseCase().LoadCreateContext(ctx)
		if err != nil {
			return nil, "", fmt.Errorf("load create context: %w", err)
		}

		// validateProxiedGraphPlan enforces a uniform storage class, so its
		// resolved useWisp decides which table the whole plan routes to. The
		// collision preflight runs inside this transaction, so it cannot race
		// a concurrent create of the same explicit ID.
		useWisp, err := validateProxiedGraphPlan(&plan, in, cctx, uowIssueExists(ctx, uw))
		if err != nil {
			return nil, "", fmt.Errorf("invalid graph plan: %w", err)
		}

		var result domain.GraphApplyResult
		var applyErr error
		if useWisp {
			result, applyErr = uw.IssueUseCase().ApplyWispGraph(ctx, domainPlan, in.createdBy)
		} else {
			result, applyErr = uw.IssueUseCase().ApplyIssueGraph(ctx, domainPlan, in.createdBy)
		}
		if applyErr != nil {
			return nil, "", fmt.Errorf("graph create: %w", applyErr)
		}

		return result.IDs, commitMsg, nil
	})
	if err != nil {
		return HandleError("%v", err)
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Fix the wrapped validation error in the graph file
  2. Ensure every dep target exists in the plan or the database
  3. Use a uniform storage class (all wisps or all issues) per plan
  4. De-duplicate explicit IDs across plan nodes

Example fix

// before: dep target not in plan or DB
{"nodes":[{"id":"bd-1","deps":[{"target":"bd-404"}]}]}
// after: reference an existing node
{"nodes":[{"id":"bd-1","deps":[{"target":"bd-2"}]},{"id":"bd-2"}]}
Defensive patterns

Strategy: validation

Validate before calling

// client-side pre-check before graph-apply
ids := map[string]bool{}
for _, n := range plan.Nodes {
    if ids[n.ID] { return fmt.Errorf("duplicate id %s", n.ID) }
    ids[n.ID] = true
}
for _, n := range plan.Nodes {
    for _, d := range n.Deps {
        if !ids[d.Target] && !existsOnServer(d.Target) {
            return fmt.Errorf("dep target %s not found", d.Target)
        }
    }
}

Prevention

When it happens

Trigger: runCreateProxiedGraph applies a plan whose nodes fail validation: inconsistent useWisp routing, dep on a nonexistent ID, duplicate explicit IDs, or invalid node fields (checked via uowIssueExists preflight).

Common situations: Hand-written or generated graph JSON referencing IDs not in the plan or DB; plan mixing wisps and issues; alias/dep typo in the graph file.

Related errors


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