# Review Pull Requests on a Go CLI Tool

Yes. An agent checks out the pull request, runs go vet, go test -race and golangci-lint in its sandbox, and posts one review on flags, output and errors.

[Run this on Rush](https://cloud.getrush.ai/run/review-pull-requests/go-cli/), in a Project that has your repository.

## What the agent does

The agent reads the command's surface before the diff, because in a CLI the review is mostly about what a flag does and what the user sees. In charmbracelet/glow, module `charm.land/glow/v3` on Go 1.26.7, `main.go` defines `rootCmd` with `Use: "glow [SOURCE|DIR]"`, `Args: cobra.MaximumNArgs(1)`, and the flags `--config`, `-p/--pager`, `-t/--tui`, `-s/--style`, `-w/--width`, `-a/--all`, `-l/--line-numbers`, `-n/--preserve-new-lines` and a hidden `-m/--mouse`, each bound to Viper so `glow.yml` and `GLOW_*` environment variables can set it. `validateOptions` reads those values back, refuses `--pager` with `--tui`, checks a custom style path exists, switches to the `notty` style when stdout is not a terminal and no `--style` was passed, and caps auto-detected width at 120 with a fallback of 80. `sourceFromArg` turns `-` into stdin, a `github://` or `gitlab://` or bare host path into a README URL through `url.go`, an `http` or `https` URL into a fetched body, a directory into the first README it finds, and anything else into a file. `config_cmd.go` writes a `defaultConfig` and opens it in `EDITOR`.

Then it reads what the repository already checks. `.github/workflows/coverage.yml` runs `go test -race -covermode atomic -coverprofile=profile.cov ./...`; `lint.yml` runs golangci-lint with `--issues-exit-code=0` and `only-new-issues`, so lint never fails the build; `build.yml` calls Charm's shared workflows for build, snapshot, govulncheck, semgrep and ruleguard with Go 1.26. `.golangci.yml` is a version 2 config with `run.tests: false` and enables `bodyclose`, `exhaustive`, `goconst`, `godot`, `gosec`, `misspell`, `nilerr`, `noctx`, `revive`, `wrapcheck` and others, with `gofumpt` and `goimports` as formatters; `main.go` already carries `//nolint:gosec`, `//nolint:nestif` and `//nolint: noctx,bodyclose` where it knows it breaks them. `Taskfile.yaml` has `lint` as `golangci-lint run` and `test` as `go test ./...`. The agent runs `go vet ./...`, `go test -race ./...`, `golangci-lint run` and `gofumpt -l .` in the sandbox and quotes each.

The tests are few and specific, which shapes what the agent can prove. `glow_test.go` parses `-p`, `-s light` and `-w 40` through `rootCmd.ParseFlags` and checks the package-level variables; `url_test.go` maps `github.com/charmbracelet/glow`, `github://caarlos0/dotfiles.fish` and GitLab forms to raw README URLs but is skipped because it uses the network; `ui/pager_test.go` sets `GLOW_TEXT_SIZING=on` and asserts that an H2 is wrapped in the OSC 66 `s=2` escape while an H1 with a background is not; `ui/ui_test.go`, `ui/rawpaint_test.go` and `utils/textsize_test.go` cover the rest. A pull request that adds a flag without a case in `TestGlowFlags`, or changes rendering without a case beside `TestGlamourRenderTextSizing`, gets a finding that names the test to extend, and the agent writes that case in the sandbox to confirm the behaviour before it says so.

The diff is read with the three questions the prompt names. Flag handling: a new flag needs a `BindPFlag` line, a `viper.SetDefault` where the config file should carry it, a mention in `defaultConfig`, and a read in `validateOptions`, since the flag variables are overwritten from Viper there; a flag read directly from its variable skips the config file. Terminal output: `executeCLI` builds a `glamour.TermRenderer` with the style, `WithWordWrap(width)`, a base URL for relative links and `WithPreservedNewLines`, wraps non-markdown sources in a code block by extension, and pipes to `PAGER` or `less -r` through `shell.Fields`; a change that writes to `os.Stdout` directly instead of the `io.Writer` passed in, or that ignores the `notty` style, is a finding. Error messages: the repository wraps every error with `fmt.Errorf("...: %w", err)` and `wrapcheck` enforces it, `rootCmd.Execute` exits 1 after closing the log, and `sourceFromArg` returns `missing markdown source` for a directory with no README; a bare `return err`, a `log.Fatal` inside the TUI, or a message that prints a raw path the user did not type is flagged with the line. One GitHub review is posted per run and nothing is pushed.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Review this pull request on a Go CLI built with Cobra, Viper and Bubble Tea. Check it out, run go vet ./..., go test -race ./..., golangci-lint run with the repository's .golangci.yml and gofumpt -l ., and read the diff with main.go, config_cmd.go, url.go, ui/config.go and the tests beside them. Look for flags added without a BindPFlag, a default, a defaultConfig entry and a read in validateOptions; output written straight to os.Stdout instead of the writer the function takes, or rendering that ignores the notty style when stdout is not a terminal; errors returned unwrapped where wrapcheck expects fmt.Errorf with %w, or messages that leak internal paths; and behaviour changes with no case in glow_test.go, url_test.go or ui/pager_test.go. Confirm a finding with a short test in the sandbox where you can, but do not add it to the branch. Post one GitHub review that quotes the vet, race, lint and gofumpt results, separates blocking issues from suggestions, and cites file and line for every finding. Do not push commits.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app/pull/42"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is a review on the pull request. Its summary quotes `go vet ./...`, `go test -race ./...` with any race report, `golangci-lint run` with the linters that fired, and the files `gofumpt -l` listed. Findings follow with file and line in `main.go`, `ui/` or `utils/`, marked blocking or suggestion, and a flag finding lists the four places the flag must be wired through.

The transcript keeps the module download, every command with its output, and the short tests the agent wrote to confirm a finding, so when the review says a new flag is ignored when set from `glow.yml`, the parse that proved it is in the record.

Before merging, a reviewer runs `go test -race ./...` on the branch, tries the new flag from a terminal and from a pipe, and opens each cited line. A race report, a `wrapcheck` failure or a flag that skips Viper is blocking by default; a `godot` or `misspell` note is not, which matches the repository's own `--issues-exit-code=0`.

## Run it on every pull request opened

Create an automation in the Project with a `github` trigger on `pull_request.opened` and `pull_request.synchronize`, `branch_patterns` set to `main`, and this prompt. The trigger adds the pull request after the prompt, so every pull request and every push to it gets its own sandbox, its own module cache and its own review, and a `read_only` automation comments without pushing.

The review is the result on the pull request and the run is listed in the Project. `max_runs_per_hour`, 1 to 30, keeps the monthly grouped Dependabot update that `.github/dependabot.yml` configures to a bounded number of runs; a fire past the cap is skipped as `rate_limited` and the pull request is reviewed on its next push.

## Why run it on Rush?

A CLI change is judged by what the user sees, and that depends on whether stdout is a terminal, what `PAGER` is set to, whether a config file exists and how wide the window is. Reading the diff tells a reviewer none of that; running the binary from a pipe and from a pty with a `glow.yml` in `XDG_CONFIG_HOME` does. On Rush the agent has its own sandbox with a terminal, builds the branch, runs the race detector and the linters the repository configures, and keeps every command's output, so the review of a flag or a rendering change rests on something that ran.

One sandbox per pull request keeps reviews parallel and the branch untouched; the author keeps their checkout and the reviewer gets findings with the `go test -race` and `golangci-lint` output attached, in the same form the repository's `coverage.yml` and `lint.yml` would report later.

## Frequently asked questions

### Does it run golangci-lint the way the repository does?

It uses `.golangci.yml`, a version 2 config with `run.tests: false`, so test files are not linted. The repository's `lint.yml` passes `--issues-exit-code=0` and `only-new-issues`, so the review quotes the findings but blocks only on ones the pull request introduced.

### Can it test the TUI?

The unit tests in `ui/` render a pager model without a terminal, and the agent can extend those in the sandbox. A full `glow` session in TUI mode is started from a pty in the sandbox to check it draws and quits, and the review labels anything observed that way as a manual check rather than a test.

### What about the skipped URL test?

`url_test.go` is skipped because it fetches README URLs from GitHub and GitLab. A change to `url.go`, `github.go` or `gitlab.go` is confirmed by a table test of the URL construction alone, written in the sandbox, so the review does not depend on the network.

### Which Go version does it build with?

`go.mod` says 1.26.7 and `build.yml` runs ruleguard with Go 1.26, so the agent uses that toolchain. A diff that raises the `go` directive or depends on a newer standard library API is a finding with the line.
