Find the commit that broke production
A new error started showing up in production after a deploy. You want to know which change caused it and who wrote it. Here is the manual way to find out, then how ZipLogger runs the same steps for you, and where that stops working.
The manual method
It always comes down to the same steps:
- Get a stack trace with file names and line numbers. An error message alone ("Object reference not set to an instance of an object") gives you nothing to search for.
- Find the frames in your own code. Framework frames tell you where it failed, not what changed.
- Blame those lines at the commit that was deployed. That gives you the last commit to touch each line.
- List what shipped between the last good release and the bad one, and look for overlap with the blamed commits.
- If nothing stands out, bisect.
Here is a typical .NET trace:
System.NullReferenceException: Object reference not set to an instance of an object.
at Shop.Orders.OrderService.Process(Order o) in /src/Orders/OrderService.cs:line 142
at Shop.Api.RequestHandler.Handle() in /src/Api/RequestHandler.cs:line 88
at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
Two frames are yours. The git commands for steps 3 to 5:
# Who last touched the failing lines, as of the deployed commit
git blame -L 135,145 3f9c2a1 -- src/Orders/OrderService.cs
git blame -L 82,92 3f9c2a1 -- src/Api/RequestHandler.cs
# What changed in those folders between the two releases
git log --oneline v2026.9.1..v2026.9.2 -- src/Orders/ src/Api/
# Still unclear: let git find the first bad commit
git bisect start v2026.9.2 v2026.9.1
A commit that shows up in both the blame and the release diff is your best lead. It is a lead, not proof. Blame only says who last changed a line. The real cause can be a caller that now passes null, a config change, or a dependency upgrade.
This works, but it takes a local clone, the right commit checked out, and ten minutes of copying paths out of a log viewer. That is the part worth automating.
How ZipLogger does it
In ZipLogger, errors are grouped into patterns (see log pattern clustering). Go to Search → Patterns, expand an error pattern and click Find the commit that caused this. On the Issues page, an Issue's Find suspect commit button runs the same analysis on that Issue's stack trace. This is what happens next.
1. Parse the stack trace
The parser pulls file:line pairs out of the trace. It understands three shapes:
- .NET:
in /src/Orders/OrderService.cs:line 142 - Python:
File "/app/orders/service.py", line 42 - Anything written as
file.ext:lineorfile.ext:line:column. That covers Node.js and TypeScript (/src/app.ts:88:12), Java (OrderService.java:142), Go (service.go:88) and others in the same style.
Repeated frames are counted once.
2. Match frames to files in your repository
ZipLogger lists the repository's file tree once through the GitHub or GitLab API. Each frame is matched by file name. When several files have the same name, it picks the one that shares the most trailing folders with the frame. So /src/Orders/OrderService.cs on your server matches src/Orders/OrderService.cs in the repo, even though the build ran in a different directory.
3. Blame only those files, through the API
Nothing is cloned. For each matched file, ZipLogger asks GitHub (through its GraphQL blame) or GitLab (through its blame API) which commit last touched each line, on the branch you configured. Each file is blamed once per analysis, however many frames point into it.
4. Rank the suspects
Every frame whose line lands on a commit gives that commit points: 1 for the hit, plus a recency bonus of 1 / (1 + age in days / 14). A commit from today earns 2 points per line. One from two weeks ago earns 1.5, and one from six weeks ago earns 1.25. Points add up per commit, so a commit that touched three lines in the trace beats one that touched a single line.
You get the top five in a table with the columns Commit, Author, When, Score, Message and Blamed lines. The score is relative to the top suspect, so the first row always reads 100%. Read it as an order, not a probability.
5. Show what changed
ZipLogger fetches the diff of the top suspect and shows it under WHAT CHANGED. You do not need an AI key for this part.
If you have added your own Anthropic, OpenAI or Gemini key under Settings → AI analysis, you can click Ask AI: root cause & suggested fix. It sends the error, the stack trace and that one diff to your provider and comes back with a root cause and a suggested fix. The prompt asks the model to judge whether the commit plausibly caused the error, not to assume it did.
From there you can Mark resolved, flag it as a False positive, or Re-analyze.
What you need to set up
Stack traces in your error logs
This is the input, so it matters most. Log the exception object, not just a message:
catch (Exception ex)
{
logger.LogError(ex, "Payment failed for order {OrderId}", order.Id);
}
try:
charge(order)
except Exception:
log.exception("Payment failed for order %s", order.id)
Over OpenTelemetry, the stack trace comes from the exception.stacktrace attribute on a log record (see sending OTLP traces and logs). If a pattern has no stack trace, the button is replaced by "Attribution unavailable: this error was logged without an exception".
In .NET, line numbers only appear in the trace if the .pdb files are deployed next to your assemblies. Without them there is no :line 142 to blame.
A connected repository
Go to Settings → Integrations, find the Repositories card and click Connect repo. You have these options:
- GitHub App, when your workspace shows it: click Connect GitHub →, install the app and pick repositories. No token to create or rotate.
- GitHub token: a fine-grained token scoped to the repo with Contents: Read-only.
- GitLab token: a personal or project access token with read_repository. gitlab.com and self-managed GitLab both work.
Tokens are encrypted at rest and never shown again. With the GitHub App, ZipLogger asks GitHub for a short-lived installation token when it runs an analysis.
Fill in Default branch with the branch your production builds come from, or leave it empty to use the repository's default. That is the branch blame runs against.
Git regression detection is included on the Pro, Team and Business plans. It is not on the Free plan.
Release and commit on your events (recommended)
The ZipLogger SDKs fill in the release and commit for you. You can also set them in the environment:
docker run \
-e ZIPLOGGER_RELEASE=2026.9.2 \
-e ZIPLOGGER_COMMIT_SHA=$(git rev-parse HEAD) \
your-image
The SDKs also read GIT_COMMIT and COMMIT_SHA, and the .NET SDK reads the +sha suffix of the assembly's informational version. Over OpenTelemetry, use the service.version and vcs.ref.head.revision resource attributes.
ZipLogger records each new release the first time it sees it. The pattern's examples then show a Release and a Release date column, which tells you which deploy the error first appeared in. That gives you the release range for step 4 of the manual method. Be aware that the blame step does not use this commit today. It blames the head of the branch you configured.
Where it does not work
- Minified JavaScript. Blame does not use source maps, even when you upload them for the Issue page. A browser frame such as
index-4f2a91.js:1:48213points at a bundle that is not in your repository, so you get "No suspect commit found". Server-side Node.js works when the frames name your source files. For TypeScript, run Node with--enable-source-mapsso the traces point at.tsfiles and not at compiled output. - Traces without
file:line. Formats the parser does not recognize give nothing to blame. PHP'sFoo.php(42)frame style is one example. A trace with nofile:lineframes at all, such as a timeout thrown from framework code, is refused, because there is nothing to blame. - Same file names in libraries. Frames are matched by file name. A library frame whose file name also exists in your repo, such as
index.js, can be matched to your file. Check the Blamed lines column before you act on a result. - The branch moved after the deploy. Blame runs on the current head of the branch, not on the commit that was running. If later commits moved the lines around, the line numbers in the trace may land on different code. Analyze soon after the error appears.
- Deleted lines and non-code causes. Blame can only show lines that exist. If the bug is a null check someone removed, you see the commits around it. Config changes, bad data and dependency upgrades don't show up in blame at all.
- Other git hosts. Only GitHub (github.com) and GitLab (gitlab.com or self-managed) are supported. Bitbucket, Azure DevOps and GitHub Enterprise Server are not.
- Very large repositories. GitHub can truncate the file tree listing for huge repos. Files missing from the listing can't be matched.
Next step
The setup reference is in the docs under git regression detection. If you already send errors with stack traces, connect a repository and run it on your noisiest error pattern. New here? Sign up and send one exception first.