{"record":{"id":"10f4dbc98fe0e4db","repo":"gastownhall/beads","slug":"node-q-storage-class-s-conflicts-with-ephemeral","errorCode":null,"errorMessage":"node %q: storage_class %s conflicts with ephemeral/no_history: wisp-plane records are storage class ephemeral","messagePattern":"node %q: storage_class (.+?) conflicts with ephemeral/no_history: wisp-plane records are storage class ephemeral","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/graph_apply.go","lineNumber":926,"sourceCode":"\t\t\treturn false, false, \"\", fmt.Errorf(\"node %q: storage_class ephemeral and no_history are mutually exclusive\", node.Key)\n\t\t}\n\t\tif node.Ephemeral != nil && !*node.Ephemeral {\n\t\t\treturn false, false, \"\", fmt.Errorf(\"node %q: storage_class ephemeral conflicts with ephemeral: false\", node.Key)\n\t\t}\n\t\tephemeral = true\n\t\tclass = \"\"\n\t}\n\t// Reconcile the requested durable class with the effective wisp plane\n\t// (flag > config, Protocol v0.1 §C1.3): an explicit node storage_class\n\t// contradicts an ephemeral/no_history node and is rejected, so the durable\n\t// intent is preserved rather than silently collapsed into an\n\t// effective-ephemeral record; a per-type config default yields to the\n\t// effective plane. versioned normalizes to the unset marker only after the\n\t// check (C2.4).\n\tvar conflict bool\n\tclass, conflict = reconcileStorageClassPlane(class, node.StorageClass != \"\", ephemeral || noHistory)\n\tif conflict {\n\t\treturn false, false, \"\", fmt.Errorf(\"node %q: storage_class %s conflicts with ephemeral/no_history: wisp-plane records are storage class ephemeral\", node.Key, class)\n\t}\n\treturn ephemeral, noHistory, class, nil\n}\n\nfunc executeGraphApply(ctx context.Context, plan *GraphApplyPlan, opts GraphApplyOptions) (*GraphApplyResult, error) {\n\tif err := opts.Validate(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tkeyToID := make(map[string]string, len(plan.Nodes))\n\towner := getOwner()\n\n\tcommitMsg := plan.CommitMessage\n\tif commitMsg == \"\" {\n\t\tcommitMsg = fmt.Sprintf(\"bd: graph-apply %d nodes\", len(plan.Nodes))\n\t}\n\n\tif err := store.RunInTransaction(ctx, commitMsg, func(tx storage.Transaction) error {","sourceCodeStart":908,"sourceCodeEnd":944,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/graph_apply.go#L908-L944","documentation":"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.","triggerScenarios":"Applying a node with `ephemeral: true` and/or `no_history: true` while also carrying an explicit non-ephemeral `storage_class` (e.g. `versioned`).","commonSituations":"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.","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."],"exampleFix":"// before\n{\"key\": \"bd-123\", \"no_history\": true, \"storage_class\": \"versioned\"}\n// after\n{\"key\": \"bd-123\", \"no_history\": true}","handlingStrategy":"validation","validationCode":"for _, n := range plan.Nodes {\n  wisp := (n.Ephemeral != nil && *n.Ephemeral) || (n.NoHistory != nil && *n.NoHistory)\n  if wisp && n.StorageClass != \"\" && n.StorageClass != \"ephemeral\" {\n    return fmt.Errorf(\"node %q: wisp-plane nodes must not carry durable storage_class\", n.Key)\n  }\n}","typeGuard":"func wispPlaneWithDurableClass(n Node) bool {\n  wisp := (n.Ephemeral != nil && *n.Ephemeral) || (n.NoHistory != nil && *n.NoHistory)\n  return wisp && n.StorageClass != \"\" && n.StorageClass != \"ephemeral\"\n}","tryCatchPattern":"if err := bd.GraphApply(ctx, plan); err != nil {\n  if strings.Contains(err.Error(), \"wisp-plane records are storage class ephemeral\") {\n    plan = clearStorageClassOnWispNodes(plan); return bd.GraphApply(ctx, plan)\n  }\n}","preventionTips":["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."],"tags":["cli","graph-apply","storage-class","wisp-plane"],"backgroundTag":"conflicting-storage-settings","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}