{"record":{"id":"37b8782806e457e5","repo":"gastownhall/beads","slug":"ephemeral-and-no-history-are-mutually-exclusive-37b878","errorCode":null,"errorMessage":"ephemeral and no_history are mutually exclusive","messagePattern":"ephemeral and no_history are mutually exclusive","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/types/types.go","lineNumber":962,"sourceCode":"\n// IsValid reports whether a persistence mode is a known explicit value.\n// Empty is invalid because it would be indistinguishable from an omitted mode.\nfunc (m PersistenceMode) IsValid() bool {\n\treturn slices.Contains(validPersistenceModes, m)\n}\n\n// NormalizePersistenceMode maps a requested mode from current to its exact\n// persistence fields without mutating an issue. The returned values are\n// Ephemeral, NoHistory, and StorageClass, respectively. Plane and retention\n// changes preserve the create-selected class. Promotion clears an explicit\n// ephemeral class marker to select normalized versioned storage. An\n// unversioned record may remain persistent but cannot move to a wisp mode.\nfunc NormalizePersistenceMode(current Issue, mode PersistenceMode) (bool, bool, StorageClass, error) {\n\tif !mode.IsValid() {\n\t\treturn false, false, \"\", fmt.Errorf(\"invalid persistence mode %q\", mode)\n\t}\n\tif current.Ephemeral && current.NoHistory {\n\t\treturn false, false, \"\", fmt.Errorf(\"ephemeral and no_history are mutually exclusive\")\n\t}\n\tif !current.StorageClass.IsValid() {\n\t\treturn false, false, \"\", fmt.Errorf(\"invalid storage class %q\", current.StorageClass)\n\t}\n\twispPlane := current.Ephemeral || current.NoHistory\n\tif wispPlane && current.StorageClass != \"\" && current.StorageClass != StorageClassEphemeral {\n\t\treturn false, false, \"\", fmt.Errorf(\"storage class %q conflicts with ephemeral/no_history\", current.StorageClass)\n\t}\n\tif !wispPlane && current.StorageClass == StorageClassEphemeral {\n\t\treturn false, false, \"\", fmt.Errorf(\"storage class ephemeral requires ephemeral or no_history\")\n\t}\n\tif current.StorageClass == StorageClassUnversioned && mode != PersistenceModePersistent {\n\t\treturn false, false, \"\", fmt.Errorf(\"cannot move unversioned record to persistence mode %q\", mode)\n\t}\n\tif persistenceMode(current) == mode {\n\t\treturn current.Ephemeral, current.NoHistory, current.StorageClass, nil\n\t}\n","sourceCodeStart":944,"sourceCodeEnd":980,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/types/types.go#L944-L980","documentation":"An issue cannot be both ephemeral and no_history at the same time; these wisp-plane flags are mutually exclusive. NormalizePersistenceMode rejects the combination up front because the resulting storage semantics are contradictory.","triggerScenarios":"Calling NormalizePersistenceMode with current.Ephemeral == true and current.NoHistory == true, e.g. an Issue built by code that sets both booleans, or a record mutated by two overlapping updates.","commonSituations":"Applying two independent updates (one enabling ephemeral, one enabling no_history) without reading the record in between; hand-edited JSONL export; copying flags from two different commands.","solutions":["Clear one of the two flags on the Issue before normalizing (keep ephemeral OR no_history, not both)","Re-fetch the issue and apply a single persistence change","Fix the update path so a persistence change sets one mode, replacing rather than OR-ing flags"],"exampleFix":"// before\nissue.Ephemeral = true; issue.NoHistory = true\n// after\nissue.Ephemeral = true; issue.NoHistory = false","handlingStrategy":"validation","validationCode":"if issue.Ephemeral && issue.NoHistory {\n    return fmt.Errorf(\"issue %s has both ephemeral and no_history set\", issue.ID)\n}","typeGuard":"func hasConflictingWispFlags(i Issue) bool {\n    return i.Ephemeral && i.NoHistory\n}","tryCatchPattern":"res, err := NormalizePersistenceMode(issue, mode)\nif err != nil && strings.Contains(err.Error(), \"mutually exclusive\") {\n    // clear one flag and retry once\n}","preventionTips":["Apply persistence changes through a single update path that replaces, not ORs, flags","Re-fetch the issue before mutating persistence fields","Add an invariant test that no stored issue has both flags set"],"tags":["persistence","ephemeral","state-conflict"],"backgroundTag":"ephemeral-no-history-conflict","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}