gastownhall/beads · error

DeleteWisp: id must not be empty

Error message

DeleteWisp: id must not be empty

What it means

DeleteWisp validates its required id argument before doing any work and returns this error when the id is an empty string. Like DeleteIssue, it is an input-validation guard at the use-case layer; no storage call occurs. It applies to wisp (ephemeral issue) deletion.

Source

Thrown at internal/storage/domain/issue_delete.go:42

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)
}

func (u *issueUseCaseImpl) DeleteIssues(ctx context.Context, params DeleteIssuesParams, actor string) (DeleteIssuesResult, error) {
	return u.deleteMany(ctx, params, actor)
}

func (u *issueUseCaseImpl) DeleteWisps(ctx context.Context, params DeleteIssuesParams, actor string) (DeleteIssuesResult, error) {
	return u.deleteMany(ctx, params, actor)
}

func (u *issueUseCaseImpl) PreviewDelete(ctx context.Context, ids []string) (DeletePreview, error) {
	return u.previewDelete(ctx, ids)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Pass the real wisp ID to DeleteWisp
  2. Validate the wisp ID exists at the source (lookup result, JSON field) before calling
  3. Skip empty entries in batch-delete loops instead of passing them through

Example fix

// before
for _, w := range wisps {
	uc.DeleteWisp(ctx, w.ID, actor) // panics the flow with empty-id error
}
// after
for _, w := range wisps {
	if w.ID == "" { continue }
	uc.DeleteWisp(ctx, w.ID, actor)
}
Defensive patterns

Strategy: validation

Validate before calling

for _, w := range wisps {
	if strings.TrimSpace(w.ID) == "" { continue } // skip malformed entries
	if _, err := uc.DeleteWisp(ctx, w.ID, actor); err != nil { return err }
}

Type guard

func hasWispID(w Wisp) bool { return strings.TrimSpace(w.ID) != "" }

Try / catch

res, err := uc.DeleteWisp(ctx, id, actor)
if err != nil && strings.Contains(err.Error(), "DeleteWisp: id must not be empty") {
	log.Printf("skipping: empty wisp id")
	return nil
}

Prevention

When it happens

Trigger: Calling DeleteWisp(ctx, "", actor) — an uninitialized wisp ID, a failed wisp lookup returning an empty ID, or whitespace-only input that was trimmed to "".

Common situations: Agent loops deleting wisps from a batch where some entries lack IDs; reading wisp IDs from JSONL export files with missing fields; variable shadowing leaving the ID unset.

Related errors


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