From 612c59629f8eeb47be9386d6045601525d53a952 Mon Sep 17 00:00:00 2001 From: chrome Date: Sat, 24 Jan 2026 01:20:12 -0800 Subject: [PATCH] fix(plugin): don't record false success for manual plugin runs `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 --- internal/cmd/plugin.go | 9 +++++---- internal/plugin/recording.go | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/internal/cmd/plugin.go b/internal/cmd/plugin.go index 40bde4c0..5d3194a1 100644 --- a/internal/cmd/plugin.go +++ b/internal/cmd/plugin.go @@ -392,7 +392,7 @@ func runPluginRun(cmd *cobra.Command, args []string) error { if duration == "" { duration = "1h" // default } - count, err := recorder.CountRunsSince(p.Name, duration) + count, err := recorder.CountSuccessfulRunsSince(p.Name, duration) if err != nil { // Log warning but continue 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.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) beadID, err := recorder.RecordRun(plugin.PluginRunRecord{ PluginName: p.Name, RigName: p.RigName, - Result: plugin.ResultSuccess, // Manual runs are marked success - Body: "Manual run via gt plugin run", + Result: plugin.ResultSkipped, // Instructions printed, not executed + Body: "Manual run via gt plugin run (instructions printed, not executed)", }) if err != nil { fmt.Fprintf(os.Stderr, "Warning: failed to record run: %v\n", err) diff --git a/internal/plugin/recording.go b/internal/plugin/recording.go index 77af0dc5..cbe174d0 100644 --- a/internal/plugin/recording.go +++ b/internal/plugin/recording.go @@ -213,3 +213,19 @@ func (r *Recorder) CountRunsSince(pluginName string, since string) (int, error) } 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 +}