gastownhall/beads · error
cannot move unversioned record to persistence mode %q
Error message
cannot move unversioned record to persistence mode %q
What it means
An unversioned record is frozen in persistent mode: once StorageClass is 'unversioned', it cannot be moved to ephemeral or no_history (wisp modes). Normalization rejects any mode other than persistent for such records, as documented — an unversioned record may remain persistent but cannot move to a wisp mode.
Source
Thrown at internal/types/types.go:975
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 {
storageClass = ""
}
return true, false, storageClass, nilView on GitHub (pinned to 71377f2769)
Solutions
- Keep the record in persistent mode, or recreate the record as a new ephemeral/no_history issue and close the unversioned one
- Check StorageClass == StorageClassUnversioned before requesting a mode change and skip/branch accordingly
- Change the storage class policy for the record first if the workflow truly requires wisp conversion (if supported upstream)
Example fix
// before
NormalizePersistenceMode(issue, PersistenceModeEphemeral) // issue.StorageClass == unversioned
// after
if issue.StorageClass != StorageClassUnversioned {
NormalizePersistenceMode(issue, PersistenceModeEphemeral)
} Defensive patterns
Strategy: validation
Validate before calling
if issue.StorageClass == StorageClassUnversioned && mode != PersistenceModePersistent {
return fmt.Errorf("issue %s is unversioned and cannot move to %s", issue.ID, mode)
} Type guard
func canChangeMode(i Issue, m PersistenceMode) bool {
return i.StorageClass != StorageClassUnversioned || m == PersistenceModePersistent
} Try / catch
res, err := NormalizePersistenceMode(issue, mode)
if err != nil && strings.Contains(err.Error(), "cannot move unversioned record") {
// keep persistent or recreate as new wisp issue
} Prevention
- Filter out unversioned records in any bulk demotion to wisp modes
- Check storage class before issuing persistence-mode updates
- Document that unversioned records are mode-frozen in persistent
When it happens
Trigger: Calling NormalizePersistenceMode on an Issue whose current.StorageClass == StorageClassUnversioned with mode != PersistenceModePersistent — e.g. attempting to demote it to ephemeral or no_history.
Common situations: Scripted cleanup jobs that sweep records into wisp modes without checking storage class; users trying to demote an unversioned issue via flags; automation assuming all records can become wisps.
Related errors
- ephemeral and no_history are mutually exclusive
- persist sync.remote to config.yaml: %w
- failed to persist sync.remote to config.yaml: %w
- failed to update gate with discovered run ID: %w
- failed to update gate with discovered run ID: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/9a10ccdef30e7355.
Report an issue: GitHub.