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
- Pass the real actor (username/agent name) in the close call.
- Set up the environment identity (git config user.name or bd's actor config) so defaults resolve.
- Validate actor non-empty before invoking the use case.
- 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
- Always thread the authenticated user/agent name into close calls.
- Set git config user.name (or equivalent) in CI images.
- Reject empty actor at your integration's boundary, not deep in storage.
- Log actor on every close for auditability.
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.