# Fix Go CLI Errors from Sentry Alerts

Yes. An agent reads the Sentry event from your Go CLI, reproduces the panic or wrong output in a Go test, fixes it and opens a pull request.

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

## What the agent does

The agent reads the alert payload after the prompt, then opens the issue and its latest event through the Sentry connection. For a CLI the useful parts are the panic or error message, the goroutine stack with its in-app frames, and the tags and breadcrumbs the SDK attached: the arguments the command was run with, whether stdout was a terminal, the operating system and the terminal program. It keeps the frame's file and line and the command line as the two facts the test must reproduce.

Then it reads how the command is put together. In a Cobra CLI like charmbracelet/glow, module `charm.land/glow/v3` on Go 1.26.7 with Cobra 1.10.2, Viper 1.21.0, Bubble Tea v2 and Glamour v2, `main.go` defines the root command and its flags as package variables: `--pager`, `--tui`, `--style` with default `auto`, `--width`, `--all`, `--line-numbers`, `--preserve-new-lines` and a hidden `--mouse`, each bound to a Viper key, and `tryLoadConfigFromDefaultPlaces` reads `glow.yml` from `GLOW_CONFIG_HOME`, then `XDG_CONFIG_HOME/glow`, then the user config directories. `validateOptions` runs as `PersistentPreRunE`: it copies the Viper values into the flag variables, forces the style to `notty` when stdout is not a terminal and `--style` was not passed on the command line, and caps a detected width at 120. `sourceFromArg` turns an argument into a reader: `-` for stdin, `github://` and `gitlab://` shorthand through `url.go`, an http or https URL fetched with `http.Get` and no `Accept` header, a directory walked for a README, or a file. `executeCLI` strips front matter, decides markdown versus code with `utils.IsMarkdownFile`, whose list is `.md`, `.mdown`, `.mkdn`, `.mkd` and `.markdown`, and renders through `utils.GlamourStyle`, where `styleConfigFor` maps `auto` to the dark style for markdown and only asks the terminal for its background on the code path.

The open issues on this repository are the shape a Sentry event from a CLI like it takes, and each lands in one of those functions. A style set in `glow.yml` is ignored when output is piped, because `validateOptions` checks `cmd.Flags().Changed("style")` rather than whether Viper has a value. A file named `.mmd` renders as a code block, because of the extension list. `style: auto` renders dark on a light terminal, because the background check is on the code path only. A URL is fetched without `Accept: text/markdown`. Each one is a function with a small input and a visible output, which is what makes the test short.

The agent writes the test beside the code. For flag and config behavior it extends the table in `glow_test.go`, which already drives `rootCmd.ParseFlags` with `-p`, `-s light` and `-w 40`, adding a case that sets the Viper key and calls `validateOptions` with stdout redirected to a pipe. For rendering it calls `executeArg` with a temporary file and a `bytes.Buffer`, since the function takes an `io.Writer`, and compares the output against the expected style or wrapping. For `IsMarkdownFile` and `styleConfigFor` it adds cases in `utils/`. It watches the test fail with the message from the event, fixes the cause, runs `go test -race ./...` as `.github/workflows/coverage.yml` does and `golangci-lint run` with the repository's `.golangci.yml`, and opens a pull request that links the Sentry issue and quotes the frames.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "A Sentry alert fired for this Go CLI; the payload follows this prompt. Use the Sentry connection to read the issue, its latest event, the stack frames, the tags and the breadcrumbs, and note the command line and whether stdout was a terminal. Find the function the in-app frames point at in main.go, url.go or utils/. Write a Go test beside it that reproduces the input: extend the flag table in glow_test.go for flag and config behavior, call executeArg with a temporary file and a bytes.Buffer for rendering, or add a table case in utils/ for a helper; confirm it fails with the same error. Fix the cause rather than recovering the panic, run go test -race ./... and golangci-lint run, and open a pull request that links the Sentry issue, quotes the frames and the failing test output, and explains the cause. If it does not reproduce, stop and report what you tried.\n\nAlert payload:\n{payload}",
  "connections": [
    "sentry"
  ],
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

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 pull request that changes one function in `main.go`, `url.go` or `utils/` and adds or extends a `_test.go` file beside it. Its description links the Sentry issue, quotes the panic or the wrong output and the command line from the event, shows the new test failing before the change and passing after, and gives the `go test -race ./...` summary and the lint result.

Before merging, a reviewer checks that the fix changes the decision the function makes rather than wrapping it in `recover`, that a flag or config change keeps the precedence Viper already gives the command line over `glow.yml`, and that a rendering change is covered by the buffer test rather than by a manual run. If the event came from a release older than main and the code already changed, the description says so and the test pins the current behavior.

## Run it on every Sentry issue alert

Create an alert rule in the Sentry project for the CLI with a webhook action pointed at a handler on your server. The handler sends this request with the alert's JSON after the prompt and the Sentry issue id as `Idempotency-Key`, so a redelivered alert returns the run already started. Sentry signs its own requests and cannot add the `Authorization: Bearer` header a Rush `webhook` trigger needs, which is why the handler sits in between.

Set a webhook URL on the API key and Rush POSTs `run.completed` with the `session_url` and the result when the turn ends; the handler can post the pull request link as a comment on the Sentry issue. A CLI issue that fires from many machines should open one pull request, so the handler keys on issue id and skips issues that already have one.

## Why run it on Rush?

A CLI bug is cheap to fix and awkward to reproduce: it depends on a config file in the right directory, on whether stdout is a pipe, on the terminal's reported background, on an extension or a URL. The agent sets those conditions in a test in its own sandbox, with the environment variables and the pipe it needs, runs the one test that matters, and hands back a diff. Several alerts are several sandboxes at once.

The pull request carries its own evidence. The transcript shows the Sentry event the agent read, the test failing with the same message and passing after the change, and the exact `go test -race` and lint output, so the reviewer judges the fix rather than reconstructing the terminal the user had.

## Frequently asked questions

### A terminal CLI reporting to Sentry? What does the event contain?

Whatever the Go SDK in your CLI attaches: the panic or captured error, the stack, and the tags and breadcrumbs you add, which for a CLI should include the arguments and whether stdout was a terminal. The agent works from those; if the event has no command line, it says so and reproduces from the frames alone.

### How does it test a code path that depends on a pipe or a terminal?

By redirecting stdout to a pipe inside the test before calling `validateOptions`, and by setting `GLOW_CONFIG_HOME` to a temporary directory holding a `glow.yml`. Rendering is tested through `executeArg` with an `io.Writer`, so no terminal is involved. The Bubble Tea view is out of scope for this kind of test and the agent says so if the frames land there.

### Will it add recover() around the command?

No. The prompt asks for the cause. A panic in a helper means an input the function did not expect, and the fix is to handle that input where it is read, not to swallow the panic at the top of `main`.

### Does it need the user's terminal or config to reproduce?

Only as test inputs. The config file is written into a temporary directory the test points `GLOW_CONFIG_HOME` at, and terminal properties are either faked through the pipe or passed as explicit arguments to the helper under test. The user's machine is never touched.

### What about the lint step?

`.github/workflows/lint.yml` runs golangci-lint with `--issues-exit-code=0`, so lint findings do not fail CI, but the pull request still reports a clean `golangci-lint run` under the repository's `.golangci.yml`, which enables `gosec`, `noctx`, `wrapcheck` and others, so the fix does not add a finding.
