Back to Blog
The latest commit was guilty 32% of the time

The latest commit was guilty 32% of the time

When a deploy breaks production, the usual guess is the newest commit. We measured it: right 32% of the time. Kaizen, our Graph Hacks build on FalkorDB, walks the call graph from the error back to the change and gets 89%.

10 min read
  • falkordb
  • knowledge-graph
  • hackathon
  • root-cause-analysis
  • observability
  • cypher
  • postgres
  • ai-agents

The Kaizen landing page. A small blue label reads "Graph-powered root-cause analysis" above the headline "Find the change that broke production" and the line "Kaizen links every incident to the deployment, commit and function behind it, and shows the evidence path from change to failure." Below an "Open dashboard" button sits a dashboard preview: "Good evening, Maya", stats for 3m mean time to detect, 12 causes confirmed and 81 commits mapped, and a recent incident "Elevated failures in orders" with suspected cause toCents · 9f2c1ab, status Investigating.

We built Kaizen for Graph Hacks (WeMakeDevs × FalkorDB, October 15 to 18, 2026). It watches a running app and, when a deploy breaks it, names the commit and the function behind the failure, with the evidence. Then it opens a revert and waits for a person to approve it.

Live demo: https://kaizen.akkki.tech (click Try the demo, no GitHub account needed). Code: https://github.com/akkki007/kaizen.

The problem

Something breaks right after a deploy. The deployment had several commits, and the on-call engineer has to work out which one did it. The usual first guess is the most recent commit, followed by searching commit messages for words from the error.

We measured both habits. Across the 28 failure scenario variants we built on ShopLite, a small e-commerce app with payments, orders and inventory, the most recent commit was the culprit 32% of the time. Keyword search got 39%.

BM25 — the standard keyword-ranking formula behind most search engines. It scores a document by how often the query's words appear in it, discounting words that appear everywhere. Here the "documents" were commit messages, file paths and function names.

Both miss for the same reason. They treat commits as text and ignore what the code does. A commit that touches toCents matters when toCents is called by the function that is throwing, and keyword search has no way to know that.

The idea: walk from the error to the change

Kaizen keeps the repository as a graph in FalkorDB: commits, the functions they changed, which functions call which, the endpoints those functions serve, and the services they belong to. When an error arrives, it is attached to the function that threw it. Finding the culprit then becomes a walk through the graph:

commit -> function it changed -> call path -> function that threw -> endpoint -> service

The Kaizen pipeline, left to right: Telemetry (errors, latency) feeds Detect (Postgres windows), then FalkorDB "Walk the graph" from a commit sha through the call path to the function that threw, scored with 10 Cypher features and fixed weights; then Explain (the LLM cites ids), Alert (Slack), Revert PR (staging verify) and Approve (a person merges).

Postgres holds application state and raw telemetry. It answers "did the error rate jump in the last two 30-second windows?". FalkorDB answers the multi-hop questions, such as "how far is this commit's change from the function that threw?". Call paths in Postgres would mean slow recursive SQL, and raw telemetry samples would bloat the graph.

How a suspect gets ranked

Every candidate commit in the failing deployment gets ten feature scores. Each one is a Cypher query that returns a value, the graph ids it used and a sentence explaining it: path distance to the failing function, whether the change sits behind the failing endpoint, timing relative to the onset, PageRank centrality, a similar past incident, and so on. A fixed, versioned set of weights adds them up.

Score breakdown for the top suspect, commit 9f2c1ab "Round amounts in toCents" by bruno in deployment d-41, total 0.78 at 86% confidence, marked Confident with margin 0.57. Each feature has a bar, value, weighted contribution and cited graph ids: temporal 0.90 (+0.135, deployed 2 minutes before the onset), path 1.00 (+0.250, changes toCents, which RangeError is thrown in, distance 0), endpoint 1.00 (+0.150, handlePayment handles POST /payments), service 1.00 (+0.100, payments), dependency 0.00 (no dependency change), msg_sim 0.20 (+0.010), history 0.80 (+0.080, a similar past incident inc-1 blamed this function), sequence 1.00 (+0.050, last change to the failing function before the onset), centrality 0.80 (+0.040, toCents is called from 3 services), blast 0.30 (−0.030, wide blast radius lowers certainty slightly).

The language model does not pick the culprit. It writes the explanation after the ranking is decided, and every claim must cite graph ids that a validator checks. If the ranking is confident, the model cannot override it. When the margin between suspects is small, the result is marked "ambiguous" and the top three are shown as tentative. With no function-level evidence at all, it reports "no code evidence".

The incident page for "Elevated failures in payments" on yashchandnani07/shoplite, status Verifying fix. A pipeline row shows Detect, Investigate, Explain, Fix and Verify done, Remember pending. The error-rate chart for POST /payments sits near 0.5% until deployment d-41 (9f2c1ab), then jumps to around 38–45%. Below, the suspect list ranks commit 9f2c1ab "Round amounts in toCents" first at 0.78. The side panel shows a 64%-similar resolved incident inc-1 fixed by a revert, and a status history from open to verifying.

Results

On the same 28 scenarios, with distractor commits mixed in:

Top-1 / Top-3 — the share of scenarios where the real culprit was ranked first, or anywhere in the first three. False attribution — the share where the method confidently named the wrong commit.

Method Top-1 Top-3 False attribution
Kaizen graph score 89% 100% 0%
Graph score without the path feature 57% 100% 11%
BM25 keywords 39% 79% 61%
Most recent commit 32% 54% 68%

Read the first row again. The 11% Kaizen didn't get at top-1 were never confidently wrong; they came back flagged instead of naming the wrong commit. For an on-call engineer, "I'm not sure, here are three" is far cheaper than a wrong answer delivered with confidence.

On the held-out split, which we never tuned on, the graph score gets 83% top-1 against 44% for BM25 and 28% for the latest commit. Detection to a ranked hypothesis takes about 194 ms (p50), before the LLM writes anything.

Fixing it safely

The default fix is a revert of the culprit commit, because a revert is small and easy to review. Kaizen pushes it to a kaizen/fix-<incident> branch, never to main. The target repo's own GitHub Actions run the tests, deploy to staging and replay the recorded failing traffic. Nothing merges until someone with push access approves it.

Seeing the graph

The first version drew every function, service and commit in one canvas, and with all of them on screen it was hard to read. We replaced it with the Graph map: one service at a time, read left to right. Commits are on the left, the service's functions in columns by call depth, and the endpoints it serves on the right. When something fails, only that function turns red, and clicking any node explains what calls it, what it calls and which commit changed it.

The Graph map for the payments service (20 functions), with a FalkorDB badge reading "87 nodes · 92 edges, 2 Cypher queries in 5 ms". Commits c0ffee1, b4d5eed, 3e7a901, 91fe0ab and 5c2d8e4 sit in a column on the left. Functions are laid out by call depth: handlePayment, paymentStatus and refundPayment, then issueRefund, recordLedger and validatePayment, then checkCurrency, fraudScore, stripeRequest and toCents, then loadRates. validatePayment is solid red and labelled FAILING under a "Failure on the call path" box. Endpoints POST /payments, POST /refunds and GET /payments/:id are on the right. A side panel lists what Kaizen is doing: traffic ingested into Postgres, failure detected over two 30 s windows, stack trace mapped to the function that threw, deployment commits gathered from the graph, culprit ranked with shortestPath and algo.pageRank, team alerted on Slack.

The payments graph with validatePayment selected and its edges highlighted in blue. An explanation panel below reads "Function · Failing: validatePayment. This function is throwing errors in production. Its callers (handlePayment) inherit the failure, and it was recently changed by c0ffee1." Called by: handlePayment. Calls: toCents, checkCurrency, fraudScore. Changed by: c0ffee1.

The same data is also a selectable table, which is easier for scanning or searching every node. The screenshots use the demo repository.

The table view of the graph: 79 nodes and 92 edges, two rows selected. Columns are name, kind, service, state, out, in and detail. Rows are commits such as c0ffee1 "Refactor payment validation" (payments), a11ce22 "docs: update README" (orders), 3e7a901 "Retry Stripe requests with backoff", 7aa31c0 "Round half-even for cents", 0d9b6f2 "Reserve items before charging" (orders, inventory), 2b8e4a1 "Shorter JWT expiry" (auth) and 66c0de9 "Rate limit per API key" (gateway), all marked healthy.

What we learned

The call path did most of the work. Turning the path feature off dropped top-1 from 89% to 57%. Turning off history or dependency changes made no difference on our scenarios. Whether the changed code sits on the failing call path decides most cases, and that question needs a graph.

Fitting the weights made things worse. We fitted them on the tuning split, and they scored lower on the held-out data than the hand-set weights. With 28 scenarios that is easy to do by accident, so we kept the hand-set weights and kept a guard that stops fitting from seeing test data.

We kept the LLM out of the ranking for three reasons. A model choosing the culprit gives different answers between runs and model versions. It costs money on every evaluation run. And commit messages and log lines are text an attacker controls, which the model would read and could be steered by. Making it explain a decision that plain code already made, with citations checked against the graph, removed all three problems.

One background job per event was too slow. At 1,000 events a second the first ingest path managed 531, with a median batch latency of 18 seconds. Storing telemetry rows directly from the API and running at most one graph job per batch brought it to 496 events a second sustained at a 500 target, with p95 of 431 ms per batch.

Shared status fields cause stuck incidents. Detection, investigation, remediation and people all used to write an incident's status directly, so a failed job could leave an incident stuck in "investigating", or a job could overwrite a person's decision. Every change now goes through one state machine, and background jobs can only move an incident forward from states they expect.

Graph views need help at scale. With 20 functions in one service, lines between neighbouring columns start to share the same vertical track and become hard to follow. Capping the view at 12 functions (failing ones and entry points first) and highlighting a node's own lines on click helped. Proper edge routing is still on the list.

What is not done yet

Function mapping covers JavaScript and TypeScript only. The agent's explanations have run on five scenarios and on demo incidents in production, but we have not measured them as a benchmark. Connected repositories still have to send telemetry with a snippet; a one-click setup is planned.

The scenarios, the evaluation harness and every number above are in the repository, and make eval reproduces the table.

If you keep one thing

The commit that broke production is almost always the one whose change sits on the failing call path, and finding that path is a graph walk, not a text search.

Sources

  • The Kaizen repository: the 28 ShopLite failure scenarios, the evaluation harness (make eval), and the ingest load tests. Every number in this post comes from those runs.
  • FalkorDB for the graph store and Cypher queries, including shortestPath and algo.pageRank.
  • Graph Hacks by WeMakeDevs × FalkorDB, October 15 to 18, 2026.
  • Screenshots are from the live deployment using the demo repository, October 2026.

Comments

Sign in with GitHub to leave one — every thread is a public issue on the site's repo.

Loading comments…