gastownhall/beads · error
node %q: setting assignee: %w
Error message
node %q: setting assignee: %w
What it means
After dependencies are applied, graph apply sets each node's deferred assignee via tx.UpdateIssue. This error wraps a failure of that update — the dependencies were written but the assignee field could not be persisted — and the whole transaction (including the deps) is rolled back.
Source
Thrown at cmd/bd/graph_apply.go:1097
if graphApplySchedulingDependencyType(d.Type) {
newSchedulingEdges = append(newSchedulingEdges, [2]string{d.IssueID, d.DependsOnID})
}
}
}
}
if cyclePath, err := tx.CycleThroughEdges(ctx, newSchedulingEdges); err != nil {
return fmt.Errorf("final graph cycle check: %w", err)
} else if cyclePath != "" {
return fmt.Errorf("graph dependency cycle would be created: %s", cyclePath)
}
// Apply deferred assignees.
for i, assignee := range pendingAssignees {
updates := map[string]interface{}{
"assignee": assignee,
}
if err := tx.UpdateIssue(ctx, issues[i].ID, updates, actor); err != nil {
return fmt.Errorf("node %q: setting assignee: %w", plan.Nodes[i].Key, err)
}
}
return nil
}); err != nil {
return nil, err
}
return &GraphApplyResult{IDs: keyToID}, nil
}
// validateGraphApplyPlannedBlockingCycles rejects planned blocking edges that
// would close a blocking-dependency cycle, evaluated whole-graph before any
// insert. This early preflight is restricted to blocking edges for precise
// plan errors. Each stored edge still runs issueops.CheckDependencyCycleInTx,
// which enforces the combined blocks + conditional-blocks + parent-child graph.
func validateGraphApplyPlannedBlockingCycles(ctx context.Context, tx storage.Transaction, plan *GraphApplyPlan, keyToID map[string]string) error {
type plannedEdge struct {View on GitHub (pinned to 71377f2769)
Solutions
- Check the wrapped error for the storage-level cause
- Verify the assignee value is a valid identity accepted by the backend
- Ensure no concurrent process is modifying the same issues during apply
- Retry the apply once storage is healthy — the transaction guarantees atomicity
Example fix
// before
{"assignee":""} // empty value rejected
// after
{"assignee":"alice"} Defensive patterns
Strategy: try-catch
Validate before calling
for i, a := range pendingAssignees {
if !validAssignee(a) { return fmt.Errorf("invalid assignee %q for node %q", a, plan.Nodes[i].Key) }
} Type guard
func assigneesValid(plan Plan) bool {
return every(plan.Nodes, func(n Node) bool { return n.Assignee == "" || validAssignee(n.Assignee) })
} Try / catch
if err := bd.GraphApply(ctx, plan); err != nil {
if strings.Contains(err.Error(), "setting assignee") {
// transaction rolled back; fix assignee values and retry
return bd.GraphApply(ctx, sanitizeAssignees(plan))
}
return err
} Prevention
- Validate assignee identifiers before applying
- Avoid concurrent issue updates during graph apply
- Remember applies are atomic — a late assignee failure rolls back all deps, so retry is safe
When it happens
Trigger: tx.UpdateIssue(ctx, issues[i].ID, {"assignee": assignee}, actor) errors during the pendingAssignees loop, e.g. storage failure, issue row missing, or an invalid assignee value rejected by the backend.
Common situations: Assignee values violating DB constraints, storage connectivity problems late in a long transaction, or concurrent writers deleting/altering the issue mid-apply.
Related errors
- node %q: adding parent-child dep: %w
- adding edge %s->%s: %w
- node %q: adding dep to %q: %w
- final graph cycle check: %w
- graph create: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/a274985dfa3b3ffe.
Report an issue: GitHub.