Skip to content

feat(pr): add checks command to show build status for PRs#15

Merged
avivsinai merged 6 commits intoavivsinai:masterfrom
aviadshiber:feat/pr-checks
Dec 30, 2025
Merged

feat(pr): add checks command to show build status for PRs#15
avivsinai merged 6 commits intoavivsinai:masterfrom
aviadshiber:feat/pr-checks

Conversation

@aviadshiber
Copy link
Copy Markdown
Contributor

@aviadshiber aviadshiber commented Dec 28, 2025

Summary

  • Adds bkt pr checks <PR_ID> command that displays pipeline/CI status for pull requests
  • Supports both Bitbucket Data Center and Bitbucket Cloud APIs
  • Extracts shared CommitStatus type to pkg/types for consistency between API clients

Features

  • Color-coded output: Green for success, red for failure, yellow for in-progress
  • --wait flag: Poll until all builds complete (useful for CI scripts)
  • --interval flag: Configure initial polling frequency (default 10s)
  • --max-interval flag: Maximum polling interval with backoff (default 2m)
  • --timeout flag: Maximum time to wait for builds (default 30m, 0 for no timeout)
  • Exponential backoff: 1.5x multiplier to reduce API load during long builds
  • Jitter: ±15% random variation prevents thundering herd when multiple clients poll
  • Signal handling: Graceful Ctrl-C cancellation via signal.NotifyContext
  • Error retry: Automatic retry with backoff on transient errors (up to 3 attempts)
  • Exit code: Returns non-zero when any build fails (for automation)

Changes

  • pkg/types/commit_status.go: New shared type for commit status
  • pkg/bbcloud/client.go: Add CommitStatuses() method with pagination support
  • pkg/bbdc/client.go: Use shared type alias for consistency
  • pkg/cmd/pr/pr.go: Add checks subcommand with:
    • Exponential backoff polling (1.5x multiplier)
    • Jitter (±15%) using crypto/rand
    • Consecutive error tracking with retry logic
    • Sentinel errors (ErrNoSourceCommit, ErrBuildsFailed)
    • Extracted checksResult struct to reduce DC/Cloud code duplication
    • CANCELLED state support
  • pkg/cmd/pr/pr_test.go: Comprehensive unit tests (50+ test cases)
  • pkg/bbcloud/client_test.go: Tests for Cloud API CommitStatuses method
  • README.md: Updated with pr checks examples
  • CHANGELOG.md: Added entry for new feature

Usage

# Data Center
bkt pr checks 123 --project PROJ --repo my-repo

# Cloud
bkt pr checks 123 --workspace myworkspace --repo my-repo

# Wait for completion
bkt pr checks 123 --wait

# Custom polling with backoff settings
bkt pr checks 123 --wait --interval 5s --max-interval 1m --timeout 10m

Test plan

  • All existing tests pass
  • Unit tests for stateIcon function (15 cases)
  • Unit tests for stateColor function (11 cases, including CANCELLED/STOPPED)
  • Unit tests for isTerminalState function (14 cases)
  • Unit tests for allBuildsComplete function (6 cases)
  • Unit tests for anyBuildFailed function (5 cases)
  • Unit tests for calculatePollInterval (5 cases)
  • Unit tests for addJitter (3 test functions)
  • Unit tests for backoff progression
  • Integration tests for pollUntilComplete (7 cases)
  • Integration tests for DC workflow (4 cases)
  • Integration tests for Cloud workflow (2 cases)
  • Argument parsing tests (4 cases)
  • Validation tests (5 cases)
  • Sentinel error tests (2 cases)
  • Cloud client CommitStatuses tests (7 cases)
  • Race detection enabled (go test -race)
  • Tests use t.Parallel() for faster execution

Go Best Practices Applied

  • Error handling: Sentinel errors with errors.Is() support
  • Context: Hierarchical timeouts, proper propagation
  • Concurrency: Race-free with go test -race
  • Testing: Table-driven tests, t.Parallel(), t.Run() subtests
  • API design: Functional options pattern for polling configuration
  • Uses signal.NotifyContext for graceful Ctrl-C handling (Go 1.16+ pattern)
  • ANSI color codes with TTY detection via IOStreams.ColorEnabled()

🤖 Generated with Claude Code

Closes #14

Implements the `bkt pr checks <PR_ID>` command that displays CI/pipeline
status for pull requests. Supports both Bitbucket Data Center and Cloud.

Features:
- Add CommitStatuses method to Cloud client with pagination support
- Add checks subcommand to pr command group
- Extract shared CommitStatus type to pkg/types for consistency
- Color-coded output (green=success, red=failed, yellow=in-progress)
- --wait flag for polling until all builds complete
- --interval flag to configure polling frequency (default 10s)
- --timeout flag for maximum wait time (default 30m)
- Graceful Ctrl-C handling via signal.NotifyContext
- Returns non-zero exit code when builds fail (useful for CI scripts)

Usage:
  bkt pr checks 123                    # Show current status
  bkt pr checks 123 --wait             # Wait for completion
  bkt pr checks 123 --wait --timeout 5m
  bkt pr checks 123 --wait --interval 5s

Closes avivsinai#14

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown
Owner

@avivsinai avivsinai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: feat(pr): add checks command

Thanks for this contribution @aviadshiber! This is a well-implemented feature that aligns with our gh CLI inspiration. The test coverage is excellent (36 cases), and the core design is solid.

After reviewing against the gh pr checks implementation, here are the required changes before we can merge:


🔴 Required Fixes

1. Empty checks should error, not loop forever

Problem: allBuildsComplete([]) returns false, causing infinite polling when no builds exist.

Fix in pkg/cmd/pr/pr.go:

func allBuildsComplete(statuses []types.CommitStatus) bool {
    if len(statuses) == 0 {
        return true // No builds = nothing to wait for
    }
    // ... rest unchanged
}

And add early exit in runChecks for the --wait case:

statuses, err = fetchStatuses()
if err != nil {
    return err
}
if len(statuses) == 0 {
    // Print "no builds" message and exit - don't poll
    return printStatuses(ios, opts.ID, commitSHA, statuses, colorEnabled)
}
// Only poll if there are actual builds
if opts.Wait {
    statuses, err = pollUntilComplete(ctx, ios, opts, fetchStatuses, colorEnabled, commitSHA)
    // ...
}

2. Flag validation: --interval requires --wait

Problem: --interval 5s without --wait is silently ignored.

Fix in newChecksCmd:

cmd := &cobra.Command{
    // ...
    RunE: func(cmd *cobra.Command, args []string) error {
        // Validate flag combinations
        if cmd.Flags().Changed("interval") && !opts.Wait {
            return fmt.Errorf("cannot use --interval without --wait")
        }
        if cmd.Flags().Changed("timeout") && !opts.Wait {
            return fmt.Errorf("cannot use --timeout without --wait")
        }
        // ... existing logic
    },
}

🟡 Should Add (for gh parity)

3. Silent exit code 1 on build failure

Current: Returns fmt.Errorf("one or more builds failed") which prints error message.

Expected: Exit 1 silently (scripts check exit code, don't need message).

Add to pkg/cmdutil/errors.go:

// SilentError signals failure without printing an error message
var SilentError = errors.New("SilentError")

Update command handler to recognize it (in root command or wherever errors are handled).

Then in runChecks:

if opts.Wait && anyBuildFailed(statuses) {
    return cmdutil.SilentError
}

4. Add --fail-fast flag

Exits watch mode immediately on first failure (useful for CI).

// In checksOptions:
FailFast bool

// In newChecksCmd:
cmd.Flags().BoolVar(&opts.FailFast, "fail-fast", false, "Exit watch mode on first check failure")

// In RunE validation:
if opts.FailFast && !opts.Wait {
    return fmt.Errorf("cannot use --fail-fast without --wait")
}

// In pollUntilComplete, after printing statuses:
if opts.FailFast && anyBuildFailed(statuses) {
    return statuses, nil
}

🟢 Nice to Have (can be follow-up PRs)

5. Alternate screen buffer for cleaner watch output

Instead of printing --- separators, use terminal alternate screen:

if opts.Wait {
    ios.StartAlternateScreenBuffer()
    defer ios.StopAlternateScreenBuffer()
}

This keeps the terminal clean and shows final result on original screen.

6. Pending exit code (exit 8)

gh uses exit code 8 when checks are still pending (e.g., timeout hit):

var PendingError = errors.New("PendingError") // exit code 8

7. Remove redundant test helpers

In pkg/bbcloud/client_test.go, replace custom testContains/findSubstring with strings.Contains.


Summary Checklist

  • Fix empty checks infinite loop (#1)
  • Add flag validation for --interval/--timeout (#2)
  • Add SilentError for build failures (#3)
  • Add --fail-fast flag (#4)
  • (Optional) Clean up test helpers (#7)

Once these are addressed, happy to approve! 🚀

aviadsTaboola and others added 4 commits December 29, 2025 09:55
Improves the `bkt pr checks --wait` command with adaptive polling:

- Exponential backoff (1.5x multiplier) reduces API load during long builds
- Random jitter (±15%) prevents thundering herd when multiple clients poll
- New `--max-interval` flag caps backoff (default 2 minutes)
- Automatic retry with backoff on transient errors (up to 3 attempts)
- Uses crypto/rand for better jitter randomness distribution

Polling progression with defaults (10s base, 2m max):
  Iteration 0:  ~10s
  Iteration 1:  ~15s
  Iteration 2:  ~22s
  Iteration 3:  ~34s
  Iteration 4:  ~51s
  Iteration 5:  ~76s
  Iteration 6+: ~120s (capped)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Follow-up improvements from PR review:

## CANCELLED state support
- Add CANCELLED to isTerminalState(), stateIcon(), stateColor()
- Icon: ⊘ (circled slash), Color: yellow
- Properly handle cancelled Bitbucket Cloud builds

## Code deduplication
- Extract checksResult struct and executeStatusCheck() helper
- Common logic for polling, error handling, output, exit codes
- DC and Cloud branches now just set up client and call helper

## Integration tests for pollUntilComplete
- TestPollUntilComplete_ImmediateSuccess
- TestPollUntilComplete_MultipleIterations
- TestPollUntilComplete_ContextCancellation
- TestPollUntilComplete_Timeout
- TestPollUntilComplete_FetchErrorRetry
- TestPollUntilComplete_MaxConsecutiveErrors
- TestPollUntilComplete_ErrorResetOnSuccess

## Empty statuses UX
- Show "Waiting for builds to appear..." when no builds found
- Clearer message than generic "Waiting for 0 build(s)"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add sentinel errors (ErrNoSourceCommit, ErrBuildsFailed) for better
  error handling with errors.Is
- Add t.Parallel() to independent tests for faster test execution
- Add TestSentinelErrors to verify error wrapping behavior
- Update existing tests to use sentinel errors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add flag validation: --interval, --max-interval, --timeout, --fail-fast require --wait
- Add --fail-fast flag to exit watch mode on first failure
- Fix empty checks behavior: exit early when no builds exist
- Use cmdutil.ErrSilent for build failures (silent exit code 1)
- Clean up redundant test helpers in client_test.go
- Add tests for new flag validation and behaviors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@aviadshiber
Copy link
Copy Markdown
Contributor Author

aviadshiber commented Dec 29, 2025

Hey @avivsinai!

Thank you so much for this incredibly thorough and well-structured review! The level of detail and the clear examples made it a pleasure to work through each item. Really appreciate you taking the time to compare against the gh CLI implementation.

Updated Checklist

Here's the status of all your feedback:

🔴 Required Fixes

🟡 Should Add

🟢 Nice to Have

Additional Enhancements

Beyond your checklist, I also:

  • Added comprehensive tests for all new behaviors:
    • TestFlagValidation - 6 test cases covering all flag combinations
    • TestPollUntilComplete_EmptyBuildsExitsEarly - verifies early exit behavior
    • TestPollUntilComplete_FailFast - validates fail-fast functionality
  • Extended flag validation to include --max-interval (wasn't in your list but made sense to include!)

All tests pass with race detection enabled.

Looking forward to your re-review!

- Add alternate screen buffer for cleaner watch mode output
  - StartAlternateScreenBuffer/StopAlternateScreenBuffer in iostreams
  - ClearScreen for refreshing output in place
  - Only active when stdout is TTY
- Add ErrPending sentinel for timeout with pending checks (exit code 8)
  - Matches gh pr checks behavior
  - Distinct from ErrSilent (exit code 1 for failures)
- Add tests for new iostreams methods and ErrPending

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@aviadshiber
Copy link
Copy Markdown
Contributor Author

Follow-up: All Items Now Complete! 🎉

Just pushed two more commits addressing the remaining "Nice to Have" items:

🟢 Nice to Have (Now Complete!)

  • build(deps): bump github.com/itchyny/gojq from 0.12.13 to 0.12.17 #5 - Alternate screen buffer - Implemented!

    • Added StartAlternateScreenBuffer(), StopAlternateScreenBuffer(), and ClearScreen() to IOStreams
    • Watch mode now uses alternate screen for clean output
    • Falls back gracefully when not a TTY
  • Remove security reports section from README #6 - Pending exit code (exit 8) - Implemented!

    • Added ErrPending sentinel error in cmdutil/errors.go
    • When timeout hits with pending checks → exit code 8
    • When builds fail → exit code 1 (silent)
    • When cancelled → exit code 0
    • Matches gh pr checks behavior

Additional Enhancements Not in Original Review

These were implemented proactively to make the feature more robust:

  • Exponential backoff with jitter - Prevents API rate limiting with 1.5x multiplier and ±15% jitter using crypto/rand
  • Consecutive error handling - Backs off faster on errors, fails after 3 consecutive failures
  • Graceful signal handling - SIGINT/SIGTERM cleanup with context cancellation
  • Max interval cap - Configurable via --max-interval flag

Final Summary

Item Status
#1 - Empty checks infinite loop
#2 - Flag validation
#3 - SilentError for failures
#4 - --fail-fast flag
#5 - Alternate screen buffer
#6 - Pending exit code 8
#7 - Test helper cleanup

All 7 items complete with comprehensive tests. Ready for final review! 🚀

Copy link
Copy Markdown
Owner

@avivsinai avivsinai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All 7 review items addressed. Tests pass with race detection. LGTM 🚀

@avivsinai avivsinai merged commit a79b941 into avivsinai:master Dec 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Add bkt pr checks command for build/CI status

3 participants