gastownhall/beads · error · issueops.ErrValidation

%w: bootstrap requires a project id; this role does not mint

Error message

%w: bootstrap requires a project id; this role does not mint one

What it means

ValidateBootstrapRequest wraps issueops.ErrValidation when ProjectID is empty. The bootstrap role expects the project (or equivalent substrate identifier) to already exist and be supplied by the caller; it does not mint one itself. Like the prefix check, this fires before any transaction opens.

Source

Thrown at internal/workapi/bootstrap.go:56

// 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 == "" {
		return nil
	}
	return &issueops.AlreadyIdentifiedError{Prefix: prefix, ProjectID: projectID}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Create or look up the project first, then pass its id in BootstrapRequest.ProjectID
  2. Verify the config/env wiring actually populates ProjectID before calling bootstrap
  3. If you expected the library to create the project, note this role intentionally refuses to mint one — do that step in your setup code

Example fix

// before
req := issueops.BootstrapRequest{Prefix: "bd"} // ProjectID missing
// after
projectID := ensureProjectExists(ctx, client)
req := issueops.BootstrapRequest{Prefix: "bd", ProjectID: projectID}
Defensive patterns

Strategy: validation

Validate before calling

if req.ProjectID == "" {
    return fmt.Errorf("project must be created/selected before bootstrap")
}

Try / catch

req, err := workapi.ValidateBootstrapRequest(req)
if errors.Is(err, issueops.ErrValidation) && strings.Contains(err.Error(), "project id") {
    projectID, lookupErr := ensureProject(ctx)
    if lookupErr != nil { return lookupErr }
    return retryWithProjectID(projectID)
}

Prevention

When it happens

Trigger: Calling an issueops Bootstrapper with BootstrapRequest{ProjectID: ""}; the ProjectID field was never populated from config or a prior substrate-creation step.

Common situations: Forgetting to create/fetch the project before bootstrapping; passing a struct literal with only Prefix set; config wiring that skips the project id key.

Related errors


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