{"record":{"id":"ff0c380c6b5c41ea","repo":"gastownhall/beads","slug":"node-q-ephemeral-and-no-history-are-mutually-exc","errorCode":null,"errorMessage":"node %q: ephemeral and no_history are mutually exclusive","messagePattern":"node %q: ephemeral and no_history are mutually exclusive","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/graph_apply.go","lineNumber":896,"sourceCode":"// unset. \"ephemeral\" is the spelled-out spelling of ephemeral: true (C1.4) —\n// it routes the node to the wisp plane and leaves the marker cell empty\n// (wisp-plane rows derive their class, C1.2). A durable class\n// (versioned/unversioned) combined with an effective wisp-plane node is\n// reconciled by flag-over-config precedence: an explicit node storage_class is\n// rejected rather than silently erased, while a per-type config default yields\n// to the effective plane; versioned normalizes to the unset marker only after\n// that check.\nfunc graphApplyNodeStorageClass(node GraphApplyNode, opts GraphApplyOptions) (ephemeral, noHistory bool, class types.StorageClass, err error) {\n\tephemeral = opts.Ephemeral\n\tif node.Ephemeral != nil {\n\t\tephemeral = *node.Ephemeral\n\t}\n\tnoHistory = opts.NoHistory\n\tif node.NoHistory != nil {\n\t\tnoHistory = *node.NoHistory\n\t}\n\tif ephemeral && noHistory {\n\t\treturn false, false, \"\", fmt.Errorf(\"node %q: ephemeral and no_history are mutually exclusive\", node.Key)\n\t}\n\tissueType := types.IssueType(node.Type)\n\tif issueType == \"\" {\n\t\tissueType = types.TypeTask\n\t}\n\tclass, err = resolveStorageClass(node.StorageClass, issueType.Normalize())\n\tif err != nil {\n\t\treturn false, false, \"\", fmt.Errorf(\"node %q: %w\", node.Key, err)\n\t}\n\tif class == types.StorageClassEphemeral {\n\t\tif noHistory {\n\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 = \"\"","sourceCodeStart":878,"sourceCodeEnd":914,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/graph_apply.go#L878-L914","documentation":"During `bd graph apply`, each plan node's storage settings are validated. A node cannot be both ephemeral and no_history: ephemeral records live on the wisp plane with their own retention rules, so requesting both is contradictory. The error is returned while reconciling node flags before any issue is created, so the whole apply aborts transactionally.","triggerScenarios":"Applying a graph plan where a node sets both `ephemeral: true` and `no_history: true` (or the --ephemeral/--no-history options imply both for the same node).","commonSituations":"Hand-written or AI-generated plan JSON stacking retention flags; template nodes that copied both flags; misunderstanding that no_history is not the way to make a record ephemeral.","solutions":["Remove `no_history: true` from the node, keeping `ephemeral: true` if wisp-plane behavior is desired.","If the node should be durable, remove `ephemeral: true` and keep `no_history: true`.","Check upstream plan generation for code that sets both flags from overlapping options."],"exampleFix":"// before\n{\"key\": \"bd-123\", \"ephemeral\": true, \"no_history\": true}\n// after\n{\"key\": \"bd-123\", \"ephemeral\": true}","handlingStrategy":"validation","validationCode":"for _, n := range plan.Nodes {\n  if n.Ephemeral != nil && *n.Ephemeral && n.NoHistory != nil && *n.NoHistory {\n    return fmt.Errorf(\"node %q: cannot set both ephemeral and no_history\", n.Key)\n  }\n}","typeGuard":"func isExclusiveRetention(n Node) bool {\n  return n.Ephemeral != nil && *n.Ephemeral && n.NoHistory != nil && *n.NoHistory\n}","tryCatchPattern":"if err := bd.GraphApply(ctx, plan); err != nil {\n  if strings.Contains(err.Error(), \"mutually exclusive\") {\n    // sanitize plan: drop no_history from ephemeral nodes, retry once\n  }\n}","preventionTips":["Pick one retention mechanism per node: ephemeral OR no_history, never both.","Validate plan JSON against the graph schema before apply.","Keep plan generators free of overlapping retention flags."],"tags":["cli","graph-apply","validation","config-conflict"],"backgroundTag":"mutually-exclusive-options","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}