This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
gh-observer is a GitHub PR check watcher CLI tool that improves on gh pr checks --watch by showing runtime metrics, queue latency, and better handling of startup delays. Built as a Go application with a TUI (Terminal User Interface) using Bubbletea.
This repo uses just for all development tasks:
just build- Build thegh-observerbinary and install locally as gh extensionjust branch <name>- Create a new feature branch (format:$USER/YYYY-MM-DD-<name>)just pr- Create PR, push changes, and watch checksjust again- Push changes, update PR description, and watch GHAs (most common iterative workflow)just merge- Squash merge PR, delete branch, return to main, and pull latestjust sync- Return to main branch, pull latest, and check status
just prweb- Open current PR in web browserjust pr_update- Update PR description with current commits (done automatically byagain)just test2cast <pr>- Record asciinema demo of watching a specific PRjust release_status- Check release workflow status and list binariesjust release_age- Check how long ago the last release was
just testorgo test ./...- Run all unit testsgo test ./internal/timing/...- Run timing tests only
Unit tests live in internal/timing/calculator_test.go and cover queue latency, runtime, final duration, and duration formatting. The rest of the app (TUI, GitHub API interactions) is tested manually by running just build and pointing the binary at a real PR.
gh-observer uses automated binary building for releases via GitHub Actions and gh-extension-precompile.
To create a new release:
just release v1.0.0This command:
- Runs
gh release create v1.0.0 --generate-notes - Creates the GitHub release with auto-generated release notes
- Pushes the version tag to the repository
- Triggers
.github/workflows/release.ymlvia the tag push
When a version tag (matching v*) is pushed, the release workflow automatically:
-
Builds cross-platform binaries for 5 platforms:
gh-observer_<version>_darwin-amd64- macOS Intelgh-observer_<version>_darwin-arm64- macOS Apple Silicongh-observer_<version>_linux-amd64- Linux x86-64gh-observer_<version>_linux-arm64- Linux ARM64gh-observer_<version>_windows-amd64.exe- Windows
-
Generates checksums (
gh-observer_<version>_checksums.txt) -
Creates build attestations for supply chain security (verifiable via
gh attestation verify) -
Attaches all artifacts to the GitHub release
The .github/workflows/release.yml workflow:
- Trigger: Push of tags matching
v*pattern - Action: Uses
cli/gh-extension-precompile@v2 - Go Version: Auto-detected from
go.mod(currently 1.25.7) viago_version_fileparameter - Security: Generates attestations with
generate_attestations: true - Permissions: Requires
contents: write,id-token: write,attestations: write
To test the release workflow without committing to a stable version:
# Create a prerelease tag (tags with hyphens create prereleases automatically)
git tag v0.1.0-rc.1
git push origin v0.1.0-rc.1
# Watch the workflow run
gh run watch
# Verify release assets were created
gh release view v0.1.0-rc.1
# Test installation
gh extension install fini-net/gh-observer
# Verify build attestation (macOS example)
gh attestation verify gh-observer_v0.1.0-rc.1_darwin-arm64 --owner fini-netAfter running just release, you can verify the workflow completed successfully:
# Check workflow status
just release_status
# Or manually verify
gh release view v1.0.0
gh run list --workflow=release.yml --limit 5Once released, users can install without the Go toolchain:
# Install latest version
gh extension install fini-net/gh-observer
# Install specific version
gh extension install fini-net/gh-observer --pin v1.0.0
# Upgrade to latest
gh extension upgrade gh-observerAll release binaries include build attestations:
- Generated via GitHub Actions' built-in attestation feature
- Provides cryptographic proof of build provenance
- Can be verified using
gh attestation verify <binary> --owner fini-net - Attestation files are automatically attached to releases
- No GPG signing: Build attestations provide equivalent security without secret management complexity
- Automatic Go version detection: Uses
go_version_file: go.modto stay in sync with project requirements - Standard build process: No custom build scripts needed -
go buildworks perfectly for this project - Complementary workflows: The
just releasecommand creates releases; the GitHub Action builds binaries. They work together, not as replacements.
gh-observer follows a clean architecture with distinct layers:
- Main entry point (
main.go) - Handles command-line arguments using Cobra, configuration loading, and mode selection (TUI vs snapshot) - GitHub client layer (
internal/github/) - Abstracts GitHub API interactions - TUI layer (
internal/tui/) - Implements Bubbletea model/view/update pattern for interactive mode - Configuration (
internal/config/) - Loads user config from~/.config/gh-observer/config.yaml - Timing utilities (
internal/timing/) - Calculates queue latency, runtime, and formats durations
The application operates in two modes based on whether stdout is a terminal:
Interactive mode (default when running in a terminal):
- Uses Bubbletea TUI with live updates
- Polls GitHub API every 5s (configurable)
- Shows spinner and real-time status changes
- Automatically quits when all checks complete
- Supports keyboard input (q to quit)
Snapshot mode (when stdout is not a terminal, e.g., in scripts or CI):
- Implemented in
runSnapshot()function inmain.go - Prints a single snapshot of current check status
- Plain text output without colors or TUI
- Exits immediately after printing
- Returns appropriate exit code based on check results
- Useful for scripting:
gh-observer && echo "All checks passed!"
The TUI follows the Elm Architecture pattern (Model-View-Update):
- Model (
internal/tui/model.go) - Application state including PR metadata, check runs, rate limits, and UI state - Init (
internal/tui/update.go) - Initializes the model and kicks off PR info fetch - Update (
internal/tui/update.go) - Message handler that processes:TickMsg- Periodic refresh (every 5s configurable)PRInfoMsg- PR metadata (title, SHA, timestamps)ChecksUpdateMsg- Check run status updatestea.KeyMsg- Keyboard input (q to quit)
- View (
internal/tui/view.go) - Renders the terminal UI - Display (
internal/tui/display.go) - Column formatting, alignment widths, URL building for check hyperlinks - Styles (
internal/tui/styles.go) - Lipgloss color scheme and styling - Messages (
internal/tui/messages.go) - Custom message types for async operations
The internal/github/graphql.go module uses GraphQL to efficiently fetch check run data:
Query structure - Follows gh pr checks pattern:
Repository → PullRequest → Commits → StatusCheckRollup → CheckRun
→ CheckSuite → WorkflowRun → Workflow → Name
Key benefits:
- Single API call gets all data (workflow name + check status)
- More efficient than REST API (fewer API calls, less rate limit usage)
- Returns enriched
CheckRunInfowith workflow name included
Display format - Check names shown as "Workflow Name / Job Name":
- "CUE Validation / verify"
- "MarkdownLint / lint"
- "Claude Code Review / claude-review"
- "Checkov" (legacy checks without workflow show job name only)
The internal/timing/calculator.go module provides three core metrics:
-
Queue latency - Time from commit push to check start (
QueueLatency())- Calculated as:
check.StartedAt - headCommitTime - Shows how long GitHub took to queue the job
- Calculated as:
-
Runtime - Elapsed time for in-progress checks (
Runtime())- Calculated as:
time.Now() - check.StartedAt - Only for checks with status
in_progress
- Calculated as:
-
Final duration - Total runtime for completed checks (
FinalDuration())- Calculated as:
check.CompletedAt - check.StartedAt
- Calculated as:
The internal/github/ package provides API interaction with both REST and GraphQL:
-
GetToken()- Retrieves GitHub token using:GITHUB_TOKENenvironment variable (first priority)gh auth tokenoutput (fallback)
-
NewClient()- Creates authenticated REST API client for PR metadata -
GetCurrentPR()- Auto-detects PR number from current branch viagh pr view -
ParseOwnerRepo()- Extracts owner/repo from git remote origin (supports SSH and HTTPS formats) -
FetchPRInfo()- Retrieves PR metadata (title, SHA, timestamps) via REST API -
FetchCheckRunsGraphQL()- Fetches check runs with workflow names via GraphQL- Uses single GraphQL query for efficiency
- Returns
CheckRunInfowith workflow name, job name, status, and timestamps - Matches the approach used by
gh pr checks --watch
User configuration lives in ~/.config/gh-observer/config.yaml:
refresh_interval: 5s # How often to poll GitHub API
colors:
success: 10 # ANSI 256-color code for completed checks
failure: 9 # ANSI 256-color code for failed checks
running: 11 # ANSI 256-color code for in-progress checks
queued: 8 # ANSI 256-color code for queued checksThe internal/config/config.go module uses Viper with defaults if config doesn't exist.
The application returns meaningful exit codes:
- 0 - All checks passed successfully
- 1 - One or more checks failed (failure, timed_out, or action_required)
- 1 - Error during execution (authentication, network, etc.)
Exit code determination happens in internal/tui/update.go:
allChecksComplete()checks if all checks have statuscompleteddetermineExitCode()scans for failure conclusions and returns 1 if any found- The TUI automatically quits when all checks complete
The application tracks GitHub API rate limits:
ChecksUpdateMsgincludesRateLimitRemainingfrom API response- When remaining < 10, the refresh interval triples (
m.refreshInterval * 3) - Default rate limit assumption is 5000 if not available in response
The TUI has special handling for GitHub Actions startup delay (typically 30-90s):
- Displays helpful "Startup Phase" message while waiting for checks
- Shows elapsed time since PR creation
- Only polls for check runs after receiving the head SHA from PR info
git clone https://github.com/fini-net/gh-observer.git
cd gh-observer
just build
./gh-observergo install github.com/fini-net/gh-observer@latest# Auto-detect PR from current branch
gh-observer
# Watch specific PR number
gh-observer 123
# Use in CI pipelines (exits with check status)
gh-observer && echo "All checks passed!"go1.25+ - Go programming languagegh- GitHub CLI (for auth and PR detection)git- Version control
just- Command runner for development tasks
github.com/charmbracelet/bubbletea- TUI framework (Elm Architecture)github.com/charmbracelet/lipgloss- Terminal styling and layoutgithub.com/charmbracelet/bubbles- Reusable TUI components (spinner)github.com/google/go-github/v58- GitHub REST API client (PR metadata)github.com/shurcooL/githubv4- GitHub GraphQL API client (check runs)github.com/spf13/cobra- CLI framework for command-line argument parsinggithub.com/spf13/viper- Configuration managementgolang.org/x/oauth2- OAuth2 authentication for GitHubgolang.org/x/term- Terminal detection for snapshot vs interactive mode
- PR detection uses
gh pr viewcommand and parses JSON output - Owner/repo parsing supports both SSH (
git@github.com:owner/repo.git) and HTTPS formats - Check runs fetched via GraphQL for efficiency (single query gets workflow names)
- PR metadata fetched via REST API (simpler for basic PR info)
- GraphQL status/conclusion values normalized to lowercase for consistency
- All timestamps from GitHub API are parsed in RFC3339 format
- The TUI uses a spinner for visual feedback during polling
- Keyboard input is limited to 'q' and 'ctrl+c' for quitting
- Network errors during polling are non-fatal (stored in
m.errbut polling continues) - The application polls every 5s by default, configurable via
refresh_interval - Terminal detection uses
term.IsTerminal(os.Stdout.Fd())to switch between TUI and snapshot modes .repo.tomlfile configures repo metadata and feature flags (used by just recipes for Claude/Copilot reviews)