{"record":{"id":"7de8c042413a3a45","repo":"gastownhall/beads","slug":"storage-class-q-conflicts-with-ephemeral-no-histo","errorCode":null,"errorMessage":"storage class %q conflicts with ephemeral/no_history","messagePattern":"storage class %q conflicts with ephemeral/no_history","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/types/types.go","lineNumber":969,"sourceCode":"// 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\n\tswitch mode {\n\tcase PersistenceModePersistent:\n\t\tstorageClass := current.StorageClass\n\t\tif storageClass == StorageClassEphemeral {\n\t\t\tstorageClass = \"\"\n\t\t}\n\t\treturn false, false, storageClass, nil","sourceCodeStart":951,"sourceCodeEnd":987,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/types/types.go#L951-L987","documentation":"A record on the wisp plane (Ephemeral or NoHistory set) may only carry the 'ephemeral' storage class (or none). Any other storage class contradicts the wisp flags, so normalization rejects it.","triggerScenarios":"Calling NormalizePersistenceMode with current.Ephemeral || current.NoHistory true while current.StorageClass is a non-empty class other than StorageClassEphemeral (e.g. unversioned or versioned).","commonSituations":"Promoting/demoting a record between planes while a stale storage class from its previous life remains set; code that sets ephemeral but forgets to clear storage class; merging records from different planes.","solutions":["Clear the storage class (or set it to StorageClassEphemeral) when moving an issue to the ephemeral/no_history plane","Run the issue through NormalizePersistenceMode before writing so conflicts are resolved in one place","Fix the update path that toggles wisp flags without updating StorageClass"],"exampleFix":"// before\nissue.Ephemeral = true; issue.StorageClass = StorageClassUnversioned\n// after\nissue.Ephemeral = true; issue.StorageClass = StorageClassEphemeral","handlingStrategy":"validation","validationCode":"if (issue.Ephemeral || issue.NoHistory) && issue.StorageClass != \"\" && issue.StorageClass != StorageClassEphemeral {\n    return fmt.Errorf(\"wisp issue %s carries incompatible class %q\", issue.ID, issue.StorageClass)\n}","typeGuard":"func wispClassConflict(i Issue) bool {\n    return (i.Ephemeral || i.NoHistory) && i.StorageClass != \"\" && i.StorageClass != StorageClassEphemeral\n}","tryCatchPattern":"res, err := NormalizePersistenceMode(issue, mode)\nif err != nil && strings.Contains(err.Error(), \"conflicts with ephemeral/no_history\") {\n    issue.StorageClass = StorageClassEphemeral\n    res, err = NormalizePersistenceMode(issue, mode)\n}","preventionTips":["Whenever setting Ephemeral/NoHistory, update StorageClass in the same change","Normalize issues through NormalizePersistenceMode before persisting","Add round-trip tests for plane demotion/promotion"],"tags":["storage-class","ephemeral","state-conflict"],"backgroundTag":"storage-class-plane-conflict","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}