gastownhall/beads · error
node %q: storage_class ephemeral conflicts with ephemeral: f
Error message
node %q: storage_class ephemeral conflicts with ephemeral: false
What it means
A node explicitly set `storage_class: "ephemeral"` but also `ephemeral: false`. The explicit false contradicts the storage class, so apply aborts. The node cannot simultaneously claim to be ephemeral (via class) and not ephemeral (via flag).
Source
Thrown at cmd/bd/graph_apply.go:911
noHistory = *node.NoHistory
}
if ephemeral && noHistory {
return false, false, "", fmt.Errorf("node %q: ephemeral and no_history are mutually exclusive", node.Key)
}
issueType := types.IssueType(node.Type)
if issueType == "" {
issueType = types.TypeTask
}
class, err = resolveStorageClass(node.StorageClass, issueType.Normalize())
if err != nil {
return false, false, "", fmt.Errorf("node %q: %w", node.Key, err)
}
if class == types.StorageClassEphemeral {
if noHistory {
return false, false, "", fmt.Errorf("node %q: storage_class ephemeral and no_history are mutually exclusive", node.Key)
}
if node.Ephemeral != nil && !*node.Ephemeral {
return false, false, "", fmt.Errorf("node %q: storage_class ephemeral conflicts with ephemeral: false", node.Key)
}
ephemeral = true
class = ""
}
// Reconcile the requested durable class with the effective wisp plane
// (flag > config, Protocol v0.1 §C1.3): an explicit node storage_class
// contradicts an ephemeral/no_history node and is rejected, so the durable
// intent is preserved rather than silently collapsed into an
// effective-ephemeral record; a per-type config default yields to the
// effective plane. versioned normalizes to the unset marker only after the
// check (C2.4).
var conflict bool
class, conflict = reconcileStorageClassPlane(class, node.StorageClass != "", ephemeral || noHistory)
if conflict {
return false, false, "", fmt.Errorf("node %q: storage_class %s conflicts with ephemeral/no_history: wisp-plane records are storage class ephemeral", node.Key, class)
}
return ephemeral, noHistory, class, nil
}View on GitHub (pinned to 71377f2769)
Solutions
- Remove `"ephemeral": false` from the node, keeping storage_class ephemeral.
- Or change storage_class to a durable value if ephemeral:false is the intended state.
- Fix the plan generator so it emits only one of the two settings.
Example fix
// before
{"key": "bd-123", "storage_class": "ephemeral", "ephemeral": false}
// after
{"key": "bd-123", "storage_class": "ephemeral"} Defensive patterns
Strategy: validation
Validate before calling
for _, n := range plan.Nodes {
if n.StorageClass == "ephemeral" && n.Ephemeral != nil && !*n.Ephemeral {
return fmt.Errorf("node %q: ephemeral:false contradicts storage_class ephemeral", n.Key)
}
} Type guard
func contradictsEphemeral(n Node) bool {
return n.StorageClass == "ephemeral" && n.Ephemeral != nil && !*n.Ephemeral
} Try / catch
if err := bd.GraphApply(ctx, plan); err != nil {
if strings.Contains(err.Error(), "conflicts with ephemeral: false") {
// fix plan generator output and retry
}
} Prevention
- Set at most one of ephemeral flag / storage_class per node.
- Have generators emit storage_class OR the boolean, not both.
- Review hand-edited plan JSON for half-removed fields.
When it happens
Trigger: Applying a plan node with both `storage_class: "ephemeral"` and `"ephemeral": false`.
Common situations: Generated plans where one code path sets ephemeral:false by default and another sets storage_class; manual edits flipping the boolean without removing the storage class.
Related errors
- node %q: ephemeral and no_history are mutually exclusive
- node %q: storage_class ephemeral and no_history are mutually
- node %q: storage_class %s conflicts with ephemeral/no_histor
- batch create: %w
- node %q: updating metadata refs: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/765575b8d18742dd.
Report an issue: GitHub.