gastownhall/beads · error

invalid key %q: expected storage-class.<issue-type> (e.g. st

Error message

invalid key %q: expected storage-class.<issue-type> (e.g. storage-class.event)

What it means

Set-time validation for storage-class config keys: the key must be `storage-class.<issue-type>` with exactly one dot and a non-empty suffix naming a canonical issue type. Anything else (empty suffix, extra dots like `storage-class.a.b`) is rejected when the config value is being set, per Protocol v0.1 C-OQ1.

Source

Thrown at cmd/bd/config.go:986

// they are derived from the tracker registry at runtime via
// allRecognizedConfigPrefixes, so the recognizer cannot drift out of sync when
// a new tracker is added (GH#4427).
var recognizedConfigPrefixes = []string{
	"export.", "import.", "dolt.", "custom.",
	"status.", "types.", "doctor.suppress.", "routing.", "sync.", "git.",
	"directory.", "repos.", "external_projects.", "validation.",
	"lint.", "hierarchy.", "ai.", "backup.", "federation.", "metrics.",
	"agent.", "claim.", "storage-class.",
}

// validateStorageClassConfig validates a storage-class.<type> per-type
// default at config-set time (Protocol v0.1 C-OQ1: values are validated when
// set, not discovered broken at create time). The key suffix must name an
// issue type and the value must be a storage class.
func validateStorageClassConfig(key, value string) error {
	suffix := strings.TrimPrefix(key, "storage-class.")
	if suffix == "" || strings.Contains(suffix, ".") {
		return fmt.Errorf("invalid key %q: expected storage-class.<issue-type> (e.g. storage-class.event)", key)
	}
	// The key suffix must be a canonical, known issue type: create-time lookup
	// keys on the Normalize()d type (resolveStorageClass), so an alias like
	// storage-class.feat or a typo like storage-class.taks would pass set-time
	// validation and then silently never match — the C-OQ1 failure mode this
	// validator exists to prevent.
	issueType := types.IssueType(suffix)
	if canonical := issueType.Normalize(); canonical != issueType {
		return fmt.Errorf("invalid key %q: %q is an alias of %q, and create-time lookup uses the canonical type; set storage-class.%s instead", key, suffix, canonical, canonical)
	}
	if !issueType.IsValidWithCustom(loadEmbeddedCustomTypes()) {
		return fmt.Errorf("invalid key %q: unknown issue type %q (use a built-in type, or add it to types.custom first)", key, suffix)
	}
	if _, err := types.ParseStorageClass(value); err != nil {
		return err
	}
	return nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Use the form storage-class.<issue-type>, e.g. `bd config set storage-class.event ephemeral`
  2. Remove any extra dot-separated segments from the key
  3. Confirm the issue-type suffix is canonical (not an alias like 'feat') — a later check also rejects aliases/typos
  4. Run `bd config list` to see existing valid keys as examples

Example fix

// before
bd config set storage-class.bug.extra ephemeral
// after
bd config set storage-class.bug ephemeral
Defensive patterns

Strategy: validation

Validate before calling

# validate key shape before set
case "$KEY" in
  storage-class.[a-z]*) : ;;
  *) echo "invalid: use storage-class.<issue-type>"; exit 1 ;;
esac

Prevention

When it happens

Trigger: Running `bd config set storage-class.<something> <value>` where the suffix is empty (`storage-class.`) or contains another dot — e.g. `storage-class.bug.extra` or a bare `storage-class.` key.

Common situations: Typo with a trailing dot, nesting keys by habit (`storage-class.chore.subtype`), or scripting config sets with a malformed key template.

Related errors


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