fix(plugin): don't record false success for manual plugin runs
Some checks failed
CI / Check for .beads changes (pull_request) Successful in 9s
CI / Check embedded formulas (pull_request) Successful in 32s
CI / Test (pull_request) Failing after 1m47s
CI / Lint (pull_request) Failing after 22s
CI / Integration Tests (pull_request) Successful in 1m35s
CI / Coverage Report (pull_request) Has been skipped
Windows CI / Windows Build and Unit Tests (pull_request) Has been cancelled

`gt plugin run` was recording ResultSuccess even though it only prints
instructions without executing them. This poisoned the cooldown gate,
blocking actual executions for 24h.

Changes:
- Record manual runs as ResultSkipped instead of ResultSuccess
- Add CountSuccessfulRunsSince() to only count successful runs for gate
- Gate check now uses CountSuccessfulRunsSince() so skipped/failed runs
  don't block future executions

Fixes: hq-2dis4c

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
chrome
2026-01-24 01:20:12 -08:00
committed by John Ogle
parent 2a639ff999
commit 612c59629f
2 changed files with 21 additions and 4 deletions

View File

@@ -392,7 +392,7 @@ func runPluginRun(cmd *cobra.Command, args []string) error {
if duration == "" { if duration == "" {
duration = "1h" // default duration = "1h" // default
} }
count, err := recorder.CountRunsSince(p.Name, duration) count, err := recorder.CountSuccessfulRunsSince(p.Name, duration)
if err != nil { if err != nil {
// Log warning but continue // Log warning but continue
fmt.Fprintf(os.Stderr, "Warning: checking gate status: %v\n", err) fmt.Fprintf(os.Stderr, "Warning: checking gate status: %v\n", err)
@@ -433,13 +433,14 @@ func runPluginRun(cmd *cobra.Command, args []string) error {
fmt.Printf("%s\n", style.Bold.Render("Instructions:")) fmt.Printf("%s\n", style.Bold.Render("Instructions:"))
fmt.Println(p.Instructions) fmt.Println(p.Instructions)
// Record the run // Record the run as "skipped" - we only printed instructions, didn't execute
// Actual execution happens via Deacon patrol or an agent following the instructions
recorder := plugin.NewRecorder(townRoot) recorder := plugin.NewRecorder(townRoot)
beadID, err := recorder.RecordRun(plugin.PluginRunRecord{ beadID, err := recorder.RecordRun(plugin.PluginRunRecord{
PluginName: p.Name, PluginName: p.Name,
RigName: p.RigName, RigName: p.RigName,
Result: plugin.ResultSuccess, // Manual runs are marked success Result: plugin.ResultSkipped, // Instructions printed, not executed
Body: "Manual run via gt plugin run", Body: "Manual run via gt plugin run (instructions printed, not executed)",
}) })
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Warning: failed to record run: %v\n", err) fmt.Fprintf(os.Stderr, "Warning: failed to record run: %v\n", err)

View File

@@ -213,3 +213,19 @@ func (r *Recorder) CountRunsSince(pluginName string, since string) (int, error)
} }
return len(runs), nil return len(runs), nil
} }
// CountSuccessfulRunsSince returns the count of successful runs for a plugin since the given duration.
// Only successful runs count for cooldown gate evaluation - skipped/failed runs don't reset the cooldown.
func (r *Recorder) CountSuccessfulRunsSince(pluginName string, since string) (int, error) {
runs, err := r.GetRunsSince(pluginName, since)
if err != nil {
return 0, err
}
count := 0
for _, run := range runs {
if run.Result == ResultSuccess {
count++
}
}
return count, nil
}