sixty

documentation

Naming the change that caused a finding

A finding already knows which deploy it crossed. Link a repository and it also knows which pull request in that deploy touched the file it runs in — so the answer stops being something changed and becomes a diff you can open without leaving the page, and a fix you can ask for.

Why this can be exact rather than a guess

The usual way to attribute a problem to a commit is to take a line of code and ask git who last touched it. That is a guess wearing the clothes of an answer: the last person to edit a line is frequently not the person whose change made it expensive, and it has nothing at all to say when the regression is that a caller started invoking something fourteen times.

A finding here is already bounded. It is one release against the release before it, and on every platform the agents read a release from — Vercel, Render, Railway, Heroku, Fly, GitHub Actions — that identifier is the sha of the commit that was deployed. So the set of changes that could possibly be responsible is not recent work. It is exactly everything between those two commits, and it is knowable rather than estimated.

What linking a repository adds is the narrowing: of everything in that deploy, which changes touched a file this finding’s own stack frames point at. That is usually one.

What you will see

On a finding, under What changed between these releases:

  • changed a file this finding runs in — the strong case, and the only one that is evidence. A change in this deploy edited a file that appears in the finding’s own frames.
  • the only change in this deploy — nothing matched, but the deploy contained exactly one change. Weaker, and labelled as such.
  • changed a file next to this one — a change in a directory the frames point into. Weakest, and shown only when there is a genuine near miss.

When nothing in the deploy touched the code, it says so and stops. It does not fall back to listing everything that changed — a latency regression “explained” by a README edit is worse than no answer, because it teaches the reader that the entries which are evidence mean nothing either.

Reading the diff without leaving

Every named change opens. Click it and the pull request arrives in a drawer over the finding: its description, its files, and its diff — with the files this finding’s own stack points at hoisted to the top and marked.

That ordering is the part GitHub cannot do for you. It shows a pull request in alphabetical path order, which is a reasonable default and is never the order the answer appears in; the intersection of the deploy’s changed paths with the frames the finding was measured in is a fact this system holds and github.com does not. The same diff is available to a coding agent as get_change on the MCP server, for the same reason and in the same order.

Nothing about that diff is stored. It is fetched with the installation’s own token in the request that asked for it, handed back, and dropped.

And, if you want it, the fix

From the same drawer you can ask for the change itself. A model is given the measurement, the diff above, and the current contents of the files the finding runs in at the release that produced it — and either writes the fix or declines.

Declining is a real answer and a frequent one. The cause of a finding is often in a schema, an index, a configuration value or a file it was not shown, and a confident edit to the wrong file costs more than no edit at all. When it does decline you still get the diagnosis, which is the part worth reading either way.

What comes back is the complete contents of each file it changed, not a patch — a model-written diff is a diff that frequently does not apply, and applying one approximately is how a change lands somewhere nobody reviewed. The consequence is a size limit: a reply cut off at the model’s output ceiling is a file cut off, so a file too long to come back whole is refused rather than half-written. Where that limit falls depends on the model configured; the drawer says so when it is reached instead of proposing something truncated.

What it opens is a draft pull request, on a new branch, in one commit. It never pushes to a branch that exists, never touches your default branch, and never merges. The description says which model wrote it, how confident it said it was, and that nothing in it has been run — because a machine-written change passing as a human one is the failure this could not come back from.

What it reads, and what it keeps

By default the app asks for three repository permissions, all read-only: contents, pull requests and metadata. With those, everything above works except the last step — nothing read-only can push a branch.

Opening a fix needs write access to contents and pull requests, and that is a separate grant you make on GitHub. Until you make it the button is still there and the refusal says so plainly rather than failing. Grant it and the surface it buys is exactly one operation: create a branch, put one commit on it, open a draft pull request.

What is stored is references and only references: commit shas, file paths, pull request numbers and titles, and the login of whoever authored them. No patch reaches the database and no file contents do — not the diff you read in the drawer, and not the fix a model writes. A proposal lives in the tab that asked for it: reload the page and it is gone, which is a real cost and the right one. What survives is the number and the URL of anything actually opened.

The cache exists because a comparison between two commits is immutable. Asked once, it is remembered — which is also what keeps this inside GitHub’s rate limits without asking for a larger budget.

Connecting

On Integrations, press Connect GitHub. GitHub asks you — the person at the screen, not an administrator elsewhere — to allow read access to the repositories you choose, and installs in the same pass. You come back with the repository picker filled in.

Then point each service at the repository its code lives in. That is a separate step on purpose: a front end, an API and a worker are routinely in three different repositories, and a single “your repository” setting would be right for the smallest case and quietly wrong for every other.

An installation belongs to one organisation. If you have several workspaces, each connects its own — the grant is made by an organisation rather than set globally.

Public repositories need no installation

A public repository’s commits and pull requests are already world-readable, so they are read without a token and with no app installed anywhere. An open-source project names its repository and gets nothing else to install.

The trade is GitHub’s unauthenticated budget — sixty requests an hour rather than five thousand — which is survivable only because of the cache above. A private repository answers 404 to an anonymous request, which is the same answer it gives for a repository that does not exist, so nothing here can be used to discover whether a private repository exists.

Where it will not help

  • Releases that are not commits. SIXTY_RELEASE=v4, a Docker tag, a build number — all perfectly good release identifiers, and none of them something GitHub can resolve. The finding says so rather than guessing.
  • Standing patterns. An unbounded query or a repeated query was wrong from the first release, so no deploy caused it and there is no diff to blame. The panel is not shown for those.
  • Deploys larger than GitHub will compare. Past 300 commits the changed-file list is incomplete, so failing to find a suspect stops being evidence of anything — and the page says that rather than implying otherwise.
  • Frames it does not have. Matching works from the stack frames captured for an operation. A finding on an operation with none has nothing to intersect, and will correctly name nothing.

Monorepos

Captured frames are relative to the process’s working directory, not to the repository root, so a service deployed out of apps/api has an offset. It is learned from the first deploy that changes a file the service measures, and shown on Integrations so a wrong guess can be corrected. Until it is learned, files are matched by name alone and the finding says so — in a monorepo the same filename can exist in several packages.

For your coding agent

The same answer reaches the MCP server. A finding an agent reads carries the suspect changes, each labelled with how much it is worth, and the services these requests continue into — because the fix is frequently not in the repository the agent is sitting in, and an agent that cannot know that will change something plausible in the code it can see.

Naming the change that caused a finding — sixty and GitHub