gastownhall/beads · error
storage class ephemeral requires ephemeral or no_history
Error message
storage class ephemeral requires ephemeral or no_history
What it means
The ephemeral storage class only makes sense for records on the wisp plane. A persistent, versioned record (Ephemeral and NoHistory false) cannot claim storage class 'ephemeral', so normalization rejects the state.
Source
Thrown at internal/types/types.go:972
// 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, nil
case PersistenceModeEphemeral:
storageClass := current.StorageClass
if storageClass == StorageClassVersioned {View on GitHub (pinned to 71377f2769)
Solutions
- Clear StorageClass (letting it default to versioned) when promoting the record out of the wisp plane
- Set StorageClass to a persistent-appropriate class (versioned/unversioned) along with clearing the wisp flags
- Always apply plane changes through NormalizePersistenceMode rather than setting fields directly
Example fix
// before issue.Ephemeral = false; issue.StorageClass = StorageClassEphemeral // after issue.Ephemeral = false; issue.StorageClass = ""
Defensive patterns
Strategy: validation
Validate before calling
if !issue.Ephemeral && !issue.NoHistory && issue.StorageClass == StorageClassEphemeral {
return fmt.Errorf("persistent issue %s has ephemeral storage class", issue.ID)
} Type guard
func orphanedEphemeralClass(i Issue) bool {
return !i.Ephemeral && !i.NoHistory && i.StorageClass == StorageClassEphemeral
} Try / catch
res, err := NormalizePersistenceMode(issue, mode)
if err != nil && strings.Contains(err.Error(), "requires ephemeral or no_history") {
issue.StorageClass = ""
res, err = NormalizePersistenceMode(issue, mode)
} Prevention
- Clear the ephemeral class marker when promoting a wisp to persistent
- Never clear wisp flags without also normalizing StorageClass
- Promote records only via the library's promotion path, not direct field edits
When it happens
Trigger: Calling NormalizePersistenceMode with current.Ephemeral == false, current.NoHistory == false, and current.StorageClass == StorageClassEphemeral — e.g. a record demoted to ephemeral earlier and then flipped back to persistent without clearing the class.
Common situations: Promoting a wisp back to a persistent record while its ephemeral marker was left behind; manual edits to exported data; code paths that clear Ephemeral but not StorageClass.
Related errors
- storage class %q conflicts with ephemeral/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/d71dbd79d06019d6.
Report an issue: GitHub.