gastownhall/beads · error
node %q: storage_class %s conflicts with ephemeral/no_histor
Error message
node %q: storage_class %s conflicts with ephemeral/no_history: wisp-plane records are storage class ephemeral
What it means
After flag reconciliation, `reconcileStorageClassPlane` checks the requested durable storage_class against the effective wisp plane (node is ephemeral or no_history). Wisp-plane records are inherently storage class ephemeral, so a non-empty explicit storage_class that conflicts is rejected per Protocol v0.1 §C1.3/C2.4.
Source
Thrown at cmd/bd/graph_apply.go:926
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
}
func executeGraphApply(ctx context.Context, plan *GraphApplyPlan, opts GraphApplyOptions) (*GraphApplyResult, error) {
if err := opts.Validate(); err != nil {
return nil, err
}
keyToID := make(map[string]string, len(plan.Nodes))
owner := getOwner()
commitMsg := plan.CommitMessage
if commitMsg == "" {
commitMsg = fmt.Sprintf("bd: graph-apply %d nodes", len(plan.Nodes))
}
if err := store.RunInTransaction(ctx, commitMsg, func(tx storage.Transaction) error {View on GitHub (pinned to 71377f2769)
Solutions
- Remove the explicit storage_class from wisp-plane nodes (ephemeral/no_history) and let the plane imply ephemeral.
- Drop ephemeral/no_history from the node if the durable storage_class is what you want.
- Align the plan generator with Protocol v0.1 §C1.3 so wisp-plane nodes never carry a durable storage_class.
Example fix
// before
{"key": "bd-123", "no_history": true, "storage_class": "versioned"}
// after
{"key": "bd-123", "no_history": true} Defensive patterns
Strategy: validation
Validate before calling
for _, n := range plan.Nodes {
wisp := (n.Ephemeral != nil && *n.Ephemeral) || (n.NoHistory != nil && *n.NoHistory)
if wisp && n.StorageClass != "" && n.StorageClass != "ephemeral" {
return fmt.Errorf("node %q: wisp-plane nodes must not carry durable storage_class", n.Key)
}
} Type guard
func wispPlaneWithDurableClass(n Node) bool {
wisp := (n.Ephemeral != nil && *n.Ephemeral) || (n.NoHistory != nil && *n.NoHistory)
return wisp && n.StorageClass != "" && n.StorageClass != "ephemeral"
} Try / catch
if err := bd.GraphApply(ctx, plan); err != nil {
if strings.Contains(err.Error(), "wisp-plane records are storage class ephemeral") {
plan = clearStorageClassOnWispNodes(plan); return bd.GraphApply(ctx, plan)
}
} Prevention
- Wisp-plane nodes (ephemeral/no_history) should carry no explicit storage_class.
- Keep per-type storage_class defaults out of ephemeral node types.
- Follow Protocol v0.1 §C1.3/C2.4 in plan generators.
When it happens
Trigger: Applying a node with `ephemeral: true` and/or `no_history: true` while also carrying an explicit non-ephemeral `storage_class` (e.g. `versioned`).
Common situations: Per-type config defaults injecting storage_class while the plan sets no_history; combining template nodes (storage_class set) with ephemeral batching flags; protocol version mismatches between plan generator and bd.
Related errors
- node %q: storage_class ephemeral and no_history are mutually
- node %q: storage_class ephemeral conflicts with ephemeral: f
- node %q: ephemeral and no_history are mutually exclusive
- batch create: %w
- node %q: updating metadata refs: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/10f4dbc98fe0e4db.
Report an issue: GitHub.