gastownhall/beads · error

close: actor must not be empty

Error message

close: actor must not be empty

What it means

Guard clause in closeChecked: the actor argument is empty. bd records who closed an issue for audit; an empty actor would produce an unattributable close event, so the call is rejected before any storage write.

Source

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

	return u.close(ctx, id, params, actor, true)
}

// CloseIssueChecked closes an issue through the shared guarded close path.
func (u *issueUseCaseImpl) CloseIssueChecked(ctx context.Context, id string, params CloseIssueParams, actor string, force bool) (CloseIssueResult, error) {
	return u.closeChecked(ctx, id, params, actor, force, false)
}

// CloseWispChecked is the wisp twin of CloseIssueChecked.
func (u *issueUseCaseImpl) CloseWispChecked(ctx context.Context, id string, params CloseIssueParams, actor string, force bool) (CloseIssueResult, error) {
	return u.closeChecked(ctx, id, params, actor, force, true)
}

func (u *issueUseCaseImpl) closeChecked(ctx context.Context, id string, params CloseIssueParams, actor string, force, useWisp bool) (CloseIssueResult, error) {
	if id == "" {
		return CloseIssueResult{}, fmt.Errorf("close: id must not be empty")
	}
	if actor == "" {
		return CloseIssueResult{}, fmt.Errorf("close: actor must not be empty")
	}
	row, err := u.issueRepo.CloseChecked(ctx, id, CloseRowParams{Reason: params.Reason, Session: params.Session}, actor, force)
	if err != nil {
		return CloseIssueResult{}, fmt.Errorf("close %s: %w", id, err)
	}
	issue, err := u.issueRepo.Get(ctx, id, IssueTableOpts{UseWispsTable: row.IsWisp || useWisp})
	if err != nil {
		return CloseIssueResult{}, fmt.Errorf("close %s: reload: %w", id, err)
	}
	return CloseIssueResult{Issue: issue, Closed: !row.AlreadyClosed, OpenChildren: row.OpenChildren}, nil
}

func (u *issueUseCaseImpl) close(ctx context.Context, id string, params CloseIssueParams, actor string, useWisp bool) (CloseIssueResult, error) {
	if id == "" {
		return CloseIssueResult{}, fmt.Errorf("close: id must not be empty")
	}
	if actor == "" {
		return CloseIssueResult{}, fmt.Errorf("close: actor must not be empty")

View on GitHub (pinned to 71377f2769)

Solutions

  1. Pass the real actor (username/agent name) in the close call.
  2. Set up the environment identity (git config user.name or bd's actor config) so defaults resolve.
  3. Validate actor non-empty before invoking the use case.
  4. Fix integration code to thread the authenticated user through to this parameter.

Example fix

// before
uc.CloseIssue(ctx, id, params, "")

// after
actor := os.Getenv("BD_ACTOR")
if actor == "" { actor = "unknown-agent" }
uc.CloseIssue(ctx, id, params, actor)
Defensive patterns

Strategy: validation

Validate before calling

func resolveActor(configured string) (string, error) {
    if configured == "" {
        return "", errors.New("actor identity missing; set BD_ACTOR or git config user.name")
    }
    return configured, nil
}

Try / catch

if err != nil {
    if strings.Contains(err.Error(), "actor must not be empty") {
        return fmt.Errorf("unauthenticated close: configure an actor identity before closing issues: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling CloseIssue/CloseWisp/CloseIssueChecked/CloseWispChecked with actor="" — e.g. agent/tooling that never sets the actor name, or config losing the default actor identity.

Common situations: CI pipelines with no git user configured; custom integrations omitting the actor parameter; scripts hardcoding "" for actor.

Related errors


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