gastownhall/beads · error
DeleteIssue: id must not be empty
Error message
DeleteIssue: id must not be empty
What it means
DeleteIssue validates its required id argument before doing any work and returns this error when the id is an empty string. It is a pure input-validation guard in the use-case layer; no storage call is made. The caller passed an uninitialized or stripped issue ID.
Source
Thrown at internal/storage/domain/issue_delete.go:31
// DeleteBlockedError is the refusal returned by deleteMany when
// EnforceCascadePolicy is on, Cascade and Force are both off, and an issue in
// the deletion set has dependents outside it. The message mirrors classic
// (embedded) delete's refusal so both planes speak the same language.
type DeleteBlockedError struct {
// IssueID is the first issue in the requested deletion set (request order)
// found to have external dependents.
IssueID string
// Dependents are that issue's dependents outside the deletion set, sorted.
Dependents []string
}
func (e *DeleteBlockedError) Error() string {
return fmt.Sprintf("issue %s has dependents not in deletion set; use --cascade to delete them or --force to orphan them", e.IssueID)
}
func (u *issueUseCaseImpl) DeleteIssue(ctx context.Context, id, actor string) (DeleteIssuesResult, error) {
if id == "" {
return DeleteIssuesResult{}, fmt.Errorf("DeleteIssue: id must not be empty")
}
return u.deleteMany(ctx, DeleteIssuesParams{
IDs: []string{id},
Cascade: true,
UpdateTextReferences: true,
}, actor)
}
func (u *issueUseCaseImpl) DeleteWisp(ctx context.Context, id, actor string) (DeleteIssuesResult, error) {
if id == "" {
return DeleteIssuesResult{}, fmt.Errorf("DeleteWisp: id must not be empty")
}
return u.deleteMany(ctx, DeleteIssuesParams{
IDs: []string{id},
Cascade: true,
UpdateTextReferences: true,
}, actor)
}View on GitHub (pinned to 71377f2769)
Solutions
- Pass the actual issue ID (e.g. "bd-42") to DeleteIssue instead of an empty string
- Check where the ID is sourced (config, JSON, lookup result) and handle the missing-ID case before calling
- Add a caller-side check: if id == "" return a clear user-facing message before invoking the API
Example fix
// before
res, err := uc.DeleteIssue(ctx, issue.ID, actor) // issue.ID may be ""
// after
if issue.ID == "" {
return fmt.Errorf("cannot delete: no issue ID provided")
}
res, err := uc.DeleteIssue(ctx, issue.ID, actor) Defensive patterns
Strategy: validation
Validate before calling
func deleteIssue(id, actor string) error {
if strings.TrimSpace(id) == "" {
return fmt.Errorf("refusing to delete: issue id is empty")
}
_, err := uc.DeleteIssue(ctx, id, actor)
return err
} Type guard
func hasID(id string) bool { return strings.TrimSpace(id) != "" } Try / catch
res, err := uc.DeleteIssue(ctx, id, actor)
if err != nil && strings.Contains(err.Error(), "id must not be empty") {
return fmt.Errorf("caller bug: empty issue id passed to DeleteIssue")
} Prevention
- Never build delete calls from zero-value struct fields
- Validate IDs at the source (CLI args, JSON fields) before storage calls
- Return early on empty lookup results instead of propagating empty IDs
When it happens
Trigger: Calling DeleteIssue(ctx, "", actor) — e.g. an issue ID variable that was never populated, a struct field left zero-valued, or a string-trimming bug that reduced the ID to "".
Common situations: Scripts parsing issue IDs from JSON or CLI output where the key is missing; agent code building delete calls from empty lookup results; passing a zero-value string from a struct.
Related errors
- remove dep: sourceID and dependsOnID must not be empty
- reparent: childID must not be empty
- DeleteWisp: id must not be empty
- no store is open for this workspace
- not found
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/2e87dea55208c250.
Report an issue: GitHub.