gastownhall/beads · error
close: id must not be empty
Error message
close: id must not be empty
What it means
Guard clause in closeChecked: the issue/wisp ID argument is the empty string, so there is nothing to close. Pure input validation — no storage access happens.
Source
Thrown at internal/storage/domain/issue.go:1665
}
func (u *issueUseCaseImpl) CloseWisp(ctx context.Context, id string, params CloseIssueParams, actor string) (CloseIssueResult, error) {
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")View on GitHub (pinned to 71377f2769)
Solutions
- Ensure the caller supplies a non-empty issue ID (e.g. bd-123).
- Guard scripts: `[ -n "$ID" ] || exit 1` before invoking close.
- Check the upstream command that produced the ID for silent failure.
- Validate parsed input before constructing CloseIssueParams.
Example fix
// before
uc.CloseIssueChecked(ctx, id, params, actor, false)
// after
if id == "" {
return fmt.Errorf("cannot close: issue id is empty")
}
uc.CloseIssueChecked(ctx, id, params, actor, false) Defensive patterns
Strategy: validation
Validate before calling
func validateCloseInput(id, actor string) error {
if id == "" {
return errors.New("close: issue id is required")
}
if actor == "" {
return errors.New("close: actor is required")
}
return nil
} Try / catch
if err != nil {
if strings.Contains(err.Error(), "id must not be empty") {
return fmt.Errorf("caller bug: no issue ID supplied; check upstream command output: %w", err)
}
return err
} Prevention
- Check command exit codes before reusing an ID variable in scripts.
- Use `set -u` in bash so unset variables fail fast.
- Validate API responses (issue IDs) before chaining calls.
- Prefer structured output (--json) and parse explicitly.
When it happens
Trigger: Calling CloseIssueChecked / CloseWispChecked (or CloseIssue/CloseWisp via close) with id="" — e.g. an unset variable, empty parse result, or upstream failure that produced an empty identifier.
Common situations: Shell scripts where `bd close "$ID"` has unset ID; automation that reads issue IDs from a prior command that returned nothing; JSON templates with missing id field.
Related errors
- close: actor must not be empty
- ErrFieldTooLong
- title is required
- add labels: id must not be empty
- %w: issue %s
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/cf2a3721387239d5.
Report an issue: GitHub.