gastownhall/beads · error · issueops.ErrValidation

%w: bootstrap requires a non-empty issue prefix

Error message

%w: bootstrap requires a non-empty issue prefix

What it means

ValidateBootstrapRequest wraps issueops.ErrValidation when the bootstrap request's issue prefix is empty after normalization. Bootstrap mints issues under a prefix, so an empty prefix would create unnameable issues; the validator runs before any transaction opens, guaranteeing "a refusal writes nothing".

Source

Thrown at internal/workapi/bootstrap.go:53

//
// The settings plane strips a trailing hyphen from this key. Doing it here
// instead of relying on that means BootstrapResult.Prefix and the value a later
// VerifyIdentity answers with are one value on every backend, including the
// unit-of-work one whose write does not pass through that plane's normalizer.
func NormalizeBootstrapPrefix(prefix string) string {
	return strings.TrimRight(prefix, "-")
}

// ValidateBootstrapRequest refuses a bootstrap that cannot produce a usable
// workspace and returns the request as it will be WRITTEN.
//
// Every implementation validates through it BEFORE opening a transaction, which
// is what makes issueops.Bootstrapper's "a refusal writes nothing" true of the
// connection as well as of the keys.
func ValidateBootstrapRequest(req issueops.BootstrapRequest) (issueops.BootstrapRequest, error) {
	req.Prefix = NormalizeBootstrapPrefix(req.Prefix)
	if req.Prefix == "" {
		return issueops.BootstrapRequest{}, fmt.Errorf("%w: bootstrap requires a non-empty issue prefix", issueops.ErrValidation)
	}
	if req.ProjectID == "" {
		return issueops.BootstrapRequest{}, fmt.Errorf("%w: bootstrap requires a project id; this role does not mint one", issueops.ErrValidation)
	}
	return req, nil
}

// RefuseIdentifiedSubstrate turns the pair a bootstrap read into the refusal
// issueops.Bootstrapper promises, or nil when the substrate is free.
//
// EITHER MARKER IS ENOUGH. A substrate several rigs share carries a prefix one
// of them chose, and a bootstrap that overwrote it would rename every id the
// others are about to mint; a substrate carrying only a project id is either a
// server-provisioned one bd must adopt rather than stamp, or a bootstrap that
// failed partway, and bd cannot tell those apart. The error carries both values
// so its caller can.
func RefuseIdentifiedSubstrate(prefix, projectID string) error {
	if prefix == "" && projectID == "" {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Set a non-empty prefix on the BootstrapRequest, e.g. Prefix: "bd"
  2. Check the config/env source feeding Prefix for empty or hyphen-only values
  3. Check errors.Is(err, issueops.ErrValidation) to distinguish this refusal from backend failures

Example fix

// before
req := issueops.BootstrapRequest{Prefix: cfg.IssuePrefix, ProjectID: cfg.ProjectID}
// after
if strings.Trim(cfg.IssuePrefix, "-") == "" { return fmt.Errorf("config issuePrefix must be non-empty") }
req := issueops.BootstrapRequest{Prefix: cfg.IssuePrefix, ProjectID: cfg.ProjectID}
Defensive patterns

Strategy: validation

Validate before calling

if strings.Trim(req.Prefix, "-") == "" {
    return fmt.Errorf("bootstrap prefix must be set (got %q)", req.Prefix)
}

Try / catch

normalized, err := workapi.ValidateBootstrapRequest(req)
if errors.Is(err, issueops.ErrValidation) {
    return fmt.Errorf("config bootstrap error: %w", err)
}

Prevention

When it happens

Trigger: Calling any issueops Bootstrapper implementation with BootstrapRequest{Prefix: ""} (or a prefix that normalizes to empty, e.g. only a hyphen "-").

Common situations: Config file missing the prefix key; environment variable interpolation producing an empty string; YAML value set to just "-" which NormalizeBootstrapPrefix strips.

Related errors


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