gastownhall/beads · error

delete: cascade expansion: %w

Error message

delete: cascade expansion: %w

What it means

deleteMany wraps errors from issueRepo.FindAllDependents when expanding the deletion set in cascade mode. The 'delete: cascade expansion:' prefix identifies the dependent-graph traversal as the failing step, with the repository error preserved via %w. Deletion is aborted before anything is removed.

Source

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

}

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

func (u *issueUseCaseImpl) deleteMany(ctx context.Context, params DeleteIssuesParams, actor string) (DeleteIssuesResult, error) {
	if len(params.IDs) == 0 {
		return DeleteIssuesResult{}, nil
	}

	result := DeleteIssuesResult{}

	allIDs := params.IDs
	switch {
	case params.Cascade:
		expanded, err := u.issueRepo.FindAllDependents(ctx, params.IDs)
		if err != nil {
			return DeleteIssuesResult{}, fmt.Errorf("delete: cascade expansion: %w", err)
		}
		allIDs = expanded
	case params.EnforceCascadePolicy:
		// Embedded-parity dependent handling (see DeleteIssuesParams): without
		// Cascade, an external dependent either blocks the delete (no Force) or
		// is orphaned (Force), never silently swept.
		externalBySource, err := u.externalDependents(ctx, params.IDs)
		if err != nil {
			return DeleteIssuesResult{}, err
		}
		if params.Force {
			orphanSet := map[string]bool{}
			for _, deps := range externalBySource {
				for _, dep := range deps {
					orphanSet[dep] = true
				}
			}
			result.OrphanedIssues = sortedStringSet(orphanSet)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Unwrap the error chain to find the underlying FindAllDependents failure
  2. Confirm the Dolt database is reachable and healthy
  3. Retry the delete; cascade expansion is read-only and safe to re-run
  4. If schema errors persist, run beads schema migration/upgrade

Example fix

// before
res, err := uc.DeleteIssues(ctx, ids, actor)
if err != nil { return err }
// after
if err != nil {
	if strings.Contains(err.Error(), "cascade expansion") {
		// storage read failed; nothing was deleted — safe to retry
		return retryDelete(ctx, ids)
	}
	return err
}
Defensive patterns

Strategy: retry

Validate before calling

// pre-check storage before cascade delete
if err := pingDolt(ctx); err != nil {
	return fmt.Errorf("cannot cascade delete now: %w", err)
}

Try / catch

res, err := uc.DeleteIssues(ctx, ids, actor)
if err != nil {
	if strings.Contains(err.Error(), "cascade expansion") {
		// read-only phase failed; nothing deleted — safe to retry
		return retryWithBackoff(func() error { _, err := uc.DeleteIssues(ctx, ids, actor); return err })
	}
	return err
}

Prevention

When it happens

Trigger: Calling DeleteIssue/DeleteWisp/DeleteIssues/DeleteWisps with Cascade=true when FindAllDependents fails — DB connection failure, SQL error during the dependency-graph recursive query, or storage unavailable.

Common situations: Cascading deletes during large dependency graphs while Dolt restarts; schema drift breaking the dependents query; concurrent modifications locking rows during expansion.

Related errors


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