gastownhall/beads · error
storage class %q conflicts with ephemeral/no_history
Error message
storage class %q conflicts with ephemeral/no_history
What it means
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.
Source
Thrown at internal/types/types.go:969
// NormalizePersistenceMode maps a requested mode from current to its exact
// persistence fields without mutating an issue. The returned values are
// Ephemeral, NoHistory, and StorageClass, respectively. Plane and retention
// changes preserve the create-selected class. Promotion clears an explicit
// ephemeral class marker to select normalized versioned storage. An
// unversioned record may remain persistent but cannot move to a wisp mode.
func NormalizePersistenceMode(current Issue, mode PersistenceMode) (bool, bool, StorageClass, error) {
if !mode.IsValid() {
return false, false, "", fmt.Errorf("invalid persistence mode %q", mode)
}
if current.Ephemeral && current.NoHistory {
return false, false, "", fmt.Errorf("ephemeral and no_history are mutually exclusive")
}
if !current.StorageClass.IsValid() {
return false, false, "", fmt.Errorf("invalid storage class %q", current.StorageClass)
}
wispPlane := current.Ephemeral || current.NoHistory
if wispPlane && current.StorageClass != "" && current.StorageClass != StorageClassEphemeral {
return false, false, "", fmt.Errorf("storage class %q conflicts with ephemeral/no_history", current.StorageClass)
}
if !wispPlane && current.StorageClass == StorageClassEphemeral {
return false, false, "", fmt.Errorf("storage class ephemeral requires ephemeral or no_history")
}
if current.StorageClass == StorageClassUnversioned && mode != PersistenceModePersistent {
return false, false, "", fmt.Errorf("cannot move unversioned record to persistence mode %q", mode)
}
if persistenceMode(current) == mode {
return current.Ephemeral, current.NoHistory, current.StorageClass, nil
}
switch mode {
case PersistenceModePersistent:
storageClass := current.StorageClass
if storageClass == StorageClassEphemeral {
storageClass = ""
}
return false, false, storageClass, nilView on GitHub (pinned to 71377f2769)
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
Example fix
// before issue.Ephemeral = true; issue.StorageClass = StorageClassUnversioned // after issue.Ephemeral = true; issue.StorageClass = StorageClassEphemeral
Defensive patterns
Strategy: validation
Validate before calling
if (issue.Ephemeral || issue.NoHistory) && issue.StorageClass != "" && issue.StorageClass != StorageClassEphemeral {
return fmt.Errorf("wisp issue %s carries incompatible class %q", issue.ID, issue.StorageClass)
} Type guard
func wispClassConflict(i Issue) bool {
return (i.Ephemeral || i.NoHistory) && i.StorageClass != "" && i.StorageClass != StorageClassEphemeral
} Try / catch
res, err := NormalizePersistenceMode(issue, mode)
if err != nil && strings.Contains(err.Error(), "conflicts with ephemeral/no_history") {
issue.StorageClass = StorageClassEphemeral
res, err = NormalizePersistenceMode(issue, mode)
} Prevention
- Whenever setting Ephemeral/NoHistory, update StorageClass in the same change
- Normalize issues through NormalizePersistenceMode before persisting
- Add round-trip tests for plane demotion/promotion
When it happens
Trigger: Calling NormalizePersistenceMode with current.Ephemeral || current.NoHistory true while current.StorageClass is a non-empty class other than StorageClassEphemeral (e.g. unversioned or versioned).
Common situations: 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.
Related errors
- storage class ephemeral requires ephemeral or no_history
- ephemeral and no_history are mutually exclusive
- invalid key %q: expected storage-class.<issue-type> (e.g. st
- config storage-class.%s: %w
- node %q: storage_class ephemeral and no_history are mutually
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/7de8c042413a3a45.
Report an issue: GitHub.