Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cmd/goose/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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
}
}
Expand Down
29 changes: 26 additions & 3 deletions cmd/goose/main.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package main implements a cross-platform system tray application for monitoring GitHub pull requests.

Check failure on line 1 in cmd/goose/main.go

View workflow job for this annotation

GitHub Actions / golangci-lint

package has more than one godoc ("main") (godoclint)
// It displays incoming and outgoing PRs, highlighting those that are blocked and need attention.
// The app integrates with the Turn API to provide additional PR metadata and uses the GitHub API
// for fetching PR data.
Expand Down Expand Up @@ -54,6 +54,7 @@
// 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
Expand All @@ -63,6 +64,7 @@
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
Expand Down Expand Up @@ -863,21 +865,42 @@
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)
}
}
}
Expand Down
17 changes: 11 additions & 6 deletions cmd/goose/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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()
}
Expand Down Expand Up @@ -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)
}
})
Expand Down Expand Up @@ -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)
}
})
Expand Down Expand Up @@ -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)
}
})
Expand Down
Loading