From 2aa019b967ac188e88a65cfabe137d07638aca39 Mon Sep 17 00:00:00 2001 From: Thomas Stromberg Date: Tue, 28 Oct 2025 08:28:06 -0400 Subject: [PATCH] improve state logging & tracking --- cmd/goose/github.go | 4 ++++ cmd/goose/main.go | 29 ++++++++++++++++++++++++++--- cmd/goose/ui.go | 17 +++++++++++------ 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/cmd/goose/github.go b/cmd/goose/github.go index 2a926fe..84e1e55 100644 --- a/cmd/goose/github.go +++ b/cmd/goose/github.go @@ -482,6 +482,7 @@ func (app *App) fetchPRsInternal(ctx context.Context) (incoming []PR, outgoing [ Repository: repo, Author: issue.GetUser().GetLogin(), Number: issue.GetNumber(), + CreatedAt: issue.GetCreatedAt().Time, UpdatedAt: issue.GetUpdatedAt().Time, IsDraft: issue.GetDraft(), } @@ -577,6 +578,7 @@ func (app *App) fetchTurnDataSync(ctx context.Context, issues []*github.Issue, u actionReason := "" actionKind := "" testState := result.turnData.PullRequest.TestState + workflowState := result.turnData.Analysis.WorkflowState if action, exists := result.turnData.Analysis.NextAction[user]; exists { needsReview = true isBlocked = action.Critical // Only critical actions are blocking @@ -599,6 +601,7 @@ func (app *App) fetchTurnDataSync(ctx context.Context, issues []*github.Issue, u (*outgoing)[i].ActionReason = actionReason (*outgoing)[i].ActionKind = actionKind (*outgoing)[i].TestState = testState + (*outgoing)[i].WorkflowState = workflowState break } } else { @@ -610,6 +613,7 @@ func (app *App) fetchTurnDataSync(ctx context.Context, issues []*github.Issue, u (*incoming)[i].ActionReason = actionReason (*incoming)[i].ActionKind = actionKind (*incoming)[i].TestState = testState + (*incoming)[i].WorkflowState = workflowState break } } diff --git a/cmd/goose/main.go b/cmd/goose/main.go index f001710..0323238 100644 --- a/cmd/goose/main.go +++ b/cmd/goose/main.go @@ -54,6 +54,7 @@ const ( // PR represents a pull request with metadata. type PR struct { UpdatedAt time.Time + CreatedAt time.Time TurnDataAppliedAt time.Time FirstBlockedAt time.Time // When this PR was first detected as blocked Title string @@ -63,6 +64,7 @@ type PR struct { ActionReason string ActionKind string // The kind of action expected (review, merge, fix_tests, etc.) TestState string // Test state from Turn API: "running", "passing", "failing", etc. + WorkflowState string // Workflow state from Turn API: "running_tests", "waiting_for_review", etc. Number int IsDraft bool IsBlocked bool @@ -863,21 +865,42 @@ func (app *App) tryAutoOpenPR(ctx context.Context, pr *PR, autoBrowserEnabled bo return } + // Only auto-open if the PR is actually blocked or needs review + // This ensures we have a valid NextAction before opening + if !pr.IsBlocked && !pr.NeedsReview { + slog.Debug("[BROWSER] Skipping auto-open for non-blocked PR", + "repo", pr.Repository, "number", pr.Number, + "is_blocked", pr.IsBlocked, "needs_review", pr.NeedsReview) + return + } + if app.browserRateLimiter.CanOpen(startTime, pr.URL) { slog.Info("[BROWSER] Auto-opening newly blocked PR", - "repo", pr.Repository, "number", pr.Number, "url", pr.URL) + "repo", pr.Repository, + "number", pr.Number, + "url", pr.URL, + "workflow_state", pr.WorkflowState, + "test_state", pr.TestState, + "is_draft", pr.IsDraft, + "age_since_creation", time.Since(pr.CreatedAt).Round(time.Second), + "age_since_update", time.Since(pr.UpdatedAt).Round(time.Second)) // Use strict validation for auto-opened URLs // Validate against strict GitHub PR URL pattern for auto-opening if err := validateGitHubPRURL(pr.URL); err != nil { slog.Warn("Auto-open strict validation failed", "url", sanitizeForLog(pr.URL), "error", err) return } - if err := openURL(ctx, pr.URL); err != nil { + // Use ActionKind as the goose parameter value, or "next_action" if not set + gooseParam := pr.ActionKind + if gooseParam == "" { + gooseParam = "next_action" + } + if err := openURL(ctx, pr.URL, gooseParam); err != nil { slog.Error("[BROWSER] Failed to auto-open PR", "url", pr.URL, "error", err) } else { app.browserRateLimiter.RecordOpen(pr.URL) slog.Info("[BROWSER] Successfully opened PR in browser", - "repo", pr.Repository, "number", pr.Number, "action", pr.ActionKind) + "repo", pr.Repository, "number", pr.Number, "action", pr.ActionKind, "goose_param", gooseParam) } } } diff --git a/cmd/goose/ui.go b/cmd/goose/ui.go index a93e1c9..f5d00a4 100644 --- a/cmd/goose/ui.go +++ b/cmd/goose/ui.go @@ -20,7 +20,9 @@ import ( var _ *systray.MenuItem = nil // openURL safely opens a URL in the default browser after validation. -func openURL(ctx context.Context, rawURL string) error { +// The gooseParam parameter specifies what value to use for the ?goose= query parameter. +// If empty, defaults to "1" for menu clicks. +func openURL(ctx context.Context, rawURL string, gooseParam string) error { // Parse and validate the URL u, err := url.Parse(rawURL) if err != nil { @@ -45,10 +47,13 @@ func openURL(ctx context.Context, rawURL string) error { return errors.New("URLs with user info are not allowed") } - // Add goose=1 parameter to track source for GitHub and dash URLs + // Add goose parameter to track source for GitHub and dash URLs if u.Host == "github.com" || u.Host == "www.github.com" || u.Host == "dash.ready-to-review.dev" { q := u.Query() - q.Set("goose", "1") + if gooseParam == "" { + gooseParam = "1" + } + q.Set("goose", gooseParam) u.RawQuery = q.Encode() rawURL = u.String() } @@ -423,7 +428,7 @@ func (app *App) addPRSection(ctx context.Context, prs []PR, sectionTitle string, // Capture URL to avoid loop variable capture bug prURL := sortedPRs[prIndex].URL item.Click(func() { - if err := openURL(ctx, prURL); err != nil { + if err := openURL(ctx, prURL, ""); err != nil { slog.Error("failed to open url", "error", err) } }) @@ -619,7 +624,7 @@ func (app *App) rebuildMenu(ctx context.Context) { // Add error details errorMsg := app.systrayInterface.AddMenuItem(authError, "Click to see setup instructions") errorMsg.Click(func() { - if err := openURL(ctx, "https://cli.github.com/manual/gh_auth_login"); err != nil { + if err := openURL(ctx, "https://cli.github.com/manual/gh_auth_login", ""); err != nil { slog.Error("failed to open setup instructions", "error", err) } }) @@ -729,7 +734,7 @@ func (app *App) rebuildMenu(ctx context.Context) { // Add Web Dashboard link dashboardItem := app.systrayInterface.AddMenuItem("Web Dashboard", "") dashboardItem.Click(func() { - if err := openURL(ctx, "https://dash.ready-to-review.dev/"); err != nil { + if err := openURL(ctx, "https://dash.ready-to-review.dev/", ""); err != nil { slog.Error("failed to open dashboard", "error", err) } })