fix(polecat): validate issue exists before starting session
Add validateIssue() to check that an issue exists and is not tombstoned before creating the tmux session. This prevents CPU spin loops from agents retrying work on invalid issues. Fixes #569 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
08ef50047d
commit
d42a9bd6e0
@@ -2,6 +2,7 @@
|
||||
package polecat
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -29,6 +30,7 @@ func debugSession(context string, err error) {
|
||||
var (
|
||||
ErrSessionRunning = errors.New("session already running")
|
||||
ErrSessionNotFound = errors.New("session not found")
|
||||
ErrIssueInvalid = errors.New("issue not found or tombstoned")
|
||||
)
|
||||
|
||||
// SessionManager handles polecat session lifecycle.
|
||||
@@ -161,6 +163,14 @@ func (m *SessionManager) Start(polecat string, opts SessionStartOptions) error {
|
||||
workDir = m.clonePath(polecat)
|
||||
}
|
||||
|
||||
// Validate issue exists and isn't tombstoned BEFORE creating session.
|
||||
// This prevents CPU spin loops from agents retrying work on invalid issues.
|
||||
if opts.Issue != "" {
|
||||
if err := m.validateIssue(opts.Issue, workDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
runtimeConfig := config.LoadRuntimeConfig(m.rig.Path)
|
||||
|
||||
// Ensure runtime settings exist in polecats/ (not polecats/<name>/) so we don't
|
||||
@@ -449,6 +459,32 @@ func (m *SessionManager) StopAll(force bool) error {
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// validateIssue checks that an issue exists and is not tombstoned.
|
||||
// This must be called before starting a session to avoid CPU spin loops
|
||||
// from agents retrying work on invalid issues.
|
||||
func (m *SessionManager) validateIssue(issueID, workDir string) error {
|
||||
cmd := exec.Command("bd", "show", issueID, "--json") //nolint:gosec
|
||||
cmd.Dir = workDir
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrIssueInvalid, issueID)
|
||||
}
|
||||
|
||||
var issues []struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := json.Unmarshal(output, &issues); err != nil {
|
||||
return fmt.Errorf("parsing issue: %w", err)
|
||||
}
|
||||
if len(issues) == 0 {
|
||||
return fmt.Errorf("%w: %s", ErrIssueInvalid, issueID)
|
||||
}
|
||||
if issues[0].Status == "tombstone" {
|
||||
return fmt.Errorf("%w: %s is tombstoned", ErrIssueInvalid, issueID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// hookIssue pins an issue to a polecat's hook using bd update.
|
||||
func (m *SessionManager) hookIssue(issueID, agentID, workDir string) error {
|
||||
cmd := exec.Command("bd", "update", issueID, "--status=hooked", "--assignee="+agentID) //nolint:gosec
|
||||
|
||||
Reference in New Issue
Block a user