gastownhall/beads · error

invalid persistence mode %q

Error message

invalid persistence mode %q

What it means

NormalizePersistenceMode validates the requested PersistenceMode against the known set (persistent, ephemeral, no_history). An unrecognized string mode is rejected before any state change. This guards against typos in flags or config.

Source

Thrown at internal/types/types.go:959

	PersistenceModeEphemeral,
	PersistenceModeNoHistory,
}

// IsValid reports whether a persistence mode is a known explicit value.
// Empty is invalid because it would be indistinguishable from an omitted mode.
func (m PersistenceMode) IsValid() bool {
	return slices.Contains(validPersistenceModes, m)
}

// 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 {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Use one of the exported PersistenceMode constants instead of a raw string
  2. Check the accepted values via PersistenceMode's IsValid/parse helpers and correct the spelling
  3. Upgrade/align code after a mode rename in a newer library version

Example fix

// before
NormalizePersistenceMode(issue, PersistenceMode("persistant"))
// after
NormalizePersistenceMode(issue, PersistenceModePersistent)
Defensive patterns

Strategy: validation

Validate before calling

mode := PersistenceMode(input)
if !mode.IsValid() {
    return fmt.Errorf("%q is not a valid persistence mode", input)
}

Type guard

func validPersistenceMode(s string) (PersistenceMode, bool) {
    m := PersistenceMode(s)
    return m, m.IsValid()
}

Try / catch

res, err := NormalizePersistenceMode(issue, mode)
if err != nil && strings.Contains(err.Error(), "invalid persistence mode") {
    // surface valid options to the user
}

Prevention

When it happens

Trigger: Calling NormalizePersistenceMode(current Issue, mode) with a mode value that fails mode.IsValid() — e.g. mode "persistant" (typo), "", or an arbitrary string cast to PersistenceMode.

Common situations: Typo in a --persistence flag value; old config using a renamed mode value; programmatic code passing a raw string without using the PersistenceMode constants.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/305aab2ba5624fe2. Report an issue: GitHub.