gastownhall/beads · error

Unclaim: id must not be empty

Error message

Unclaim: id must not be empty

What it means

Validation error from Unclaim: the use case rejects the call because the issue id argument is an empty string. This is thrown before any repository interaction, so nothing was modified. It is a caller contract violation, not a storage failure.

Source

Thrown at internal/storage/domain/issue.go:1880

func (u *issueUseCaseImpl) GetStaleIssues(ctx context.Context, filter types.StaleFilter) ([]*types.Issue, error) {
	out, err := u.issueRepo.GetStaleIssues(ctx, filter)
	if err != nil {
		return nil, fmt.Errorf("GetStaleIssues: %w", err)
	}
	return out, nil
}

func (u *issueUseCaseImpl) GetEpicsEligibleForClosure(ctx context.Context) ([]*types.EpicStatus, error) {
	out, err := u.issueRepo.GetEpicsEligibleForClosure(ctx)
	if err != nil {
		return nil, fmt.Errorf("GetEpicsEligibleForClosure: %w", err)
	}
	return out, nil
}

func (u *issueUseCaseImpl) Unclaim(ctx context.Context, id, actor string, force bool) error {
	if id == "" {
		return fmt.Errorf("Unclaim: id must not be empty")
	}
	if err := u.issueRepo.UnclaimIssue(ctx, id, actor, force); err != nil {
		return fmt.Errorf("Unclaim: %w", err)
	}
	return nil
}

// UnclaimIfAssignee is the compare-and-swap release: it clears the claim only
// while the issue is still assigned to expectedAssignee, and otherwise returns
// storage.ErrAssigneeMismatch having written nothing. It is the conditional
// twin of Unclaim and runs the SAME transition (assignee cleared, status
// reopened, started_at cleared, lease dropped, row_lock rewritten, "unclaimed"
// event recorded) because both reach the one classic implementation in
// issueops — which is what makes `bd unclaim --if-assignee` behave identically
// on the proxied-server and embedded backends.
func (u *issueUseCaseImpl) UnclaimIfAssignee(ctx context.Context, id, actor, expectedAssignee string) error {
	if id == "" {
		return fmt.Errorf("UnclaimIfAssignee: id must not be empty")

View on GitHub (pinned to 71377f2769)

Solutions

  1. Ensure the issue id is resolved before calling Unclaim (check the source of the empty value)
  2. Validate the id is non-empty in your caller before invoking
  3. If the id comes from user input, surface a clear 'issue key required' message

Example fix

// before
if err := uc.Unclaim(ctx, issueID, actor, false); err != nil { ... }
// after
if issueID == "" {
	return fmt.Errorf("issue id required")
}
if err := uc.Unclaim(ctx, issueID, actor, false); err != nil { ... }
Defensive patterns

Strategy: validation

Validate before calling

if id == "" {
	return fmt.Errorf("cannot unclaim: issue id is empty")
}

Try / catch

if err := uc.Unclaim(ctx, id, actor, force); err != nil {
	if strings.Contains(err.Error(), "id must not be empty") {
		return fmt.Errorf("caller bug: empty issue id")
	}
	return err
}

Prevention

When it happens

Trigger: Calling Unclaim(ctx, "", actor, force) — passing an empty id, typically when a variable holding the issue key was never populated.

Common situations: Parsing a CLI arg or JSON field that is missing/empty and passing it straight to Unclaim; empty results from upstream lookups feeding the claim-release path.

Related errors


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