fix: bd delete --cascade now recursively deletes dependents (#787)
The --cascade flag was documented but not working for single-issue deletes. The bug had two causes: 1. CLI direct mode: Single-issue deletes bypassed the batch path where cascade expansion actually happens. Fixed by routing cascade deletes through deleteBatch() regardless of issue count. 2. Daemon/RPC mode: handleDelete() iterated through IDs individually without expanding dependents. Fixed by using DeleteIssues() with cascade flag when SQLite storage is available. Now `bd delete <id> --cascade --force` correctly deletes the target issue plus all issues that depend on it (recursively). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -681,6 +681,61 @@ func (s *Server) handleDelete(req *Request) Response {
|
||||
}
|
||||
}
|
||||
|
||||
ctx := s.reqCtx(req)
|
||||
|
||||
// Use batch delete for cascade/multi-issue operations on SQLite storage
|
||||
// This handles cascade delete properly by expanding dependents recursively
|
||||
// For simple single-issue deletes, use the direct path to preserve custom reason
|
||||
if sqlStore, ok := store.(*sqlite.SQLiteStorage); ok {
|
||||
// Use batch delete if: cascade enabled, force enabled, multiple IDs, or dry-run
|
||||
useBatchDelete := deleteArgs.Cascade || deleteArgs.Force || len(deleteArgs.IDs) > 1 || deleteArgs.DryRun
|
||||
if useBatchDelete {
|
||||
result, err := sqlStore.DeleteIssues(ctx, deleteArgs.IDs, deleteArgs.Cascade, deleteArgs.Force, deleteArgs.DryRun)
|
||||
if err != nil {
|
||||
return Response{
|
||||
Success: false,
|
||||
Error: fmt.Sprintf("delete failed: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
// Emit mutation events for deleted issues
|
||||
if !deleteArgs.DryRun {
|
||||
for _, issueID := range deleteArgs.IDs {
|
||||
s.emitMutation(MutationDelete, issueID, "", "")
|
||||
}
|
||||
}
|
||||
|
||||
// Build response
|
||||
responseData := map[string]interface{}{
|
||||
"deleted_count": result.DeletedCount,
|
||||
"total_count": len(deleteArgs.IDs),
|
||||
}
|
||||
if deleteArgs.DryRun {
|
||||
responseData["dry_run"] = true
|
||||
responseData["issue_count"] = result.DeletedCount
|
||||
}
|
||||
if result.DependenciesCount > 0 {
|
||||
responseData["dependencies_removed"] = result.DependenciesCount
|
||||
}
|
||||
if result.LabelsCount > 0 {
|
||||
responseData["labels_removed"] = result.LabelsCount
|
||||
}
|
||||
if result.EventsCount > 0 {
|
||||
responseData["events_removed"] = result.EventsCount
|
||||
}
|
||||
if len(result.OrphanedIssues) > 0 {
|
||||
responseData["orphaned_issues"] = result.OrphanedIssues
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(responseData)
|
||||
return Response{
|
||||
Success: true,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simple single-issue delete path (preserves custom reason)
|
||||
// DryRun mode: just return what would be deleted
|
||||
if deleteArgs.DryRun {
|
||||
data, _ := json.Marshal(map[string]interface{}{
|
||||
@@ -694,7 +749,6 @@ func (s *Server) handleDelete(req *Request) Response {
|
||||
}
|
||||
}
|
||||
|
||||
ctx := s.reqCtx(req)
|
||||
deletedCount := 0
|
||||
errors := make([]string, 0)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user