fix: prevent closing issues with open blockers (GH#962)

Added IsBlocked method to Storage interface that checks if an issue is
in the blocked_issues_cache and returns the blocking issue IDs.

The close command now checks for blockers before allowing an issue to
be closed:
- If an issue has open blockers, closing is blocked with an error message
- The --force flag overrides this check
- Works in both daemon mode (RPC) and direct storage mode
- Also handles cross-rig routed IDs

This addresses the bug where agents could close a bead even when it
depends on an open bug/issue.

Closes #962

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
fang
2026-01-09 22:56:56 -08:00
committed by Steve Yegge
parent 0933bf5eda
commit a851104203
8 changed files with 175 additions and 0 deletions

View File

@@ -174,6 +174,7 @@ type CloseArgs struct {
Reason string `json:"reason,omitempty"`
Session string `json:"session,omitempty"` // Claude Code session ID that closed this issue
SuggestNext bool `json:"suggest_next,omitempty"` // Return newly unblocked issues (GH#679)
Force bool `json:"force,omitempty"` // Force close even with open blockers (GH#962)
}
// CloseResult is returned when SuggestNext is true (GH#679)

View File

@@ -831,6 +831,23 @@ func (s *Server) handleClose(req *Request) Response {
}
}
// Check if issue has open blockers (GH#962)
if !closeArgs.Force {
blocked, blockers, err := store.IsBlocked(ctx, closeArgs.ID)
if err != nil {
return Response{
Success: false,
Error: fmt.Sprintf("failed to check blockers: %v", err),
}
}
if blocked && len(blockers) > 0 {
return Response{
Success: false,
Error: fmt.Sprintf("cannot close %s: blocked by open issues %v (use --force to override)", closeArgs.ID, blockers),
}
}
}
// Capture old status for rich mutation event
oldStatus := ""
if issue != nil {