All jobs

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 RushIn 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 with this body:

{
  "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.

The use case this job follows, with its automation and trigger: Fix a bug from an alert.

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.