{"record":{"id":"5abfe4fe5e6e36c6","repo":"gastownhall/beads","slug":"node-q-storage-class-ephemeral-and-no-history-ar","errorCode":null,"errorMessage":"node %q: storage_class ephemeral and no_history are mutually exclusive","messagePattern":"node %q: storage_class ephemeral and no_history are mutually exclusive","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/graph_apply.go","lineNumber":908,"sourceCode":"\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 = \"\"\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)","sourceCodeStart":890,"sourceCodeEnd":926,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/graph_apply.go#L890-L926","documentation":"If a node resolves to storage class `ephemeral`, it is placed on the wisp plane, which by definition has no durable history. Requesting no_history alongside that is redundant-conflicting, so validation rejects it. This is the storage_class-based path of the same exclusivity rule as error 710.","triggerScenarios":"Applying a graph plan where a node has `storage_class: \"ephemeral\"` and `no_history: true`, or inherits ephemeral storage class while the no_history flag/option is set.","commonSituations":"Combining per-node storage_class with global --no-history; type-default configs mapping a type to ephemeral while the plan sets no_history; copy-pasted retention settings.","solutions":["Drop `no_history` from the node (or unset the --no-history option) since storage_class ephemeral already implies it.","Change storage_class to a non-ephemeral value if you need no_history with durable storage.","Review type-default storage_class configuration that may implicitly make nodes ephemeral."],"exampleFix":"// before\n{\"key\": \"bd-123\", \"storage_class\": \"ephemeral\", \"no_history\": true}\n// after\n{\"key\": \"bd-123\", \"storage_class\": \"ephemeral\"}","handlingStrategy":"validation","validationCode":"for _, n := range plan.Nodes {\n  if n.StorageClass == \"ephemeral\" && n.NoHistory != nil && *n.NoHistory {\n    return fmt.Errorf(\"node %q: drop no_history when storage_class is ephemeral\", n.Key)\n  }\n}","typeGuard":"func ephemeralWithNoHistory(n Node) bool {\n  return n.StorageClass == \"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    plan = stripNoHistoryFromEphemeralNodes(plan); return bd.GraphApply(ctx, plan)\n  }\n}","preventionTips":["Treat storage_class ephemeral as implying no_history; never set both.","Avoid global --no-history when per-node storage_class defaults to ephemeral.","Lint plans for redundant retention flags."],"tags":["cli","graph-apply","storage-class","validation"],"backgroundTag":"mutually-exclusive-options","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}