From b77644e7bf4aba6f89594342b7b559ea61f64bc0 Mon Sep 17 00:00:00 2001 From: Lushenwar Date: Sun, 11 Oct 2026 14:45:14 -0400 Subject: [PATCH 1/5] feat(core): rank suspect commits with the alert's stack trace An alert may now carry an optional `traceback`. When present, ranking gets the trace (newest 4000 chars) and each commit that touches a file named in a frame is marked "Touches files in the stack trace". Without a traceback the prompt is byte-identical to the Phase 9 baseline (golden-prompt test). git_client records each commit's changed files from the full patch, before the 3000-char diff truncation. Co-Authored-By: Claude Opus 5.5 --- contract/pipeline_schema.json | 3 +- core/services/git_client.py | 3 ++ core/services/llm_analyzer.py | 31 ++++++++++++++++ core/tests/test_traceback_ranking.py | 54 ++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 core/tests/test_traceback_ranking.py diff --git a/contract/pipeline_schema.json b/contract/pipeline_schema.json index 074c2b6..cc7f361 100644 --- a/contract/pipeline_schema.json +++ b/contract/pipeline_schema.json @@ -13,7 +13,8 @@ "source": { "type": "string" }, "alert_name": { "type": "string" }, "timestamp": { "type": "string", "format": "date-time" }, - "error_signature": { "type": "string" } + "error_signature": { "type": "string" }, + "traceback": { "type": "string", "description": "Optional stack trace; fed to commit ranking when present" } } }, "diagnostics": { diff --git a/core/services/git_client.py b/core/services/git_client.py index 83d850c..de55d74 100644 --- a/core/services/git_client.py +++ b/core/services/git_client.py @@ -1,3 +1,4 @@ +import re import subprocess from datetime import datetime, timedelta @@ -41,6 +42,8 @@ def get_recent_diffs(repo_path: str, alert_ts: str, window_minutes: int = 60) -> "author": author, "timestamp": ts, "subject": subject, + # from the full patch, before truncation, so traceback overlap sees every file + "files": re.findall(r"^diff --git a/.* b/(.*)$", diff.stdout, flags=re.M), "diff": diff.stdout[:3000], # ponytail: truncated for LLM context; raise if diffs are large } ) diff --git a/core/services/llm_analyzer.py b/core/services/llm_analyzer.py index edc8fdb..c9acfa9 100644 --- a/core/services/llm_analyzer.py +++ b/core/services/llm_analyzer.py @@ -1,4 +1,5 @@ import os +import re import anthropic @@ -27,6 +28,28 @@ def _runbooks_block(runbooks: list[dict] | None) -> str: ) +# ponytail: keep the newest frames only; recursion traces run to thousands of lines +_TRACEBACK_MAX_CHARS = 4000 + + +def _traceback_block(traceback: str | None) -> str: + """'' without a traceback, so an alert that carries none ranks with the baseline prompt.""" + if not traceback: + return "" + return ( + "STACK TRACE (from the alert; frames show where the error surfaced, which is not necessarily " + "where it was introduced):\n" + f"\n{traceback[-_TRACEBACK_MAX_CHARS:]}\n\n\n" + ) + + +def files_in_traceback(commit_files: list[str], traceback: str | None) -> list[str]: + """Commit files that appear as a frame in the traceback. Frames carry deploy paths + (/srv/app/x.py), commits carry repo paths (x.py), so match on a path-segment suffix.""" + frames = {p.replace("\\", "/") for p in re.findall(r'File "([^"]+)", line \d+', traceback or "")} + return [f for f in commit_files if any(p == f or p.endswith("/" + f) for p in frames)] + + class LLMUnavailable(Exception): """LLM output could not be produced: timeout/network, malformed JSON, or refusal.""" @@ -61,9 +84,16 @@ def rank_suspect_commits(diffs: list[dict], alert: dict, runbooks: list[dict] | if not diffs: return [] + traceback = alert.get("traceback") + + def _overlap_line(c: dict) -> str: + hits = files_in_traceback(c.get("files", []), traceback) + return f"Touches files in the stack trace: {', '.join(hits)}\n" if hits else "" + commits_block = "\n\n".join( f"COMMIT {c['commit_hash']} | {c['author']} | {c['timestamp']}\n" f"Subject: {c['subject']}\n" + f"{_overlap_line(c)}" f"Diff:\n{c['diff']}" for c in diffs ) @@ -74,6 +104,7 @@ def rank_suspect_commits(diffs: list[dict], alert: dict, runbooks: list[dict] | f" name: {alert['alert_name']}\n" f" error: {alert['error_signature']}\n" f" time: {alert['timestamp']}\n\n" + f"{_traceback_block(traceback)}" f"{_runbooks_block(runbooks)}" f"RECENT COMMITS:\n{commits_block}\n\n" f"Use rank_commits to return every commit with a confidence_score (0=unrelated, 1=certain cause) " diff --git a/core/tests/test_traceback_ranking.py b/core/tests/test_traceback_ranking.py new file mode 100644 index 0000000..aef2e51 --- /dev/null +++ b/core/tests/test_traceback_ranking.py @@ -0,0 +1,54 @@ +# ponytail: traceback context must not move the baseline prompt; overlap must match path segments only +from types import SimpleNamespace as NS +from unittest.mock import patch + +from core.services import llm_analyzer +from core.tests.test_rag_ranking import _ALERT, _DIFFS, _GOLDEN_BASELINE_PROMPT + +_TB = ( + "Traceback (most recent call last):\n" + ' File "/srv/orders/app/db.py", line 24, in fetch_all\n' + " conn = pool.getconn()\n" + "psycopg2.pool.PoolError: connection pool exhausted" +) +_FILED = [{**_DIFFS[0], "files": ["config.json", "app/db.py"]}, {**_DIFFS[1], "files": ["README.md"]}] + + +def _prompt(diffs, alert): + captured = [] + + def fake_chat(messages, **_): + captured.append(messages[0]["content"]) + return NS( + stop_reason="tool_use", + content=[NS(type="tool_use", name="rank_commits", input={"ranked_commits": []})], + ) + + with patch.object(llm_analyzer, "chat", fake_chat): + llm_analyzer.rank_suspect_commits(diffs, alert) + return captured[0] + + +def test_no_traceback_prompt_is_byte_identical_to_baseline(): + assert _prompt(_FILED, _ALERT) == _GOLDEN_BASELINE_PROMPT + assert _prompt(_FILED, {**_ALERT, "traceback": ""}) == _GOLDEN_BASELINE_PROMPT + + +def test_traceback_sits_after_alert_and_marks_only_overlapping_commits(): + prompt = _prompt(_FILED, {**_ALERT, "traceback": _TB}) + assert prompt.index("ALERT:") < prompt.index("") < prompt.index("RECENT COMMITS:") + assert prompt.count("Touches files in the stack trace:") == 1 + assert "Subject: config: raise pool size\nTouches files in the stack trace: app/db.py\nDiff:" in prompt + + +def test_overlap_matches_whole_path_segments(): + tb = 'File "/srv/orders/app/db.py", line 1\nFile "C:\\svc\\app\\cart.py", line 2' + files = ["app/db.py", "db.py", "pp/db.py", "app/cart.py", "app/orders.py"] + assert llm_analyzer.files_in_traceback(files, tb) == ["app/db.py", "db.py", "app/cart.py"] + assert llm_analyzer.files_in_traceback(files, None) == [] + + +def test_long_traceback_keeps_newest_frames(): + tb = "x" * 10_000 + "\nTypeError: newest" + block = llm_analyzer._traceback_block(tb) + assert "TypeError: newest" in block and len(block) < llm_analyzer._TRACEBACK_MAX_CHARS + 200 From 0e2af444b058dc9d2babacc2223960806f6b59ca Mon Sep 17 00:00:00 2001 From: Lushenwar Date: Sun, 11 Oct 2026 14:46:30 -0400 Subject: [PATCH 2/5] feat(sandbox): forward real stack traces in alerts The toy app logged repr(exc) as its "traceback"; it now logs and returns the real formatted trace in its 500 body. The load generator forwards the last error's real signature and trace instead of a fixed "Error rate spike detected", and chaos_cli probes the app once after injecting a bug so its alert carries the trace too (skipped when the app isn't running). Co-Authored-By: Claude Opus 5.5 --- core/tests/test_traceback_ranking.py | 14 ++++++++++++++ sandbox/app/main.py | 9 +++++++-- sandbox/chaos_cli.py | 19 +++++++++++++++++++ sandbox/load_generator.py | 10 +++++++++- 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/core/tests/test_traceback_ranking.py b/core/tests/test_traceback_ranking.py index aef2e51..0c468b5 100644 --- a/core/tests/test_traceback_ranking.py +++ b/core/tests/test_traceback_ranking.py @@ -52,3 +52,17 @@ def test_long_traceback_keeps_newest_frames(): tb = "x" * 10_000 + "\nTypeError: newest" block = llm_analyzer._traceback_block(tb) assert "TypeError: newest" in block and len(block) < llm_analyzer._TRACEBACK_MAX_CHARS + 200 + + +def test_sandbox_app_traceback_matches_its_repo_path(): + from fastapi.testclient import TestClient + + from sandbox.app import main as sandbox_app + + with patch.object(sandbox_app, "_bug", return_value="db_failure"): + r = TestClient(sandbox_app.app, raise_server_exceptions=False).get("/api/data") + assert r.status_code == 500 and r.json()["error"].startswith("RuntimeError: OperationalError") + tb = r.json()["traceback"] + assert llm_analyzer.files_in_traceback(["sandbox/app/main.py", "core/db.py"], tb) == [ + "sandbox/app/main.py" + ] diff --git a/sandbox/app/main.py b/sandbox/app/main.py index 25345c6..a01e67e 100644 --- a/sandbox/app/main.py +++ b/sandbox/app/main.py @@ -1,5 +1,6 @@ import json import os +import traceback from datetime import datetime, timezone from fastapi import FastAPI, Request from fastapi.responses import JSONResponse @@ -44,7 +45,11 @@ async def catch_all(request: Request, exc: Exception): "level": "ERROR", "message": str(exc), "endpoint": request.url.path, - "traceback": repr(exc), + "traceback": "".join(traceback.format_exception(exc)), } print(json.dumps(entry)) - return JSONResponse(status_code=500, content={"error": str(exc)}) + # ponytail: debug-mode error body (like a Sentry event) so the load generator can forward the trace + return JSONResponse( + status_code=500, + content={"error": f"{type(exc).__name__}: {exc}", "traceback": entry["traceback"]}, + ) diff --git a/sandbox/chaos_cli.py b/sandbox/chaos_cli.py index de8b2a0..2ae4f09 100644 --- a/sandbox/chaos_cli.py +++ b/sandbox/chaos_cli.py @@ -16,6 +16,9 @@ REPO_ROOT = Path(__file__).parent.parent CONFIG = REPO_ROOT / "sandbox" / "app" / "config.json" CORE_ALERT = "http://localhost:8000/alert" +APP = "http://localhost:8001" +# endpoint that raises for each bug; memory_leak degrades without raising, so it has none +_PROBES = {"db_failure": ("GET", "/api/data"), "null_pointer": ("POST", "/api/process")} _ERROR_SIGS = { "db_failure": "OperationalError: connection to server at '127.0.0.1' failed: Connection refused", @@ -33,6 +36,18 @@ def _write_config_and_commit(bug: str | None): subprocess.run(["git", "commit", "-m", msg], cwd=REPO_ROOT, check=True) +def _probe_traceback(bug_type: str) -> str | None: + """Hit the sandbox app once so the alert carries the real stack trace, as Sentry would.""" + if bug_type not in _PROBES: + return None + method, path = _PROBES[bug_type] + try: + r = httpx.request(method, APP + path, json={} if method == "POST" else None, timeout=3.0) + return r.json().get("traceback") if r.status_code >= 500 else None + except (httpx.HTTPError, ValueError): + return None + + @click.group() def cli(): pass @@ -53,6 +68,10 @@ def trigger_bug(bug_type: str): "error_signature": _ERROR_SIGS[bug_type], "bug_type": bug_type, } + tb = _probe_traceback(bug_type) + if tb: + alert["traceback"] = tb + click.echo(f"[chaos] traceback: {'captured from sandbox app' if tb else 'none (app not running?)'}") try: r = httpx.post(CORE_ALERT, json=alert, timeout=5.0) data = r.json() diff --git a/sandbox/load_generator.py b/sandbox/load_generator.py index 2f9dff9..a7e7041 100644 --- a/sandbox/load_generator.py +++ b/sandbox/load_generator.py @@ -18,6 +18,7 @@ async def run(tps: int, duration: int): counts = {"ok": 0, "err": 0} + last_error = {} # most recent 500 body: real signature + traceback for the alert alerted = False deadline = time.monotonic() + duration interval = 1.0 / tps @@ -28,6 +29,11 @@ async def run(tps: int, duration: int): try: r = await client.get(f"{APP}/api/data") counts["err" if r.status_code >= 500 else "ok"] += 1 + if r.status_code >= 500: + try: + last_error = r.json() + except ValueError: + pass except Exception: counts["err"] += 1 @@ -41,9 +47,11 @@ async def run(tps: int, duration: int): "source": "load_generator", "alert_name": "HTTP_500_Internal_Server_Error", "timestamp": datetime.now(timezone.utc).isoformat(), - "error_signature": "Error rate spike detected", + "error_signature": last_error.get("error", "Error rate spike detected"), "error_rate_pct": round(err_pct, 1), } + if last_error.get("traceback"): + alert["traceback"] = last_error["traceback"] try: await client.post(CORE, json=alert, timeout=5.0) print("\n[load] alert fired -> sentinel core") From bb5757d577104e12810a041013f7c5a6b40b9262 Mon Sep 17 00:00:00 2001 From: Lushenwar Date: Sun, 11 Oct 2026 14:49:33 -0400 Subject: [PATCH 3/5] feat(dashboard): show the alert's stack trace on the incident page Collapsible under the error signature; absent when the alert carried none. Co-Authored-By: Claude Opus 5.5 --- dashboard/src/app/incidents/[id]/page.tsx | 6 ++++++ dashboard/src/lib/api.ts | 1 + 2 files changed, 7 insertions(+) diff --git a/dashboard/src/app/incidents/[id]/page.tsx b/dashboard/src/app/incidents/[id]/page.tsx index 9513367..c45407d 100644 --- a/dashboard/src/app/incidents/[id]/page.tsx +++ b/dashboard/src/app/incidents/[id]/page.tsx @@ -48,6 +48,12 @@ export default function IncidentDetail() {

Trigger

{trigger.alert_name} — {trigger.source} — {new Date(trigger.timestamp).toLocaleString()}

{trigger.error_signature}
+ {trigger.traceback && ( +
+ Stack trace +
{trigger.traceback}
+
+ )} {diagnostics && ( diff --git a/dashboard/src/lib/api.ts b/dashboard/src/lib/api.ts index e0caa53..2004182 100644 --- a/dashboard/src/lib/api.ts +++ b/dashboard/src/lib/api.ts @@ -26,6 +26,7 @@ export type Trigger = { alert_name: string; timestamp: string; error_signature: string; + traceback?: string; }; // shape returned by core's db rows (see core/db.py) From 6a345e231aa34c9d4899c669f875aad08bf7355c Mon Sep 17 00:00:00 2001 From: Lushenwar Date: Sun, 11 Oct 2026 15:32:19 -0400 Subject: [PATCH 4/5] feat(eval): traceback condition with real-line stack traces for 20 cases Cases gain a "traceback" spec: app frames are anchored to source text and rendered with the line numbers of the post-commit fixture repo; library frames are literal. The 4 metric alerts (timeouts, OOM, idle consumer) have none. Where the raising call is ambiguous, frames take the path that does not run through the culprit's lines, so the culprit's file is in 13/20 traces. run_eval --condition traceback sends the trace; baseline and rag strip it, so their prompts are unchanged. Records note has_traceback and culprit_in_traceback, and the report splits a comparison by those groups. Co-Authored-By: Claude Opus 5.5 --- eval/cases/auth_issuer_mesh_hosts.json | 38 +++++++++++++ eval/cases/cache_contains_recursion.json | 54 +++++++++++++++++++ eval/cases/cart_price_field_v2.json | 24 +++++++++ eval/cases/cart_sku_lowercased.json | 30 +++++++++++ eval/cases/config_reformat_hidden_string.json | 24 +++++++++ eval/cases/db_fetch_one_leak.json | 26 +++++++++ eval/cases/export_tempfile_never_closed.json | 15 ++++++ eval/cases/guest_email_normalise.json | 14 +++++ eval/cases/httpx_028_drops_proxies.json | 14 +++++ .../inventory_cache_key_per_request.json | 26 +++++++++ eval/cases/inventory_v3_payload.json | 19 +++++++ eval/cases/jwt_leeway_milliseconds.json | 39 ++++++++++++++ eval/cases/money_returns_decimal.json | 34 ++++++++++++ eval/cases/order_sql_missing_param.json | 19 +++++++ eval/cases/page_size_zero_division.json | 14 +++++ .../payments_body_without_content_type.json | 20 +++++++ eval/cases/payments_retry_loop.json | 21 ++++++++ eval/cases/redis_legacy_lrem_args.json | 33 ++++++++++++ eval/cases/settings_env_quotes.json | 48 +++++++++++++++++ eval/cases/since_filter_naive_cutoff.json | 9 ++++ eval/config.py | 4 ++ eval/fixture.py | 37 +++++++++++++ eval/report.py | 21 ++++++++ eval/run_eval.py | 12 ++++- eval/tests/test_eval.py | 22 ++++++++ 25 files changed, 616 insertions(+), 1 deletion(-) diff --git a/eval/cases/auth_issuer_mesh_hosts.json b/eval/cases/auth_issuer_mesh_hosts.json index 691d55d..0afde5d 100644 --- a/eval/cases/auth_issuer_mesh_hosts.json +++ b/eval/cases/auth_issuer_mesh_hosts.json @@ -85,5 +85,43 @@ "culprit_index": 4, "expected_runbook_id": "auth_failures", "note": "Tokens still carry iss=https://auth.internal; the mesh rename changed the expected issuer, not just a transport URL. Leeway affects exp/iat checks and the algorithm list only widens what is accepted." + }, + "traceback": { + "frames": [ + [ + "app/auth.py", + "current_user_id", + "return verify_token(" + ], + [ + "app/auth.py", + "verify_token", + "return jwt.decode(" + ], + [ + "/usr/local/lib/python3.12/site-packages/jwt/api_jwt.py", + 210, + "decode", + "decoded = self.decode_complete(" + ], + [ + "/usr/local/lib/python3.12/site-packages/jwt/api_jwt.py", + 151, + "decode_complete", + "self._validate_claims(" + ], + [ + "/usr/local/lib/python3.12/site-packages/jwt/api_jwt.py", + 244, + "_validate_claims", + "self._validate_iss(payload, issuer)" + ], + [ + "/usr/local/lib/python3.12/site-packages/jwt/api_jwt.py", + 330, + "_validate_iss", + "raise InvalidIssuerError(\"Invalid issuer\")" + ] + ] } } diff --git a/eval/cases/cache_contains_recursion.json b/eval/cases/cache_contains_recursion.json index ab6912c..4dc731e 100644 --- a/eval/cases/cache_contains_recursion.json +++ b/eval/cases/cache_contains_recursion.json @@ -85,5 +85,59 @@ "culprit_index": 2, "expected_runbook_id": null, "note": "__contains__ now calls get() and get() starts with 'key not in self', so each calls the other. The earlier __contains__ read _data directly and was fine." + }, + "traceback": { + "frames": [ + [ + "app/handlers/orders.py", + "create_order", + "stock = inventory.get_stock_many(" + ], + [ + "app/clients/inventory.py", + "get_stock_many", + "get_stock(sku" + ], + [ + "app/clients/inventory.py", + "get_stock", + "cached = _stock_cache.get(" + ], + [ + "app/cache.py", + "get", + "if key not in self" + ], + [ + "app/cache.py", + "__contains__", + "return self.get(key) is not None" + ], + [ + "app/cache.py", + "get", + "if key not in self" + ], + [ + "app/cache.py", + "__contains__", + "return self.get(key) is not None" + ], + [ + "app/cache.py", + "get", + "if key not in self" + ], + [ + "app/cache.py", + "__contains__", + "return self.get(key) is not None" + ], + [ + "app/cache.py", + "get", + "if key not in self" + ] + ] } } diff --git a/eval/cases/cart_price_field_v2.json b/eval/cases/cart_price_field_v2.json index 2de684d..1e61a5c 100644 --- a/eval/cases/cart_price_field_v2.json +++ b/eval/cases/cart_price_field_v2.json @@ -81,5 +81,29 @@ "culprit_index": 2, "expected_runbook_id": "null_field_handling", "note": "Carts saved before the v2 payload only carry unit_price_cents, so .get('price_cents') yields None and qty * None raises inside _line_total. The helper extraction is behaviour-preserving." + }, + "traceback": { + "frames": [ + [ + "app/cart.py", + "checkout", + "return create_order(" + ], + [ + "app/handlers/orders.py", + "create_order", + "total = sum(_line_total(line)" + ], + [ + "app/handlers/orders.py", + "", + "total = sum(_line_total(line)" + ], + [ + "app/handlers/orders.py", + "_line_total", + "return line[\"qty\"] * line[\"unit_price_cents\"]" + ] + ] } } diff --git a/eval/cases/cart_sku_lowercased.json b/eval/cases/cart_sku_lowercased.json index 7117ee9..3a08442 100644 --- a/eval/cases/cart_sku_lowercased.json +++ b/eval/cases/cart_sku_lowercased.json @@ -68,5 +68,35 @@ "culprit_index": 2, "expected_runbook_id": null, "note": "SKUs are upper-case (SKU-10293); the cart now lower-cases them before the stock lookup. quote() leaves 'SKU-10293' unchanged." + }, + "traceback": { + "frames": [ + [ + "app/cart.py", + "checkout", + "return create_order(" + ], + [ + "app/handlers/orders.py", + "create_order", + "stock = inventory.get_stock_many(" + ], + [ + "app/clients/inventory.py", + "get_stock_many", + "get_stock(sku" + ], + [ + "app/clients/inventory.py", + "get_stock", + "resp.raise_for_status()" + ], + [ + "/usr/local/lib/python3.12/site-packages/httpx/_models.py", + 763, + "raise_for_status", + "raise HTTPStatusError(message, request=request, response=self)" + ] + ] } } diff --git a/eval/cases/config_reformat_hidden_string.json b/eval/cases/config_reformat_hidden_string.json index 2e67eea..ce0a03e 100644 --- a/eval/cases/config_reformat_hidden_string.json +++ b/eval/cases/config_reformat_hidden_string.json @@ -72,5 +72,29 @@ "culprit_index": 1, "expected_runbook_id": "config_parse_errors", "note": "The one-key-per-line reformat quietly turned cache.max_entries into the string \"5000\"; TTLCache.set compares len(...) >= max_entries. The TTL change keeps an int." + }, + "traceback": { + "frames": [ + [ + "app/handlers/orders.py", + "create_order", + "stock = inventory.get_stock_many(" + ], + [ + "app/clients/inventory.py", + "get_stock_many", + "get_stock(sku" + ], + [ + "app/clients/inventory.py", + "get_stock", + "_stock_cache.set(" + ], + [ + "app/cache.py", + "set", + "if len(self._data) >= self.max_entries" + ] + ] } } diff --git a/eval/cases/db_fetch_one_leak.json b/eval/cases/db_fetch_one_leak.json index 60e2cf9..5b30a41 100644 --- a/eval/cases/db_fetch_one_leak.json +++ b/eval/cases/db_fetch_one_leak.json @@ -77,5 +77,31 @@ "culprit_index": 1, "expected_runbook_id": "db_failures", "note": "fetch_one returns before putconn on every miss (and on any exception), leaking one connection per lookup that finds nothing; a pool of 6 only lowers the ceiling, it cannot be exhausted without a leak." + }, + "traceback": { + "frames": [ + [ + "app/handlers/orders.py", + "list_orders", + "rows = db.fetch_all(_LIST_SQL" + ], + [ + "app/db.py", + "fetch_all", + "conn = pool.getconn()" + ], + [ + "/usr/local/lib/python3.12/site-packages/psycopg2/pool.py", + 169, + "getconn", + "return self._getconn(key)" + ], + [ + "/usr/local/lib/python3.12/site-packages/psycopg2/pool.py", + 93, + "_getconn", + "raise PoolError(\"connection pool exhausted\")" + ] + ] } } diff --git a/eval/cases/export_tempfile_never_closed.json b/eval/cases/export_tempfile_never_closed.json index 6668fbd..77ba39d 100644 --- a/eval/cases/export_tempfile_never_closed.json +++ b/eval/cases/export_tempfile_never_closed.json @@ -86,5 +86,20 @@ "culprit_index": 1, "expected_runbook_id": "file_handle_exhaustion", "note": "The temp-file rewrite never closes the NamedTemporaryFile, leaking a descriptor per export. The audit log opens a file per order but closes it via the with block." + }, + "traceback": { + "frames": [ + [ + "app/handlers/orders.py", + "create_order", + "audit.record(\"order.created\"" + ], + [ + "app/audit.py", + "record", + "with open(AUDIT_LOG" + ] + ], + "error": "OSError: [Errno 24] Too many open files: '/var/log/orders/audit.jsonl'" } } diff --git a/eval/cases/guest_email_normalise.json b/eval/cases/guest_email_normalise.json index 7c32011..416fff6 100644 --- a/eval/cases/guest_email_normalise.json +++ b/eval/cases/guest_email_normalise.json @@ -81,5 +81,19 @@ "culprit_index": 4, "expected_runbook_id": "null_field_handling", "note": "Guest orders have customer_email NULL; .strip() on None raises. The email_domain commit guards None." + }, + "traceback": { + "frames": [ + [ + "app/handlers/orders.py", + "get_order", + "serializers.order_to_dict(row)" + ], + [ + "app/serializers.py", + "order_to_dict", + "row[\"customer_email\"].strip()" + ] + ] } } diff --git a/eval/cases/httpx_028_drops_proxies.json b/eval/cases/httpx_028_drops_proxies.json index 1f9356d..5c29fce 100644 --- a/eval/cases/httpx_028_drops_proxies.json +++ b/eval/cases/httpx_028_drops_proxies.json @@ -82,5 +82,19 @@ "culprit_index": 3, "expected_runbook_id": "dependency_regressions", "note": "httpx 0.28 removed the deprecated proxies= argument (replaced by proxy=). The proxy commit was valid against the pinned 0.27.2; the version bump is what broke it." + }, + "traceback": { + "frames": [ + [ + "app/handlers/orders.py", + "", + "from app.clients import inventory, payments" + ], + [ + "app/clients/payments.py", + "", + "_http = httpx.Client(" + ] + ] } } diff --git a/eval/cases/inventory_cache_key_per_request.json b/eval/cases/inventory_cache_key_per_request.json index 7f689e4..1eaa2f9 100644 --- a/eval/cases/inventory_cache_key_per_request.json +++ b/eval/cases/inventory_cache_key_per_request.json @@ -101,5 +101,31 @@ "culprit_index": 4, "expected_runbook_id": "rate_limiting", "note": "The cache key now includes a fresh uuid per batch, so the stock cache never hits; a 60s TTL alone can raise lookups at most ~5x over 300s, not 31x." + }, + "traceback": { + "frames": [ + [ + "app/handlers/orders.py", + "create_order", + "stock = inventory.get_stock_many(" + ], + [ + "app/clients/inventory.py", + "get_stock_many", + "get_stock(sku" + ], + [ + "app/clients/inventory.py", + "get_stock", + "resp.raise_for_status()" + ], + [ + "/usr/local/lib/python3.12/site-packages/httpx/_models.py", + 763, + "raise_for_status", + "raise HTTPStatusError(message, request=request, response=self)" + ] + ], + "error": "httpx.HTTPStatusError: Client error '429 Too Many Requests' for url 'https://inventory.internal/v2/stock/SKU-30117'" } } diff --git a/eval/cases/inventory_v3_payload.json b/eval/cases/inventory_v3_payload.json index b893e40..b996b90 100644 --- a/eval/cases/inventory_v3_payload.json +++ b/eval/cases/inventory_v3_payload.json @@ -89,5 +89,24 @@ "culprit_index": 2, "expected_runbook_id": "null_field_handling", "note": "The response parser still reads ['available'] while the client now calls a different API version; the only change to what the response contains. The other inventory and order commits never touch parsing." + }, + "traceback": { + "frames": [ + [ + "app/handlers/orders.py", + "create_order", + "stock = inventory.get_stock_many(" + ], + [ + "app/clients/inventory.py", + "get_stock_many", + "get_stock(sku" + ], + [ + "app/clients/inventory.py", + "get_stock", + "resp.json()[\"available\"]" + ] + ] } } diff --git a/eval/cases/jwt_leeway_milliseconds.json b/eval/cases/jwt_leeway_milliseconds.json index cee8aa4..c455a5b 100644 --- a/eval/cases/jwt_leeway_milliseconds.json +++ b/eval/cases/jwt_leeway_milliseconds.json @@ -85,5 +85,44 @@ "culprit_index": 3, "expected_runbook_id": "auth_failures", "note": "leeway_s is seconds but is now wrapped as milliseconds (45ms), so ~4s of clock skew fails the iat check. Widening leeway_s to 45 would tolerate it; the PyJWT minor bump does not change leeway handling." + }, + "traceback": { + "frames": [ + [ + "app/auth.py", + "current_user_id", + "return verify_token(" + ], + [ + "app/auth.py", + "verify_token", + "return jwt.decode(" + ], + [ + "/usr/local/lib/python3.12/site-packages/jwt/api_jwt.py", + 210, + "decode", + "decoded = self.decode_complete(" + ], + [ + "/usr/local/lib/python3.12/site-packages/jwt/api_jwt.py", + 151, + "decode_complete", + "self._validate_claims(" + ], + [ + "/usr/local/lib/python3.12/site-packages/jwt/api_jwt.py", + 244, + "_validate_claims", + "self._validate_iat(payload, now, leeway)" + ], + [ + "/usr/local/lib/python3.12/site-packages/jwt/api_jwt.py", + 330, + "_validate_iat", + "raise ImmatureSignatureError(\"The token is not yet valid (iat)\")" + ] + ], + "error": "jwt.exceptions.ImmatureSignatureError: The token is not yet valid (iat)" } } diff --git a/eval/cases/money_returns_decimal.json b/eval/cases/money_returns_decimal.json index cb2f9b6..51f5491 100644 --- a/eval/cases/money_returns_decimal.json +++ b/eval/cases/money_returns_decimal.json @@ -81,5 +81,39 @@ "culprit_index": 4, "expected_runbook_id": null, "note": "money() now returns a Decimal that lands in every order response body. The thousands-separator change still returned a str." + }, + "traceback": { + "frames": [ + [ + "/usr/local/lib/python3.12/site-packages/starlette/responses.py", + 181, + "render", + "return json.dumps(" + ], + [ + "/usr/local/lib/python3.12/json/__init__.py", + 238, + "dumps", + "**kw).encode(obj)" + ], + [ + "/usr/local/lib/python3.12/json/encoder.py", + 200, + "encode", + "chunks = self.iterencode(o, _one_shot=True)" + ], + [ + "/usr/local/lib/python3.12/json/encoder.py", + 258, + "iterencode", + "return _iterencode(o, 0)" + ], + [ + "/usr/local/lib/python3.12/json/encoder.py", + 180, + "default", + "raise TypeError(f'Object of type {o.__class__.__name__} '" + ] + ] } } diff --git a/eval/cases/order_sql_missing_param.json b/eval/cases/order_sql_missing_param.json index c6ebc06..65b7e25 100644 --- a/eval/cases/order_sql_missing_param.json +++ b/eval/cases/order_sql_missing_param.json @@ -81,5 +81,24 @@ "culprit_index": 3, "expected_runbook_id": null, "note": "_ORDER_SQL gained a second placeholder but get_order still passes (order_id,). The list query added a placeholder and its parameter together." + }, + "traceback": { + "frames": [ + [ + "app/handlers/orders.py", + "get_order", + "row = db.fetch_one(_ORDER_SQL" + ], + [ + "app/db.py", + "fetch_one", + "rows = fetch_all(sql, params)" + ], + [ + "app/db.py", + "fetch_all", + "cur.execute(sql, params)" + ] + ] } } diff --git a/eval/cases/page_size_zero_division.json b/eval/cases/page_size_zero_division.json index 2718c88..1bf7fff 100644 --- a/eval/cases/page_size_zero_division.json +++ b/eval/cases/page_size_zero_division.json @@ -77,5 +77,19 @@ "culprit_index": 0, "expected_runbook_id": "pagination_errors", "note": "Dropping the max(1, ...) clamp lets page_size=0 through; the later total_pages line is where it divides, but that line is safe for any page_size >= 1." + }, + "traceback": { + "frames": [ + [ + "app/handlers/orders.py", + "list_orders", + "body = paginate(" + ], + [ + "app/pagination.py", + "paginate", + "total_pages = -(-len(items) // page_size)" + ] + ] } } diff --git a/eval/cases/payments_body_without_content_type.json b/eval/cases/payments_body_without_content_type.json index 7b1d472..c2953cc 100644 --- a/eval/cases/payments_body_without_content_type.json +++ b/eval/cases/payments_body_without_content_type.json @@ -89,5 +89,25 @@ "culprit_index": 2, "expected_runbook_id": null, "note": "httpx sets Content-Type: application/json only for json=; with content= the body is sent untyped. The idempotency header commit leaves the json= body intact." + }, + "traceback": { + "frames": [ + [ + "app/handlers/orders.py", + "create_order", + "payments.charge(order_id, total, card_token)" + ], + [ + "app/clients/payments.py", + "charge", + "resp.raise_for_status()" + ], + [ + "/usr/local/lib/python3.12/site-packages/httpx/_models.py", + 763, + "raise_for_status", + "raise HTTPStatusError(message, request=request, response=self)" + ] + ] } } diff --git a/eval/cases/payments_retry_loop.json b/eval/cases/payments_retry_loop.json index eb66ae6..499f028 100644 --- a/eval/cases/payments_retry_loop.json +++ b/eval/cases/payments_retry_loop.json @@ -85,5 +85,26 @@ "culprit_index": 3, "expected_runbook_id": "rate_limiting", "note": "payments.retries was not read by any code until the retry loop; the loop retries 429s immediately with no backoff, giving 1+4=5 attempts per order. The config bump was inert on its own." + }, + "traceback": { + "frames": [ + [ + "app/handlers/orders.py", + "create_order", + "payments.charge(order_id, total, card_token)" + ], + [ + "app/clients/payments.py", + "charge", + "resp.raise_for_status()" + ], + [ + "/usr/local/lib/python3.12/site-packages/httpx/_models.py", + 763, + "raise_for_status", + "raise HTTPStatusError(message, request=request, response=self)" + ] + ], + "error": "httpx.HTTPStatusError: Client error '429 Too Many Requests' for url 'https://payments.internal/v1/charges'" } } diff --git a/eval/cases/redis_legacy_lrem_args.json b/eval/cases/redis_legacy_lrem_args.json index ba5349d..310acb4 100644 --- a/eval/cases/redis_legacy_lrem_args.json +++ b/eval/cases/redis_legacy_lrem_args.json @@ -77,5 +77,38 @@ "culprit_index": 0, "expected_runbook_id": "dependency_regressions", "note": "redis-py 2.x uses lrem(name, value, num); 3.0+ uses lrem(name, count, value). Under 2.10.6 the job payload is sent as the count. A count of -1 is valid in the current argument order." + }, + "traceback": { + "frames": [ + [ + "app/worker.py", + "run_forever", + "_r.lrem(PROCESSING" + ], + [ + "/usr/local/lib/python3.12/site-packages/redis/client.py", + 1418, + "lrem", + "return self.execute_command('LREM', name, num, value)" + ], + [ + "/usr/local/lib/python3.12/site-packages/redis/client.py", + 673, + "execute_command", + "return self.parse_response(connection, command_name, **options)" + ], + [ + "/usr/local/lib/python3.12/site-packages/redis/client.py", + 680, + "parse_response", + "response = connection.read_response()" + ], + [ + "/usr/local/lib/python3.12/site-packages/redis/connection.py", + 629, + "read_response", + "raise response" + ] + ] } } diff --git a/eval/cases/settings_env_quotes.json b/eval/cases/settings_env_quotes.json index 20050b1..20af029 100644 --- a/eval/cases/settings_env_quotes.json +++ b/eval/cases/settings_env_quotes.json @@ -77,5 +77,53 @@ "culprit_index": 2, "expected_runbook_id": "config_parse_errors", "note": "Deployments set overrides JSON-encoded (README), e.g. ORDERS_DB_HOST='\"10.0.4.12\"'; without json.loads the quotes stay in the host name. Not a database or network problem." + }, + "traceback": { + "frames": [ + [ + "app/handlers/orders.py", + "get_order", + "row = db.fetch_one(_ORDER_SQL" + ], + [ + "app/db.py", + "fetch_one", + "rows = fetch_all(" + ], + [ + "app/db.py", + "fetch_all", + "pool = get_pool()" + ], + [ + "app/db.py", + "get_pool", + "_pool = psycopg2.pool.ThreadedConnectionPool(" + ], + [ + "/usr/local/lib/python3.12/site-packages/psycopg2/pool.py", + 161, + "__init__", + "AbstractConnectionPool.__init__(" + ], + [ + "/usr/local/lib/python3.12/site-packages/psycopg2/pool.py", + 59, + "__init__", + "self._connect()" + ], + [ + "/usr/local/lib/python3.12/site-packages/psycopg2/pool.py", + 63, + "_connect", + "conn = psycopg2.connect(*self._args, **self._kwargs)" + ], + [ + "/usr/local/lib/python3.12/site-packages/psycopg2/__init__.py", + 122, + "connect", + "conn = _connect(dsn, connection_factory=connection_factory, **kwasync)" + ] + ] } } diff --git a/eval/cases/since_filter_naive_cutoff.json b/eval/cases/since_filter_naive_cutoff.json index 94db78c..fc84aa7 100644 --- a/eval/cases/since_filter_naive_cutoff.json +++ b/eval/cases/since_filter_naive_cutoff.json @@ -85,5 +85,14 @@ "culprit_index": 2, "expected_runbook_id": null, "note": "The new filter strips tzinfo from the cutoff and compares it with timestamptz rows. The export change only formats a file name and never compares datetimes." + }, + "traceback": { + "frames": [ + [ + "app/handlers/orders.py", + "list_orders", + "r[\"created_at\"] >= cutoff" + ] + ] } } diff --git a/eval/config.py b/eval/config.py index 9eb896f..5491220 100644 --- a/eval/config.py +++ b/eval/config.py @@ -2,6 +2,10 @@ RUNBOOK_CORPUS_VERSION = "v1" # sandbox/runbooks/*.md, 10 docs, frozen at Phase 9B # v2 replaced v1 after the v1 baseline hit 100% top-1 (ceiling); v1 lives in git history at d6f6bd1. EVAL_SET_VERSION = "v2" # eval/cases/*.json + eval/bases/, 24 cases, frozen at Phase 9C (rev) +# Case "traceback" specs, read only by the traceback condition (baseline and rag prompts never see them). +# Frames follow the real runtime path. Where the raising call is ambiguous (a leak surfaces at any getconn +# or open), the path NOT through the culprit's lines was chosen, so the culprit file is in 13/20 traces. +TRACEBACK_SET_VERSION = "v1" # 20 of 24 cases; the 4 metric alerts (timeouts, OOM, idle queue) have none ALERT_TS = "2026-09-01T12:00:00+00:00" # fixed so fixture commit dates are reproducible DEFAULT_TRIALS = 3 diff --git a/eval/fixture.py b/eval/fixture.py index 3a24db5..aa69190 100644 --- a/eval/fixture.py +++ b/eval/fixture.py @@ -2,6 +2,7 @@ import json import os +import re import shutil import stat import subprocess @@ -69,6 +70,40 @@ def _chmod_retry(func, p, _exc): shutil.rmtree(path, onerror=_chmod_retry) +APP_ROOT = "/srv/orders" # deploy path frames carry; repo paths are relative to it + + +def render_traceback(repo: Path, spec: dict, error: str) -> str: + """Python-style traceback whose app frames point at real lines of the post-commit repo. + + Frame forms: [repo_file, func, anchor] (line = first line containing anchor after `def func(`, or the + file's only line containing it for /), [abs_path, line, func, code] for library frames, + or [text] for a literal line such as "[Previous line repeated 996 more times]". + """ + out = ["Traceback (most recent call last):"] + for fr in spec["frames"]: + if len(fr) == 1: + out.append(f" {fr[0]}") + continue + if len(fr) == 4: + path, line, func, code = fr + else: + rel, func, anchor = fr + lines = (repo / rel).read_text(encoding="utf-8").splitlines() + start = next( + (i for i, x in enumerate(lines) if re.match(rf"\s*def {re.escape(func)}\(", x)), None + ) + hits = [i for i, x in enumerate(lines) if anchor in x and (start is None or i >= start)] + if not hits or (start is None and len(hits) != 1): + raise ValueError( + f"{rel}:{func}: anchor must match {'once' if start is None else ''}: {anchor!r}" + ) + path, line, code = f"{APP_ROOT}/{rel}", hits[0] + 1, lines[hits[0]].strip() + out += [f' File "{path}", line {line}, in {func}', f" {code}"] + out.append(spec.get("error", error)) + return "\n".join(out) + + @contextmanager def build(case: dict): """Yields (repo_path, alert, label). label gains culprit_hash for the scorer.""" @@ -101,6 +136,8 @@ def build(case: dict): "timestamp": ALERT_TS, "error_signature": case["alert"]["error_signature"], } + if case.get("traceback"): # only the traceback condition sends it; run_eval strips it otherwise + alert["traceback"] = render_traceback(repo, case["traceback"], case["alert"]["error_signature"]) label = {**case["label"], "culprit_hash": hashes[case["label"]["culprit_index"]]} yield str(repo), alert, label finally: diff --git a/eval/report.py b/eval/report.py index 37021ee..416ed5a 100644 --- a/eval/report.py +++ b/eval/report.py @@ -110,6 +110,19 @@ def paired(a: list[dict], b: list[dict]) -> list[tuple]: return [(cid, *h) for cid, h in sorted(hits.items())] +def traceback_groups(records: list[dict]) -> dict[str, str]: + """case_id -> traceback group, from whichever run recorded it (pre-traceback runs did not).""" + groups = {} + for r in records: + if r.get("has_traceback"): + groups[r["case_id"]] = ( + "culprit file in trace" if r["culprit_in_traceback"] else "trace misses culprit file" + ) + elif "has_traceback" in r and r["case_id"] not in groups: + groups[r["case_id"]] = "no trace" + return groups + + def _fmt_conf(x): return f"{x:.2f}" if x is not None else "n/a" @@ -165,6 +178,14 @@ def print_comparison(a: dict, b: dict): ties = len(rows) - wins - losses print(f" {lb} wins {wins}, losses {losses}, ties {ties} (n={len(rows)} cases)") + groups = traceback_groups(a["records"] + b["records"]) + if groups: + print("\n== By traceback content (does a trace that names only other files mislead?)") + for group in ("culprit file in trace", "trace misses culprit file", "no trace"): + for label, res in ((la, a), (lb, b)): + s = summarize([r for r in res["records"] if groups.get(r["case_id"]) == group]) + print(f" {group:<30} {label:<15} top-1 {_pct(s['top1'], s['n'])}, MRR {s['mrr']:.3f}") + print("\n== Null-runbook cases only (does irrelevant context hurt?)") for label, res in ((la, a), (lb, b)): s = summarize([r for r in res["records"] if not r["expected_runbook_id"]]) diff --git a/eval/run_eval.py b/eval/run_eval.py index 74161af..8a2c813 100644 --- a/eval/run_eval.py +++ b/eval/run_eval.py @@ -26,6 +26,8 @@ def run_trial(case: dict, condition: str) -> dict: with fixture.build(case) as (repo, alert, label): + if condition != "traceback": + alert.pop("traceback", None) # baseline and rag prompts stay byte-identical to Phase 9 diffs = git_client.get_recent_diffs(repo, alert["timestamp"]) rag = condition == "rag" runbooks = ( @@ -40,6 +42,13 @@ def run_trial(case: dict, condition: str) -> dict: "expected_runbook_id": label["expected_runbook_id"], "commits_in_window": len(diffs), "truncated_diffs": sum(len(d["diff"]) >= 3000 for d in diffs), + "has_traceback": "traceback" in alert, + "culprit_in_traceback": bool( + llm_analyzer.files_in_traceback( + next(d["files"] for d in diffs if d["commit_hash"] == label["culprit_hash"]), + alert.get("traceback"), + ) + ), "retrieved_runbooks": [ {"id": r["id"], "similarity_score": r["similarity_score"]} for r in runbooks ], @@ -108,7 +117,7 @@ def _git_sha() -> str: def main(argv=None): p = argparse.ArgumentParser() - p.add_argument("--condition", choices=["baseline", "rag"], required=True) + p.add_argument("--condition", choices=["baseline", "rag", "traceback"], required=True) p.add_argument("--trials", type=int, default=config.DEFAULT_TRIALS) p.add_argument("--cases", default="*.json", help="glob inside eval/cases") p.add_argument("--out", required=True) @@ -126,6 +135,7 @@ def main(argv=None): "condition": args.condition, "dry_run": args.dry_run, "eval_set_version": config.EVAL_SET_VERSION, + "traceback_set_version": config.TRACEBACK_SET_VERSION if args.condition == "traceback" else None, "runbook_corpus_version": config.RUNBOOK_CORPUS_VERSION, "runbooks_ingested": n_runbooks, "runbook_floor": llm_analyzer.RUNBOOK_FLOOR, diff --git a/eval/tests/test_eval.py b/eval/tests/test_eval.py index 3aa6f1b..461b6f3 100644 --- a/eval/tests/test_eval.py +++ b/eval/tests/test_eval.py @@ -74,6 +74,11 @@ def test_fixture_commits_land_in_window_in_order(case): assert [d["subject"] for d in diffs] == [c["message"] for c in reversed(case["commits"])] assert label["culprit_hash"] in {d["commit_hash"] for d in diffs} assert all(len(d["diff"]) < 3000 for d in diffs), "diff hit the truncation limit" + if case.get("traceback"): # every app frame resolved to a real line; no answer-announcing words + assert alert["traceback"].startswith("Traceback (most recent call last):") + assert not _BANNED.search(alert["traceback"]), alert["traceback"] + else: + assert "traceback" not in alert # ---------- scorer ---------- @@ -123,6 +128,7 @@ def test_paired(): "condition", [ "baseline", + "traceback", pytest.param("rag", marks=pytest.mark.skipif(not _RAG_READY, reason="runbooks param lands in 9E")), ], ) @@ -145,6 +151,7 @@ def capture(messages, **kw): for value in filter(None, leaks): assert value not in prompt, f"{case['id']}: {value!r} leaked into prompt" assert "culprit" not in prompt.lower() + assert ("" in prompt) == (condition == "traceback" and bool(case.get("traceback"))) assert len(prompts) == len(CASES) @@ -163,3 +170,18 @@ def dead_key(*_a, **_kw): run_eval.main(["--condition", "baseline", "--trials", "3", "--out", str(out)]) res = report.load(out) assert len(res["records"]) == run_eval.MAX_CONSECUTIVE_ERRORS and "aborted" in res + + +def test_traceback_groups_prefer_the_run_that_saw_the_trace(): + base = [{"case_id": "a", "has_traceback": False}, {"case_id": "b", "has_traceback": False}] + tb = [ + {"case_id": "a", "has_traceback": True, "culprit_in_traceback": False}, + {"case_id": "b", "has_traceback": False}, + {"case_id": "c", "has_traceback": True, "culprit_in_traceback": True}, + ] + old = [{"case_id": "d"}] # pre-traceback runs carry no field + assert report.traceback_groups(base + tb + old) == { + "a": "trace misses culprit file", + "b": "no trace", + "c": "culprit file in trace", + } From acae254e849b7802176324a84e540d1aebf79cc3 Mon Sep 17 00:00:00 2001 From: Lushenwar Date: Sun, 11 Oct 2026 15:50:19 -0400 Subject: [PATCH 5/5] docs(metrics): stack traces in ranking, 95.8% top-1 on eval set v2 72 live trials (24 cases x 3, claude-sonnet-5): 95.8% top-1 against the two existing baselines' 90.3% and 91.7%. The gain is one case, httpx_028_drops_proxies, which went from 0/9 to 3/3. Traces naming only decoy files did not pull rankings towards the decoys. Adds ADR-8 and a README line. Also records that no earlier trial hit diff truncation. Co-Authored-By: Claude Opus 5.5 --- ADR.md | 20 + METRICS.md | 57 + README.md | 1 + .../traceback_v2_sonnet5_2026-10-11.json | 3881 +++++++++++++++++ 4 files changed, 3959 insertions(+) create mode 100644 eval/results/traceback_v2_sonnet5_2026-10-11.json diff --git a/ADR.md b/ADR.md index 055a1ce..8349f71 100644 --- a/ADR.md +++ b/ADR.md @@ -145,3 +145,23 @@ depends on two things that could change: - **Baseline headroom.** At 90% top-1 there is little left to win. A larger, harder or real incident corpus — especially real runbooks written by the team that wrote the code — would be the test that could change this decision. + +## ADR-8: Stack traces in commit ranking — on whenever the alert has one + +**Context.** The alert was one line of error text; Sentinel never used the +stack trace a real error tracker attaches. A trace says where an error +surfaced, which is often not where it was introduced, so it could just as +easily mislead the ranker as help it. + +**Decision.** An alert may carry an optional `traceback`. Ranking gets the +newest 4000 characters of it, and each commit that touches a file named in a +frame is marked (frames carry deploy paths, so files match on a path-segment +suffix). The prompt says frames show where the error surfaced, not +necessarily its cause. Without a trace the prompt is byte-identical to the +baseline. No flag: having a trace is what switches it on. + +**Measured result** (METRICS.md, 2026-10-11): 95.8% top-1 against baselines +of 90.3% and 91.7%. The gain comes from one case that went from 0/9 to 3/3; +cases whose trace named only decoy files improved rather than regressed. +These traces were written to go through decoys wherever the raising call +was ambiguous, so they are harder than typical traces. diff --git a/METRICS.md b/METRICS.md index 5e85b33..d4e1eee 100644 --- a/METRICS.md +++ b/METRICS.md @@ -227,3 +227,60 @@ false alarms** (in-sample it was 9/13 and 9/131), so it holds. On Haiku 4.5 it flags 7/14 wrong but 22/58 right picks, and Haiku's raw confidence is identical for right and wrong picks (0.93 / 0.93): the rule is calibrated for Sonnet 5 and should be re-checked if the model changes. + +## Stack traces in commit ranking (2026-10-11) + +**Question:** does sending the alert's stack trace to the ranker, and marking +commits that touch a file named in a frame, improve top-1? + +**Answer: yes, by one robust case flip; everything else ties.** Traces are +used whenever an alert carries one (no flag). + +- **Inputs:** eval set v2 unchanged; 20/24 cases gained a trace + (`TRACEBACK_SET_VERSION = "v1"`), rendered against the post-commit fixture + repo so app frames carry real line numbers. The 4 metric alerts (timeouts, + OOM kill, idle queue) carry none, and their prompts are identical to + baseline. +- **Traces were built against the treatment.** Where the raising call is + ambiguous (a leak surfaces at whichever `getconn` or `open` runs next), the + frames follow a path *not* through the culprit's lines. The culprit's file + appears in 13/20 traces; the other 7 name only decoy files or no app files. +- **Control:** the two existing Sonnet 5 baselines. The baseline prompt is + byte-identical to the one they used (golden-prompt test), so no new + baseline run was needed. Same model, `claude-sonnet-5`, 3 trials per case. + +Reproduce: `python -m eval.report eval/results/baseline_v2_sonnet5_rerun_2026-10-07.json eval/results/traceback_v2_sonnet5_2026-10-11.json` + +| Metric | Baseline 10-04 | Baseline 10-07 | Traceback 10-11 | +|---|---|---|---| +| Top-1 | 90.3% (65/72) | 91.7% (66/72) | **95.8% (69/72)** | +| Top-3 | 100% | 100% | 100% | +| MRR | 0.947 | 0.958 | 0.975 | +| LLM failures | 0/72 | 0/72 | 0/72 | +| Ranking latency, median | 5.8s | 6.5s | 6.3s | +| Input tokens per call, mean | n/a | 3066 | 3352 (+9%) | + +**Where the gain comes from.** `httpx_028_drops_proxies` went from **0/3 in +every earlier Sonnet run (0/9 across baseline, RAG and re-run) to 3/3**. Its +trace names only the decoy (`payments.py`, where `httpx.Client(proxies=...)` +raises at import), yet seeing the call fail at construction pointed the model +at the dependency bump. The other paired differences are single-trial swings +already seen between the two baselines (`since_filter_naive_cutoff`, +`page_size_zero_division`, `export_tempfile_never_closed`). + +| Group (trials) | Baseline 10-04 | Baseline 10-07 | Traceback | +|---|---|---|---| +| Culprit file in trace (39) | 38/39 | 38/39 | 39/39 | +| Trace misses culprit file (21) | 15/21 | 16/21 | 18/21 | +| No trace (12) | 12/12 | 12/12 | 12/12 | + +A trace that pointed at a decoy did not pull the ranking towards it: that +group improved too. `export_tempfile_never_closed`, whose trace points at the +audit-log decoy, stayed at 0/3, where baseline had already put it (0/3 and +1/3). + +**Limitation:** a 4–5 point gain on 24 cases rests on one case. Read it as +"traces help and did not hurt here", not as a precise effect size. + +**Truncation check:** none of the 216 baseline/RAG/re-run trials hit the +3000-character diff limit, so truncation did not cause any of the misses. diff --git a/README.md b/README.md index 6054570..f0f4e45 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ Every hop reads and writes one auditable incident row conforming to - [METRICS.md](METRICS.md) — measured end-to-end timings, methodology, and per-run results - Measured diagnostic accuracy: faulty commit ranked #1 in 90.3% of 72 trials across 24 injected-fault cases with decoy commits ([METRICS.md, Phase 9](METRICS.md#phase-9--diagnostic-accuracy-and-runbook-context-in-ranking)) +- With the alert's stack trace in the ranking prompt: 95.8% of 72 trials, against 90.3% and 91.7% without, the gain coming from one case that went from 0/9 to 3/3 ([METRICS.md](METRICS.md#stack-traces-in-commit-ranking-2026-10-11)) - [ADR.md](ADR.md) — why explicit state loops over LangChain, polling over WebSockets, Chroma over Pinecone, FastAPI over Flask, tool-call JSON over free-form parsing, sandbox over live infra ## Sandbox reference diff --git a/eval/results/traceback_v2_sonnet5_2026-10-11.json b/eval/results/traceback_v2_sonnet5_2026-10-11.json new file mode 100644 index 0000000..3537cd6 --- /dev/null +++ b/eval/results/traceback_v2_sonnet5_2026-10-11.json @@ -0,0 +1,3881 @@ +{ + "condition": "traceback", + "dry_run": false, + "eval_set_version": "v2", + "traceback_set_version": "v1", + "runbook_corpus_version": "v1", + "runbooks_ingested": 10, + "runbook_floor": 0.35, + "model": "claude-sonnet-5", + "sentinel_git_sha": "6a345e231aa34c9d4899c669f875aad08bf7355c", + "started_at": "2026-10-11T19:36:38.073186+00:00", + "trials": 3, + "n_cases": 24, + "records": [ + { + "trial": 0, + "case_id": "auth_issuer_mesh_hosts", + "category": "auth_rejection", + "culprit_hash": "5e68f8a", + "expected_runbook_id": "auth_failures", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "auth_failures", + "similarity_score": 0.317 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.245 + }, + { + "id": "null_field_handling", + "similarity_score": 0.236 + } + ], + "ranking": [ + { + "commit_hash": "5e68f8a", + "confidence_score": 0.95 + }, + { + "commit_hash": "45a24d5", + "confidence_score": 0.05 + }, + { + "commit_hash": "df0ec4c", + "confidence_score": 0.05 + }, + { + "commit_hash": "298be8d", + "confidence_score": 0.02 + }, + { + "commit_hash": "d20025a", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 8.58, + "usage": { + "input_tokens": 3560, + "output_tokens": 738 + } + }, + { + "trial": 0, + "case_id": "cache_contains_recursion", + "category": "infinite_recursion", + "culprit_hash": "4584056", + "expected_runbook_id": null, + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "memory_leaks", + "similarity_score": 0.326 + }, + { + "id": "file_handle_exhaustion", + "similarity_score": 0.31 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.231 + } + ], + "ranking": [ + { + "commit_hash": "4584056", + "confidence_score": 0.98 + }, + { + "commit_hash": "6484609", + "confidence_score": 0.05 + }, + { + "commit_hash": "7e63ea3", + "confidence_score": 0.03 + }, + { + "commit_hash": "6cc141a", + "confidence_score": 0.0 + }, + { + "commit_hash": "a98de00", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.52, + "usage": { + "input_tokens": 3446, + "output_tokens": 591 + } + }, + { + "trial": 0, + "case_id": "cart_price_field_v2", + "category": "null_or_missing_field", + "culprit_hash": "d0ab213", + "expected_runbook_id": "null_field_handling", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "config_parse_errors", + "similarity_score": 0.26 + }, + { + "id": "pagination_errors", + "similarity_score": 0.207 + }, + { + "id": "memory_leaks", + "similarity_score": 0.17 + } + ], + "ranking": [ + { + "commit_hash": "d0ab213", + "confidence_score": 0.95 + }, + { + "commit_hash": "73e1933", + "confidence_score": 0.3 + }, + { + "commit_hash": "4fbde4c", + "confidence_score": 0.05 + }, + { + "commit_hash": "2bbbf3f", + "confidence_score": 0.02 + }, + { + "commit_hash": "f618f59", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.7, + "usage": { + "input_tokens": 3384, + "output_tokens": 727 + } + }, + { + "trial": 0, + "case_id": "cart_sku_lowercased", + "category": "identifier_format", + "culprit_hash": "c2ec824", + "expected_runbook_id": null, + "commits_in_window": 4, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "auth_failures", + "similarity_score": 0.293 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.247 + }, + { + "id": "null_field_handling", + "similarity_score": 0.229 + } + ], + "ranking": [ + { + "commit_hash": "c2ec824", + "confidence_score": 0.9 + }, + { + "commit_hash": "64dde29", + "confidence_score": 0.1 + }, + { + "commit_hash": "d0cb0b3", + "confidence_score": 0.02 + }, + { + "commit_hash": "91f8d36", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.97, + "usage": { + "input_tokens": 2991, + "output_tokens": 623 + } + }, + { + "trial": 0, + "case_id": "checkout_deadline_extra_call", + "category": "latency_timeout", + "culprit_hash": "efebc07", + "expected_runbook_id": "request_timeouts", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": false, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "request_timeouts", + "similarity_score": 0.487 + }, + { + "id": "db_failures", + "similarity_score": 0.373 + }, + { + "id": "rate_limiting", + "similarity_score": 0.353 + } + ], + "ranking": [ + { + "commit_hash": "efebc07", + "confidence_score": 0.85 + }, + { + "commit_hash": "a035477", + "confidence_score": 0.55 + }, + { + "commit_hash": "2b765f4", + "confidence_score": 0.3 + }, + { + "commit_hash": "be85cc9", + "confidence_score": 0.1 + }, + { + "commit_hash": "91b8b07", + "confidence_score": 0.02 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 7.62, + "usage": { + "input_tokens": 3255, + "output_tokens": 808 + } + }, + { + "trial": 0, + "case_id": "config_reformat_hidden_string", + "category": "config_type_error", + "culprit_hash": "c3629ef", + "expected_runbook_id": "config_parse_errors", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "config_parse_errors", + "similarity_score": 0.246 + }, + { + "id": "pagination_errors", + "similarity_score": 0.19 + }, + { + "id": "memory_leaks", + "similarity_score": 0.172 + } + ], + "ranking": [ + { + "commit_hash": "c3629ef", + "confidence_score": 0.9 + }, + { + "commit_hash": "3af442c", + "confidence_score": 0.2 + }, + { + "commit_hash": "976c988", + "confidence_score": 0.05 + }, + { + "commit_hash": "a6a3c41", + "confidence_score": 0.05 + }, + { + "commit_hash": "89436d3", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.56, + "usage": { + "input_tokens": 3480, + "output_tokens": 700 + } + }, + { + "trial": 0, + "case_id": "db_fetch_one_leak", + "category": "db_pool_exhaustion", + "culprit_hash": "af9b861", + "expected_runbook_id": "db_failures", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "db_failures", + "similarity_score": 0.628 + }, + { + "id": "memory_leaks", + "similarity_score": 0.344 + }, + { + "id": "request_timeouts", + "similarity_score": 0.28 + } + ], + "ranking": [ + { + "commit_hash": "af9b861", + "confidence_score": 0.9 + }, + { + "commit_hash": "f46cd8c", + "confidence_score": 0.5 + }, + { + "commit_hash": "6d50d20", + "confidence_score": 0.05 + }, + { + "commit_hash": "1618815", + "confidence_score": 0.05 + }, + { + "commit_hash": "1bf1024", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.22, + "usage": { + "input_tokens": 3516, + "output_tokens": 690 + } + }, + { + "trial": 0, + "case_id": "export_tempfile_never_closed", + "category": "file_handle_leak", + "culprit_hash": "6eea0fc", + "expected_runbook_id": "file_handle_exhaustion", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "file_handle_exhaustion", + "similarity_score": 0.625 + }, + { + "id": "memory_leaks", + "similarity_score": 0.411 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.291 + } + ], + "ranking": [ + { + "commit_hash": "5cedb21", + "confidence_score": 0.9 + }, + { + "commit_hash": "b245524", + "confidence_score": 0.25 + }, + { + "commit_hash": "6eea0fc", + "confidence_score": 0.2 + }, + { + "commit_hash": "0be651d", + "confidence_score": 0.02 + }, + { + "commit_hash": "7453733", + "confidence_score": 0.01 + } + ], + "culprit_rank": 3, + "error": null, + "rank_latency_s": 7.86, + "usage": { + "input_tokens": 3722, + "output_tokens": 813 + } + }, + { + "trial": 0, + "case_id": "guest_email_normalise", + "category": "null_or_missing_field", + "culprit_hash": "7dd25fe", + "expected_runbook_id": "null_field_handling", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "null_field_handling", + "similarity_score": 0.241 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.208 + }, + { + "id": "pagination_errors", + "similarity_score": 0.161 + } + ], + "ranking": [ + { + "commit_hash": "7dd25fe", + "confidence_score": 0.97 + }, + { + "commit_hash": "9519064", + "confidence_score": 0.2 + }, + { + "commit_hash": "30a7434", + "confidence_score": 0.0 + }, + { + "commit_hash": "1c0135b", + "confidence_score": 0.0 + }, + { + "commit_hash": "cde392a", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.23, + "usage": { + "input_tokens": 3135, + "output_tokens": 563 + } + }, + { + "trial": 0, + "case_id": "httpx_028_drops_proxies", + "category": "dependency_version", + "culprit_hash": "b35322f", + "expected_runbook_id": "dependency_regressions", + "commits_in_window": 4, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "auth_failures", + "similarity_score": 0.294 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.285 + }, + { + "id": "memory_leaks", + "similarity_score": 0.244 + } + ], + "ranking": [ + { + "commit_hash": "b35322f", + "confidence_score": 0.95 + }, + { + "commit_hash": "741f699", + "confidence_score": 0.85 + }, + { + "commit_hash": "b45189f", + "confidence_score": 0.03 + }, + { + "commit_hash": "e3601f6", + "confidence_score": 0.02 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.28, + "usage": { + "input_tokens": 2993, + "output_tokens": 599 + } + }, + { + "trial": 0, + "case_id": "inventory_cache_key_per_request", + "category": "rate_limit_429", + "culprit_hash": "526f8dd", + "expected_runbook_id": "rate_limiting", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "rate_limiting", + "similarity_score": 0.382 + }, + { + "id": "auth_failures", + "similarity_score": 0.381 + }, + { + "id": "null_field_handling", + "similarity_score": 0.309 + } + ], + "ranking": [ + { + "commit_hash": "526f8dd", + "confidence_score": 0.95 + }, + { + "commit_hash": "bf4cc5f", + "confidence_score": 0.35 + }, + { + "commit_hash": "93320f2", + "confidence_score": 0.05 + }, + { + "commit_hash": "2e1b06b", + "confidence_score": 0.02 + }, + { + "commit_hash": "d9caa65", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.09, + "usage": { + "input_tokens": 3709, + "output_tokens": 678 + } + }, + { + "trial": 0, + "case_id": "inventory_v3_payload", + "category": "null_or_missing_field", + "culprit_hash": "1c29d94", + "expected_runbook_id": "null_field_handling", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "config_parse_errors", + "similarity_score": 0.33 + }, + { + "id": "file_handle_exhaustion", + "similarity_score": 0.28 + }, + { + "id": "memory_leaks", + "similarity_score": 0.271 + } + ], + "ranking": [ + { + "commit_hash": "1c29d94", + "confidence_score": 0.9 + }, + { + "commit_hash": "16d2191", + "confidence_score": 0.1 + }, + { + "commit_hash": "c6f0797", + "confidence_score": 0.05 + }, + { + "commit_hash": "5823d1f", + "confidence_score": 0.03 + }, + { + "commit_hash": "fb9b710", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.17, + "usage": { + "input_tokens": 3556, + "output_tokens": 582 + } + }, + { + "trial": 0, + "case_id": "jwt_leeway_milliseconds", + "category": "auth_rejection", + "culprit_hash": "2eeb222", + "expected_runbook_id": "auth_failures", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "auth_failures", + "similarity_score": 0.488 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.198 + }, + { + "id": "request_timeouts", + "similarity_score": 0.159 + } + ], + "ranking": [ + { + "commit_hash": "2eeb222", + "confidence_score": 0.95 + }, + { + "commit_hash": "e8548c5", + "confidence_score": 0.2 + }, + { + "commit_hash": "2e11931", + "confidence_score": 0.05 + }, + { + "commit_hash": "a028c62", + "confidence_score": 0.05 + }, + { + "commit_hash": "716e22d", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 7.0, + "usage": { + "input_tokens": 3526, + "output_tokens": 674 + } + }, + { + "trial": 0, + "case_id": "money_returns_decimal", + "category": "json_serialization", + "culprit_hash": "b14897b", + "expected_runbook_id": null, + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "config_parse_errors", + "similarity_score": 0.298 + }, + { + "id": "null_field_handling", + "similarity_score": 0.151 + }, + { + "id": "auth_failures", + "similarity_score": 0.13 + } + ], + "ranking": [ + { + "commit_hash": "b14897b", + "confidence_score": 0.97 + }, + { + "commit_hash": "dedc41a", + "confidence_score": 0.1 + }, + { + "commit_hash": "626c22d", + "confidence_score": 0.03 + }, + { + "commit_hash": "cd4a49f", + "confidence_score": 0.01 + }, + { + "commit_hash": "92cb3da", + "confidence_score": 0.01 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.45, + "usage": { + "input_tokens": 3313, + "output_tokens": 719 + } + }, + { + "trial": 0, + "case_id": "order_read_sync_audit", + "category": "latency_timeout", + "culprit_hash": "8aed86c", + "expected_runbook_id": "request_timeouts", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": false, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "request_timeouts", + "similarity_score": 0.539 + }, + { + "id": "rate_limiting", + "similarity_score": 0.369 + }, + { + "id": "auth_failures", + "similarity_score": 0.35 + } + ], + "ranking": [ + { + "commit_hash": "8aed86c", + "confidence_score": 0.85 + }, + { + "commit_hash": "d3d6f87", + "confidence_score": 0.45 + }, + { + "commit_hash": "94578cc", + "confidence_score": 0.3 + }, + { + "commit_hash": "5abe2e4", + "confidence_score": 0.05 + }, + { + "commit_hash": "b7ea08d", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 7.84, + "usage": { + "input_tokens": 3260, + "output_tokens": 813 + } + }, + { + "trial": 0, + "case_id": "order_sql_missing_param", + "category": "sql_param_mismatch", + "culprit_hash": "9ef79b2", + "expected_runbook_id": null, + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "pagination_errors", + "similarity_score": 0.322 + }, + { + "id": "file_handle_exhaustion", + "similarity_score": 0.23 + }, + { + "id": "memory_leaks", + "similarity_score": 0.188 + } + ], + "ranking": [ + { + "commit_hash": "9ef79b2", + "confidence_score": 0.95 + }, + { + "commit_hash": "661f871", + "confidence_score": 0.05 + }, + { + "commit_hash": "66d5f2f", + "confidence_score": 0.02 + }, + { + "commit_hash": "93ebe2f", + "confidence_score": 0.01 + }, + { + "commit_hash": "411ddfa", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.61, + "usage": { + "input_tokens": 3452, + "output_tokens": 612 + } + }, + { + "trial": 0, + "case_id": "page_size_zero_division", + "category": "pagination_boundary", + "culprit_hash": "6265f24", + "expected_runbook_id": "pagination_errors", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "config_parse_errors", + "similarity_score": 0.146 + }, + { + "id": "memory_leaks", + "similarity_score": 0.13 + }, + { + "id": "pagination_errors", + "similarity_score": 0.127 + } + ], + "ranking": [ + { + "commit_hash": "6265f24", + "confidence_score": 0.92 + }, + { + "commit_hash": "ed3861b", + "confidence_score": 0.65 + }, + { + "commit_hash": "0d241ff", + "confidence_score": 0.1 + }, + { + "commit_hash": "ef48acf", + "confidence_score": 0.02 + }, + { + "commit_hash": "debe268", + "confidence_score": 0.01 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.5, + "usage": { + "input_tokens": 3189, + "output_tokens": 599 + } + }, + { + "trial": 0, + "case_id": "payments_body_without_content_type", + "category": "http_content_type", + "culprit_hash": "33a9e2a", + "expected_runbook_id": null, + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "auth_failures", + "similarity_score": 0.274 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.164 + }, + { + "id": "null_field_handling", + "similarity_score": 0.143 + } + ], + "ranking": [ + { + "commit_hash": "33a9e2a", + "confidence_score": 0.95 + }, + { + "commit_hash": "e60edf2", + "confidence_score": 0.05 + }, + { + "commit_hash": "5430f37", + "confidence_score": 0.02 + }, + { + "commit_hash": "91f3040", + "confidence_score": 0.02 + }, + { + "commit_hash": "64ee5a8", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.94, + "usage": { + "input_tokens": 3771, + "output_tokens": 559 + } + }, + { + "trial": 0, + "case_id": "payments_retry_loop", + "category": "rate_limit_429", + "culprit_hash": "f07e927", + "expected_runbook_id": "rate_limiting", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "rate_limiting", + "similarity_score": 0.357 + }, + { + "id": "auth_failures", + "similarity_score": 0.352 + }, + { + "id": "request_timeouts", + "similarity_score": 0.323 + } + ], + "ranking": [ + { + "commit_hash": "f07e927", + "confidence_score": 0.85 + }, + { + "commit_hash": "07a85f8", + "confidence_score": 0.8 + }, + { + "commit_hash": "c1fc55f", + "confidence_score": 0.05 + }, + { + "commit_hash": "429c4dd", + "confidence_score": 0.0 + }, + { + "commit_hash": "51bc043", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.97, + "usage": { + "input_tokens": 3682, + "output_tokens": 631 + } + }, + { + "trial": 0, + "case_id": "redis_legacy_lrem_args", + "category": "dependency_version", + "culprit_hash": "0751831", + "expected_runbook_id": "dependency_regressions", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "null_field_handling", + "similarity_score": 0.324 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.317 + }, + { + "id": "auth_failures", + "similarity_score": 0.269 + } + ], + "ranking": [ + { + "commit_hash": "0751831", + "confidence_score": 0.55 + }, + { + "commit_hash": "fd92e84", + "confidence_score": 0.4 + }, + { + "commit_hash": "3059910", + "confidence_score": 0.03 + }, + { + "commit_hash": "7b9dd2c", + "confidence_score": 0.01 + }, + { + "commit_hash": "957a42e", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.84, + "usage": { + "input_tokens": 3032, + "output_tokens": 723 + } + }, + { + "trial": 0, + "case_id": "settings_env_quotes", + "category": "config_type_error", + "culprit_hash": "15aba07", + "expected_runbook_id": "config_parse_errors", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "db_failures", + "similarity_score": 0.374 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.278 + }, + { + "id": "memory_leaks", + "similarity_score": 0.233 + } + ], + "ranking": [ + { + "commit_hash": "15aba07", + "confidence_score": 0.85 + }, + { + "commit_hash": "cae9a6c", + "confidence_score": 0.1 + }, + { + "commit_hash": "423c51f", + "confidence_score": 0.05 + }, + { + "commit_hash": "3e36aa4", + "confidence_score": 0.05 + }, + { + "commit_hash": "55cfa0d", + "confidence_score": 0.02 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 7.28, + "usage": { + "input_tokens": 3391, + "output_tokens": 740 + } + }, + { + "trial": 0, + "case_id": "since_filter_naive_cutoff", + "category": "timezone_mismatch", + "culprit_hash": "266c09c", + "expected_runbook_id": null, + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "pagination_errors", + "similarity_score": 0.279 + }, + { + "id": "dependency_regressions", + "similarity_score": 0.224 + }, + { + "id": "memory_leaks", + "similarity_score": 0.21 + } + ], + "ranking": [ + { + "commit_hash": "266c09c", + "confidence_score": 0.95 + }, + { + "commit_hash": "f3ad8b7", + "confidence_score": 0.4 + }, + { + "commit_hash": "7f71cc9", + "confidence_score": 0.05 + }, + { + "commit_hash": "ef6b4a1", + "confidence_score": 0.02 + }, + { + "commit_hash": "5948e6d", + "confidence_score": 0.01 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.55, + "usage": { + "input_tokens": 3219, + "output_tokens": 708 + } + }, + { + "trial": 0, + "case_id": "worker_queue_renamed", + "category": "queue_stall", + "culprit_hash": "f8d8258", + "expected_runbook_id": null, + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": false, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "request_timeouts", + "similarity_score": 0.411 + }, + { + "id": "rate_limiting", + "similarity_score": 0.409 + }, + { + "id": "file_handle_exhaustion", + "similarity_score": 0.383 + } + ], + "ranking": [ + { + "commit_hash": "f8d8258", + "confidence_score": 0.85 + }, + { + "commit_hash": "0f497ec", + "confidence_score": 0.2 + }, + { + "commit_hash": "110eec6", + "confidence_score": 0.05 + }, + { + "commit_hash": "aacbcd7", + "confidence_score": 0.02 + }, + { + "commit_hash": "3434d6b", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.81, + "usage": { + "input_tokens": 2774, + "output_tokens": 639 + } + }, + { + "trial": 0, + "case_id": "worker_retains_job_payloads", + "category": "memory_growth", + "culprit_hash": "3037583", + "expected_runbook_id": "memory_leaks", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": false, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "file_handle_exhaustion", + "similarity_score": 0.48 + }, + { + "id": "memory_leaks", + "similarity_score": 0.434 + }, + { + "id": "rate_limiting", + "similarity_score": 0.424 + } + ], + "ranking": [ + { + "commit_hash": "3037583", + "confidence_score": 0.95 + }, + { + "commit_hash": "088e964", + "confidence_score": 0.2 + }, + { + "commit_hash": "947ac52", + "confidence_score": 0.05 + }, + { + "commit_hash": "15f34e0", + "confidence_score": 0.02 + }, + { + "commit_hash": "63c2633", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 7.58, + "usage": { + "input_tokens": 3095, + "output_tokens": 817 + } + }, + { + "trial": 1, + "case_id": "auth_issuer_mesh_hosts", + "category": "auth_rejection", + "culprit_hash": "5e68f8a", + "expected_runbook_id": "auth_failures", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "auth_failures", + "similarity_score": 0.317 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.245 + }, + { + "id": "null_field_handling", + "similarity_score": 0.236 + } + ], + "ranking": [ + { + "commit_hash": "5e68f8a", + "confidence_score": 0.95 + }, + { + "commit_hash": "45a24d5", + "confidence_score": 0.05 + }, + { + "commit_hash": "df0ec4c", + "confidence_score": 0.05 + }, + { + "commit_hash": "298be8d", + "confidence_score": 0.02 + }, + { + "commit_hash": "d20025a", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.12, + "usage": { + "input_tokens": 3560, + "output_tokens": 587 + } + }, + { + "trial": 1, + "case_id": "cache_contains_recursion", + "category": "infinite_recursion", + "culprit_hash": "4584056", + "expected_runbook_id": null, + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "memory_leaks", + "similarity_score": 0.326 + }, + { + "id": "file_handle_exhaustion", + "similarity_score": 0.31 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.231 + } + ], + "ranking": [ + { + "commit_hash": "4584056", + "confidence_score": 0.98 + }, + { + "commit_hash": "6484609", + "confidence_score": 0.1 + }, + { + "commit_hash": "7e63ea3", + "confidence_score": 0.05 + }, + { + "commit_hash": "6cc141a", + "confidence_score": 0.0 + }, + { + "commit_hash": "a98de00", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.78, + "usage": { + "input_tokens": 3446, + "output_tokens": 591 + } + }, + { + "trial": 1, + "case_id": "cart_price_field_v2", + "category": "null_or_missing_field", + "culprit_hash": "d0ab213", + "expected_runbook_id": "null_field_handling", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "config_parse_errors", + "similarity_score": 0.26 + }, + { + "id": "pagination_errors", + "similarity_score": 0.207 + }, + { + "id": "memory_leaks", + "similarity_score": 0.17 + } + ], + "ranking": [ + { + "commit_hash": "d0ab213", + "confidence_score": 0.95 + }, + { + "commit_hash": "73e1933", + "confidence_score": 0.2 + }, + { + "commit_hash": "4fbde4c", + "confidence_score": 0.05 + }, + { + "commit_hash": "2bbbf3f", + "confidence_score": 0.02 + }, + { + "commit_hash": "f618f59", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 7.0, + "usage": { + "input_tokens": 3384, + "output_tokens": 711 + } + }, + { + "trial": 1, + "case_id": "cart_sku_lowercased", + "category": "identifier_format", + "culprit_hash": "c2ec824", + "expected_runbook_id": null, + "commits_in_window": 4, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "auth_failures", + "similarity_score": 0.293 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.247 + }, + { + "id": "null_field_handling", + "similarity_score": 0.229 + } + ], + "ranking": [ + { + "commit_hash": "c2ec824", + "confidence_score": 0.9 + }, + { + "commit_hash": "64dde29", + "confidence_score": 0.2 + }, + { + "commit_hash": "d0cb0b3", + "confidence_score": 0.05 + }, + { + "commit_hash": "91f8d36", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.47, + "usage": { + "input_tokens": 2991, + "output_tokens": 618 + } + }, + { + "trial": 1, + "case_id": "checkout_deadline_extra_call", + "category": "latency_timeout", + "culprit_hash": "efebc07", + "expected_runbook_id": "request_timeouts", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": false, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "request_timeouts", + "similarity_score": 0.487 + }, + { + "id": "db_failures", + "similarity_score": 0.373 + }, + { + "id": "rate_limiting", + "similarity_score": 0.353 + } + ], + "ranking": [ + { + "commit_hash": "efebc07", + "confidence_score": 0.85 + }, + { + "commit_hash": "a035477", + "confidence_score": 0.55 + }, + { + "commit_hash": "2b765f4", + "confidence_score": 0.3 + }, + { + "commit_hash": "be85cc9", + "confidence_score": 0.1 + }, + { + "commit_hash": "91b8b07", + "confidence_score": 0.02 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.94, + "usage": { + "input_tokens": 3255, + "output_tokens": 755 + } + }, + { + "trial": 1, + "case_id": "config_reformat_hidden_string", + "category": "config_type_error", + "culprit_hash": "c3629ef", + "expected_runbook_id": "config_parse_errors", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "config_parse_errors", + "similarity_score": 0.246 + }, + { + "id": "pagination_errors", + "similarity_score": 0.19 + }, + { + "id": "memory_leaks", + "similarity_score": 0.172 + } + ], + "ranking": [ + { + "commit_hash": "c3629ef", + "confidence_score": 0.95 + }, + { + "commit_hash": "3af442c", + "confidence_score": 0.2 + }, + { + "commit_hash": "976c988", + "confidence_score": 0.05 + }, + { + "commit_hash": "a6a3c41", + "confidence_score": 0.05 + }, + { + "commit_hash": "89436d3", + "confidence_score": 0.01 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.36, + "usage": { + "input_tokens": 3480, + "output_tokens": 677 + } + }, + { + "trial": 1, + "case_id": "db_fetch_one_leak", + "category": "db_pool_exhaustion", + "culprit_hash": "af9b861", + "expected_runbook_id": "db_failures", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "db_failures", + "similarity_score": 0.628 + }, + { + "id": "memory_leaks", + "similarity_score": 0.344 + }, + { + "id": "request_timeouts", + "similarity_score": 0.28 + } + ], + "ranking": [ + { + "commit_hash": "af9b861", + "confidence_score": 0.95 + }, + { + "commit_hash": "f46cd8c", + "confidence_score": 0.55 + }, + { + "commit_hash": "1618815", + "confidence_score": 0.05 + }, + { + "commit_hash": "6d50d20", + "confidence_score": 0.02 + }, + { + "commit_hash": "1bf1024", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 8.09, + "usage": { + "input_tokens": 3516, + "output_tokens": 822 + } + }, + { + "trial": 1, + "case_id": "export_tempfile_never_closed", + "category": "file_handle_leak", + "culprit_hash": "6eea0fc", + "expected_runbook_id": "file_handle_exhaustion", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "file_handle_exhaustion", + "similarity_score": 0.625 + }, + { + "id": "memory_leaks", + "similarity_score": 0.411 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.291 + } + ], + "ranking": [ + { + "commit_hash": "5cedb21", + "confidence_score": 0.95 + }, + { + "commit_hash": "b245524", + "confidence_score": 0.05 + }, + { + "commit_hash": "6eea0fc", + "confidence_score": 0.05 + }, + { + "commit_hash": "0be651d", + "confidence_score": 0.02 + }, + { + "commit_hash": "7453733", + "confidence_score": 0.01 + } + ], + "culprit_rank": 3, + "error": null, + "rank_latency_s": 6.69, + "usage": { + "input_tokens": 3722, + "output_tokens": 720 + } + }, + { + "trial": 1, + "case_id": "guest_email_normalise", + "category": "null_or_missing_field", + "culprit_hash": "7dd25fe", + "expected_runbook_id": "null_field_handling", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "null_field_handling", + "similarity_score": 0.241 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.208 + }, + { + "id": "pagination_errors", + "similarity_score": 0.161 + } + ], + "ranking": [ + { + "commit_hash": "7dd25fe", + "confidence_score": 0.98 + }, + { + "commit_hash": "9519064", + "confidence_score": 0.1 + }, + { + "commit_hash": "30a7434", + "confidence_score": 0.0 + }, + { + "commit_hash": "1c0135b", + "confidence_score": 0.0 + }, + { + "commit_hash": "cde392a", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.31, + "usage": { + "input_tokens": 3135, + "output_tokens": 629 + } + }, + { + "trial": 1, + "case_id": "httpx_028_drops_proxies", + "category": "dependency_version", + "culprit_hash": "b35322f", + "expected_runbook_id": "dependency_regressions", + "commits_in_window": 4, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "auth_failures", + "similarity_score": 0.294 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.285 + }, + { + "id": "memory_leaks", + "similarity_score": 0.244 + } + ], + "ranking": [ + { + "commit_hash": "b35322f", + "confidence_score": 0.9 + }, + { + "commit_hash": "741f699", + "confidence_score": 0.85 + }, + { + "commit_hash": "b45189f", + "confidence_score": 0.02 + }, + { + "commit_hash": "e3601f6", + "confidence_score": 0.01 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.0, + "usage": { + "input_tokens": 2993, + "output_tokens": 585 + } + }, + { + "trial": 1, + "case_id": "inventory_cache_key_per_request", + "category": "rate_limit_429", + "culprit_hash": "526f8dd", + "expected_runbook_id": "rate_limiting", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "rate_limiting", + "similarity_score": 0.382 + }, + { + "id": "auth_failures", + "similarity_score": 0.381 + }, + { + "id": "null_field_handling", + "similarity_score": 0.309 + } + ], + "ranking": [ + { + "commit_hash": "526f8dd", + "confidence_score": 0.92 + }, + { + "commit_hash": "bf4cc5f", + "confidence_score": 0.55 + }, + { + "commit_hash": "93320f2", + "confidence_score": 0.05 + }, + { + "commit_hash": "2e1b06b", + "confidence_score": 0.01 + }, + { + "commit_hash": "d9caa65", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.34, + "usage": { + "input_tokens": 3709, + "output_tokens": 665 + } + }, + { + "trial": 1, + "case_id": "inventory_v3_payload", + "category": "null_or_missing_field", + "culprit_hash": "1c29d94", + "expected_runbook_id": "null_field_handling", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "config_parse_errors", + "similarity_score": 0.33 + }, + { + "id": "file_handle_exhaustion", + "similarity_score": 0.28 + }, + { + "id": "memory_leaks", + "similarity_score": 0.271 + } + ], + "ranking": [ + { + "commit_hash": "1c29d94", + "confidence_score": 0.92 + }, + { + "commit_hash": "16d2191", + "confidence_score": 0.05 + }, + { + "commit_hash": "c6f0797", + "confidence_score": 0.02 + }, + { + "commit_hash": "5823d1f", + "confidence_score": 0.01 + }, + { + "commit_hash": "fb9b710", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.14, + "usage": { + "input_tokens": 3556, + "output_tokens": 586 + } + }, + { + "trial": 1, + "case_id": "jwt_leeway_milliseconds", + "category": "auth_rejection", + "culprit_hash": "2eeb222", + "expected_runbook_id": "auth_failures", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "auth_failures", + "similarity_score": 0.488 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.198 + }, + { + "id": "request_timeouts", + "similarity_score": 0.159 + } + ], + "ranking": [ + { + "commit_hash": "2eeb222", + "confidence_score": 0.95 + }, + { + "commit_hash": "2e11931", + "confidence_score": 0.3 + }, + { + "commit_hash": "e8548c5", + "confidence_score": 0.1 + }, + { + "commit_hash": "a028c62", + "confidence_score": 0.05 + }, + { + "commit_hash": "716e22d", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 7.02, + "usage": { + "input_tokens": 3526, + "output_tokens": 705 + } + }, + { + "trial": 1, + "case_id": "money_returns_decimal", + "category": "json_serialization", + "culprit_hash": "b14897b", + "expected_runbook_id": null, + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "config_parse_errors", + "similarity_score": 0.298 + }, + { + "id": "null_field_handling", + "similarity_score": 0.151 + }, + { + "id": "auth_failures", + "similarity_score": 0.13 + } + ], + "ranking": [ + { + "commit_hash": "b14897b", + "confidence_score": 0.98 + }, + { + "commit_hash": "dedc41a", + "confidence_score": 0.05 + }, + { + "commit_hash": "626c22d", + "confidence_score": 0.05 + }, + { + "commit_hash": "cd4a49f", + "confidence_score": 0.02 + }, + { + "commit_hash": "92cb3da", + "confidence_score": 0.02 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.92, + "usage": { + "input_tokens": 3313, + "output_tokens": 708 + } + }, + { + "trial": 1, + "case_id": "order_read_sync_audit", + "category": "latency_timeout", + "culprit_hash": "8aed86c", + "expected_runbook_id": "request_timeouts", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": false, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "request_timeouts", + "similarity_score": 0.539 + }, + { + "id": "rate_limiting", + "similarity_score": 0.369 + }, + { + "id": "auth_failures", + "similarity_score": 0.35 + } + ], + "ranking": [ + { + "commit_hash": "8aed86c", + "confidence_score": 0.85 + }, + { + "commit_hash": "d3d6f87", + "confidence_score": 0.6 + }, + { + "commit_hash": "94578cc", + "confidence_score": 0.55 + }, + { + "commit_hash": "5abe2e4", + "confidence_score": 0.05 + }, + { + "commit_hash": "b7ea08d", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 7.72, + "usage": { + "input_tokens": 3260, + "output_tokens": 802 + } + }, + { + "trial": 1, + "case_id": "order_sql_missing_param", + "category": "sql_param_mismatch", + "culprit_hash": "9ef79b2", + "expected_runbook_id": null, + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "pagination_errors", + "similarity_score": 0.322 + }, + { + "id": "file_handle_exhaustion", + "similarity_score": 0.23 + }, + { + "id": "memory_leaks", + "similarity_score": 0.188 + } + ], + "ranking": [ + { + "commit_hash": "9ef79b2", + "confidence_score": 0.97 + }, + { + "commit_hash": "661f871", + "confidence_score": 0.1 + }, + { + "commit_hash": "66d5f2f", + "confidence_score": 0.02 + }, + { + "commit_hash": "93ebe2f", + "confidence_score": 0.01 + }, + { + "commit_hash": "411ddfa", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.59, + "usage": { + "input_tokens": 3452, + "output_tokens": 626 + } + }, + { + "trial": 1, + "case_id": "page_size_zero_division", + "category": "pagination_boundary", + "culprit_hash": "6265f24", + "expected_runbook_id": "pagination_errors", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "config_parse_errors", + "similarity_score": 0.146 + }, + { + "id": "memory_leaks", + "similarity_score": 0.13 + }, + { + "id": "pagination_errors", + "similarity_score": 0.127 + } + ], + "ranking": [ + { + "commit_hash": "6265f24", + "confidence_score": 0.92 + }, + { + "commit_hash": "ed3861b", + "confidence_score": 0.65 + }, + { + "commit_hash": "0d241ff", + "confidence_score": 0.08 + }, + { + "commit_hash": "ef48acf", + "confidence_score": 0.02 + }, + { + "commit_hash": "debe268", + "confidence_score": 0.01 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.03, + "usage": { + "input_tokens": 3189, + "output_tokens": 655 + } + }, + { + "trial": 1, + "case_id": "payments_body_without_content_type", + "category": "http_content_type", + "culprit_hash": "33a9e2a", + "expected_runbook_id": null, + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "auth_failures", + "similarity_score": 0.274 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.164 + }, + { + "id": "null_field_handling", + "similarity_score": 0.143 + } + ], + "ranking": [ + { + "commit_hash": "33a9e2a", + "confidence_score": 0.97 + }, + { + "commit_hash": "e60edf2", + "confidence_score": 0.05 + }, + { + "commit_hash": "91f3040", + "confidence_score": 0.03 + }, + { + "commit_hash": "5430f37", + "confidence_score": 0.02 + }, + { + "commit_hash": "64ee5a8", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.28, + "usage": { + "input_tokens": 3771, + "output_tokens": 639 + } + }, + { + "trial": 1, + "case_id": "payments_retry_loop", + "category": "rate_limit_429", + "culprit_hash": "f07e927", + "expected_runbook_id": "rate_limiting", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "rate_limiting", + "similarity_score": 0.357 + }, + { + "id": "auth_failures", + "similarity_score": 0.352 + }, + { + "id": "request_timeouts", + "similarity_score": 0.323 + } + ], + "ranking": [ + { + "commit_hash": "f07e927", + "confidence_score": 0.85 + }, + { + "commit_hash": "07a85f8", + "confidence_score": 0.75 + }, + { + "commit_hash": "c1fc55f", + "confidence_score": 0.05 + }, + { + "commit_hash": "429c4dd", + "confidence_score": 0.0 + }, + { + "commit_hash": "51bc043", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.58, + "usage": { + "input_tokens": 3682, + "output_tokens": 572 + } + }, + { + "trial": 1, + "case_id": "redis_legacy_lrem_args", + "category": "dependency_version", + "culprit_hash": "0751831", + "expected_runbook_id": "dependency_regressions", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "null_field_handling", + "similarity_score": 0.324 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.317 + }, + { + "id": "auth_failures", + "similarity_score": 0.269 + } + ], + "ranking": [ + { + "commit_hash": "0751831", + "confidence_score": 0.85 + }, + { + "commit_hash": "fd92e84", + "confidence_score": 0.55 + }, + { + "commit_hash": "3059910", + "confidence_score": 0.05 + }, + { + "commit_hash": "7b9dd2c", + "confidence_score": 0.02 + }, + { + "commit_hash": "957a42e", + "confidence_score": 0.01 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.73, + "usage": { + "input_tokens": 3032, + "output_tokens": 671 + } + }, + { + "trial": 1, + "case_id": "settings_env_quotes", + "category": "config_type_error", + "culprit_hash": "15aba07", + "expected_runbook_id": "config_parse_errors", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "db_failures", + "similarity_score": 0.374 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.278 + }, + { + "id": "memory_leaks", + "similarity_score": 0.233 + } + ], + "ranking": [ + { + "commit_hash": "15aba07", + "confidence_score": 0.9 + }, + { + "commit_hash": "423c51f", + "confidence_score": 0.1 + }, + { + "commit_hash": "cae9a6c", + "confidence_score": 0.05 + }, + { + "commit_hash": "3e36aa4", + "confidence_score": 0.05 + }, + { + "commit_hash": "55cfa0d", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.5, + "usage": { + "input_tokens": 3391, + "output_tokens": 720 + } + }, + { + "trial": 1, + "case_id": "since_filter_naive_cutoff", + "category": "timezone_mismatch", + "culprit_hash": "266c09c", + "expected_runbook_id": null, + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "pagination_errors", + "similarity_score": 0.279 + }, + { + "id": "dependency_regressions", + "similarity_score": 0.224 + }, + { + "id": "memory_leaks", + "similarity_score": 0.21 + } + ], + "ranking": [ + { + "commit_hash": "266c09c", + "confidence_score": 0.95 + }, + { + "commit_hash": "f3ad8b7", + "confidence_score": 0.35 + }, + { + "commit_hash": "7f71cc9", + "confidence_score": 0.05 + }, + { + "commit_hash": "ef6b4a1", + "confidence_score": 0.02 + }, + { + "commit_hash": "5948e6d", + "confidence_score": 0.01 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.22, + "usage": { + "input_tokens": 3219, + "output_tokens": 700 + } + }, + { + "trial": 1, + "case_id": "worker_queue_renamed", + "category": "queue_stall", + "culprit_hash": "f8d8258", + "expected_runbook_id": null, + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": false, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "request_timeouts", + "similarity_score": 0.411 + }, + { + "id": "rate_limiting", + "similarity_score": 0.409 + }, + { + "id": "file_handle_exhaustion", + "similarity_score": 0.383 + } + ], + "ranking": [ + { + "commit_hash": "f8d8258", + "confidence_score": 0.9 + }, + { + "commit_hash": "0f497ec", + "confidence_score": 0.25 + }, + { + "commit_hash": "110eec6", + "confidence_score": 0.1 + }, + { + "commit_hash": "aacbcd7", + "confidence_score": 0.03 + }, + { + "commit_hash": "3434d6b", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.12, + "usage": { + "input_tokens": 2774, + "output_tokens": 661 + } + }, + { + "trial": 1, + "case_id": "worker_retains_job_payloads", + "category": "memory_growth", + "culprit_hash": "3037583", + "expected_runbook_id": "memory_leaks", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": false, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "file_handle_exhaustion", + "similarity_score": 0.48 + }, + { + "id": "memory_leaks", + "similarity_score": 0.434 + }, + { + "id": "rate_limiting", + "similarity_score": 0.424 + } + ], + "ranking": [ + { + "commit_hash": "3037583", + "confidence_score": 0.95 + }, + { + "commit_hash": "088e964", + "confidence_score": 0.2 + }, + { + "commit_hash": "947ac52", + "confidence_score": 0.05 + }, + { + "commit_hash": "15f34e0", + "confidence_score": 0.02 + }, + { + "commit_hash": "63c2633", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.39, + "usage": { + "input_tokens": 3095, + "output_tokens": 568 + } + }, + { + "trial": 2, + "case_id": "auth_issuer_mesh_hosts", + "category": "auth_rejection", + "culprit_hash": "5e68f8a", + "expected_runbook_id": "auth_failures", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "auth_failures", + "similarity_score": 0.317 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.245 + }, + { + "id": "null_field_handling", + "similarity_score": 0.236 + } + ], + "ranking": [ + { + "commit_hash": "5e68f8a", + "confidence_score": 0.95 + }, + { + "commit_hash": "45a24d5", + "confidence_score": 0.05 + }, + { + "commit_hash": "df0ec4c", + "confidence_score": 0.05 + }, + { + "commit_hash": "298be8d", + "confidence_score": 0.02 + }, + { + "commit_hash": "d20025a", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.33, + "usage": { + "input_tokens": 3560, + "output_tokens": 598 + } + }, + { + "trial": 2, + "case_id": "cache_contains_recursion", + "category": "infinite_recursion", + "culprit_hash": "4584056", + "expected_runbook_id": null, + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "memory_leaks", + "similarity_score": 0.326 + }, + { + "id": "file_handle_exhaustion", + "similarity_score": 0.31 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.231 + } + ], + "ranking": [ + { + "commit_hash": "4584056", + "confidence_score": 0.98 + }, + { + "commit_hash": "6484609", + "confidence_score": 0.05 + }, + { + "commit_hash": "7e63ea3", + "confidence_score": 0.02 + }, + { + "commit_hash": "6cc141a", + "confidence_score": 0.0 + }, + { + "commit_hash": "a98de00", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.05, + "usage": { + "input_tokens": 3446, + "output_tokens": 572 + } + }, + { + "trial": 2, + "case_id": "cart_price_field_v2", + "category": "null_or_missing_field", + "culprit_hash": "d0ab213", + "expected_runbook_id": "null_field_handling", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "config_parse_errors", + "similarity_score": 0.26 + }, + { + "id": "pagination_errors", + "similarity_score": 0.207 + }, + { + "id": "memory_leaks", + "similarity_score": 0.17 + } + ], + "ranking": [ + { + "commit_hash": "d0ab213", + "confidence_score": 0.95 + }, + { + "commit_hash": "73e1933", + "confidence_score": 0.2 + }, + { + "commit_hash": "4fbde4c", + "confidence_score": 0.03 + }, + { + "commit_hash": "2bbbf3f", + "confidence_score": 0.01 + }, + { + "commit_hash": "f618f59", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.7, + "usage": { + "input_tokens": 3384, + "output_tokens": 708 + } + }, + { + "trial": 2, + "case_id": "cart_sku_lowercased", + "category": "identifier_format", + "culprit_hash": "c2ec824", + "expected_runbook_id": null, + "commits_in_window": 4, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "auth_failures", + "similarity_score": 0.293 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.247 + }, + { + "id": "null_field_handling", + "similarity_score": 0.229 + } + ], + "ranking": [ + { + "commit_hash": "c2ec824", + "confidence_score": 0.85 + }, + { + "commit_hash": "64dde29", + "confidence_score": 0.2 + }, + { + "commit_hash": "d0cb0b3", + "confidence_score": 0.02 + }, + { + "commit_hash": "91f8d36", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.98, + "usage": { + "input_tokens": 2991, + "output_tokens": 642 + } + }, + { + "trial": 2, + "case_id": "checkout_deadline_extra_call", + "category": "latency_timeout", + "culprit_hash": "efebc07", + "expected_runbook_id": "request_timeouts", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": false, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "request_timeouts", + "similarity_score": 0.487 + }, + { + "id": "db_failures", + "similarity_score": 0.373 + }, + { + "id": "rate_limiting", + "similarity_score": 0.353 + } + ], + "ranking": [ + { + "commit_hash": "efebc07", + "confidence_score": 0.85 + }, + { + "commit_hash": "a035477", + "confidence_score": 0.55 + }, + { + "commit_hash": "2b765f4", + "confidence_score": 0.3 + }, + { + "commit_hash": "be85cc9", + "confidence_score": 0.1 + }, + { + "commit_hash": "91b8b07", + "confidence_score": 0.02 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.78, + "usage": { + "input_tokens": 3255, + "output_tokens": 746 + } + }, + { + "trial": 2, + "case_id": "config_reformat_hidden_string", + "category": "config_type_error", + "culprit_hash": "c3629ef", + "expected_runbook_id": "config_parse_errors", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "config_parse_errors", + "similarity_score": 0.246 + }, + { + "id": "pagination_errors", + "similarity_score": 0.19 + }, + { + "id": "memory_leaks", + "similarity_score": 0.172 + } + ], + "ranking": [ + { + "commit_hash": "c3629ef", + "confidence_score": 0.9 + }, + { + "commit_hash": "3af442c", + "confidence_score": 0.2 + }, + { + "commit_hash": "976c988", + "confidence_score": 0.05 + }, + { + "commit_hash": "a6a3c41", + "confidence_score": 0.05 + }, + { + "commit_hash": "89436d3", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.12, + "usage": { + "input_tokens": 3480, + "output_tokens": 693 + } + }, + { + "trial": 2, + "case_id": "db_fetch_one_leak", + "category": "db_pool_exhaustion", + "culprit_hash": "af9b861", + "expected_runbook_id": "db_failures", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "db_failures", + "similarity_score": 0.628 + }, + { + "id": "memory_leaks", + "similarity_score": 0.344 + }, + { + "id": "request_timeouts", + "similarity_score": 0.28 + } + ], + "ranking": [ + { + "commit_hash": "af9b861", + "confidence_score": 0.95 + }, + { + "commit_hash": "f46cd8c", + "confidence_score": 0.45 + }, + { + "commit_hash": "1618815", + "confidence_score": 0.05 + }, + { + "commit_hash": "6d50d20", + "confidence_score": 0.02 + }, + { + "commit_hash": "1bf1024", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.5, + "usage": { + "input_tokens": 3516, + "output_tokens": 684 + } + }, + { + "trial": 2, + "case_id": "export_tempfile_never_closed", + "category": "file_handle_leak", + "culprit_hash": "6eea0fc", + "expected_runbook_id": "file_handle_exhaustion", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "file_handle_exhaustion", + "similarity_score": 0.625 + }, + { + "id": "memory_leaks", + "similarity_score": 0.411 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.291 + } + ], + "ranking": [ + { + "commit_hash": "5cedb21", + "confidence_score": 0.95 + }, + { + "commit_hash": "6eea0fc", + "confidence_score": 0.3 + }, + { + "commit_hash": "b245524", + "confidence_score": 0.05 + }, + { + "commit_hash": "0be651d", + "confidence_score": 0.02 + }, + { + "commit_hash": "7453733", + "confidence_score": 0.0 + } + ], + "culprit_rank": 2, + "error": null, + "rank_latency_s": 6.98, + "usage": { + "input_tokens": 3722, + "output_tokens": 765 + } + }, + { + "trial": 2, + "case_id": "guest_email_normalise", + "category": "null_or_missing_field", + "culprit_hash": "7dd25fe", + "expected_runbook_id": "null_field_handling", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "null_field_handling", + "similarity_score": 0.241 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.208 + }, + { + "id": "pagination_errors", + "similarity_score": 0.161 + } + ], + "ranking": [ + { + "commit_hash": "7dd25fe", + "confidence_score": 0.98 + }, + { + "commit_hash": "9519064", + "confidence_score": 0.1 + }, + { + "commit_hash": "30a7434", + "confidence_score": 0.0 + }, + { + "commit_hash": "1c0135b", + "confidence_score": 0.0 + }, + { + "commit_hash": "cde392a", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.77, + "usage": { + "input_tokens": 3135, + "output_tokens": 609 + } + }, + { + "trial": 2, + "case_id": "httpx_028_drops_proxies", + "category": "dependency_version", + "culprit_hash": "b35322f", + "expected_runbook_id": "dependency_regressions", + "commits_in_window": 4, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "auth_failures", + "similarity_score": 0.294 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.285 + }, + { + "id": "memory_leaks", + "similarity_score": 0.244 + } + ], + "ranking": [ + { + "commit_hash": "b35322f", + "confidence_score": 0.95 + }, + { + "commit_hash": "741f699", + "confidence_score": 0.85 + }, + { + "commit_hash": "b45189f", + "confidence_score": 0.03 + }, + { + "commit_hash": "e3601f6", + "confidence_score": 0.02 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.44, + "usage": { + "input_tokens": 2993, + "output_tokens": 600 + } + }, + { + "trial": 2, + "case_id": "inventory_cache_key_per_request", + "category": "rate_limit_429", + "culprit_hash": "526f8dd", + "expected_runbook_id": "rate_limiting", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "rate_limiting", + "similarity_score": 0.382 + }, + { + "id": "auth_failures", + "similarity_score": 0.381 + }, + { + "id": "null_field_handling", + "similarity_score": 0.309 + } + ], + "ranking": [ + { + "commit_hash": "526f8dd", + "confidence_score": 0.95 + }, + { + "commit_hash": "bf4cc5f", + "confidence_score": 0.3 + }, + { + "commit_hash": "93320f2", + "confidence_score": 0.05 + }, + { + "commit_hash": "2e1b06b", + "confidence_score": 0.02 + }, + { + "commit_hash": "d9caa65", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 7.17, + "usage": { + "input_tokens": 3709, + "output_tokens": 806 + } + }, + { + "trial": 2, + "case_id": "inventory_v3_payload", + "category": "null_or_missing_field", + "culprit_hash": "1c29d94", + "expected_runbook_id": "null_field_handling", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "config_parse_errors", + "similarity_score": 0.33 + }, + { + "id": "file_handle_exhaustion", + "similarity_score": 0.28 + }, + { + "id": "memory_leaks", + "similarity_score": 0.271 + } + ], + "ranking": [ + { + "commit_hash": "1c29d94", + "confidence_score": 0.93 + }, + { + "commit_hash": "16d2191", + "confidence_score": 0.07 + }, + { + "commit_hash": "c6f0797", + "confidence_score": 0.03 + }, + { + "commit_hash": "5823d1f", + "confidence_score": 0.02 + }, + { + "commit_hash": "fb9b710", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.84, + "usage": { + "input_tokens": 3556, + "output_tokens": 729 + } + }, + { + "trial": 2, + "case_id": "jwt_leeway_milliseconds", + "category": "auth_rejection", + "culprit_hash": "2eeb222", + "expected_runbook_id": "auth_failures", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "auth_failures", + "similarity_score": 0.488 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.198 + }, + { + "id": "request_timeouts", + "similarity_score": 0.159 + } + ], + "ranking": [ + { + "commit_hash": "2eeb222", + "confidence_score": 0.95 + }, + { + "commit_hash": "e8548c5", + "confidence_score": 0.2 + }, + { + "commit_hash": "2e11931", + "confidence_score": 0.05 + }, + { + "commit_hash": "a028c62", + "confidence_score": 0.02 + }, + { + "commit_hash": "716e22d", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.75, + "usage": { + "input_tokens": 3526, + "output_tokens": 630 + } + }, + { + "trial": 2, + "case_id": "money_returns_decimal", + "category": "json_serialization", + "culprit_hash": "b14897b", + "expected_runbook_id": null, + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "config_parse_errors", + "similarity_score": 0.298 + }, + { + "id": "null_field_handling", + "similarity_score": 0.151 + }, + { + "id": "auth_failures", + "similarity_score": 0.13 + } + ], + "ranking": [ + { + "commit_hash": "b14897b", + "confidence_score": 0.98 + }, + { + "commit_hash": "dedc41a", + "confidence_score": 0.05 + }, + { + "commit_hash": "626c22d", + "confidence_score": 0.02 + }, + { + "commit_hash": "cd4a49f", + "confidence_score": 0.01 + }, + { + "commit_hash": "92cb3da", + "confidence_score": 0.01 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.27, + "usage": { + "input_tokens": 3313, + "output_tokens": 683 + } + }, + { + "trial": 2, + "case_id": "order_read_sync_audit", + "category": "latency_timeout", + "culprit_hash": "8aed86c", + "expected_runbook_id": "request_timeouts", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": false, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "request_timeouts", + "similarity_score": 0.539 + }, + { + "id": "rate_limiting", + "similarity_score": 0.369 + }, + { + "id": "auth_failures", + "similarity_score": 0.35 + } + ], + "ranking": [ + { + "commit_hash": "8aed86c", + "confidence_score": 0.85 + }, + { + "commit_hash": "d3d6f87", + "confidence_score": 0.35 + }, + { + "commit_hash": "94578cc", + "confidence_score": 0.3 + }, + { + "commit_hash": "5abe2e4", + "confidence_score": 0.02 + }, + { + "commit_hash": "b7ea08d", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 7.3, + "usage": { + "input_tokens": 3260, + "output_tokens": 780 + } + }, + { + "trial": 2, + "case_id": "order_sql_missing_param", + "category": "sql_param_mismatch", + "culprit_hash": "9ef79b2", + "expected_runbook_id": null, + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "pagination_errors", + "similarity_score": 0.322 + }, + { + "id": "file_handle_exhaustion", + "similarity_score": 0.23 + }, + { + "id": "memory_leaks", + "similarity_score": 0.188 + } + ], + "ranking": [ + { + "commit_hash": "9ef79b2", + "confidence_score": 0.95 + }, + { + "commit_hash": "661f871", + "confidence_score": 0.1 + }, + { + "commit_hash": "66d5f2f", + "confidence_score": 0.05 + }, + { + "commit_hash": "93ebe2f", + "confidence_score": 0.02 + }, + { + "commit_hash": "411ddfa", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.97, + "usage": { + "input_tokens": 3452, + "output_tokens": 632 + } + }, + { + "trial": 2, + "case_id": "page_size_zero_division", + "category": "pagination_boundary", + "culprit_hash": "6265f24", + "expected_runbook_id": "pagination_errors", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "config_parse_errors", + "similarity_score": 0.146 + }, + { + "id": "memory_leaks", + "similarity_score": 0.13 + }, + { + "id": "pagination_errors", + "similarity_score": 0.127 + } + ], + "ranking": [ + { + "commit_hash": "6265f24", + "confidence_score": 0.95 + }, + { + "commit_hash": "ed3861b", + "confidence_score": 0.55 + }, + { + "commit_hash": "0d241ff", + "confidence_score": 0.05 + }, + { + "commit_hash": "ef48acf", + "confidence_score": 0.02 + }, + { + "commit_hash": "debe268", + "confidence_score": 0.01 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.56, + "usage": { + "input_tokens": 3189, + "output_tokens": 620 + } + }, + { + "trial": 2, + "case_id": "payments_body_without_content_type", + "category": "http_content_type", + "culprit_hash": "33a9e2a", + "expected_runbook_id": null, + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "auth_failures", + "similarity_score": 0.274 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.164 + }, + { + "id": "null_field_handling", + "similarity_score": 0.143 + } + ], + "ranking": [ + { + "commit_hash": "33a9e2a", + "confidence_score": 0.97 + }, + { + "commit_hash": "e60edf2", + "confidence_score": 0.05 + }, + { + "commit_hash": "5430f37", + "confidence_score": 0.02 + }, + { + "commit_hash": "91f3040", + "confidence_score": 0.01 + }, + { + "commit_hash": "64ee5a8", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.19, + "usage": { + "input_tokens": 3771, + "output_tokens": 568 + } + }, + { + "trial": 2, + "case_id": "payments_retry_loop", + "category": "rate_limit_429", + "culprit_hash": "f07e927", + "expected_runbook_id": "rate_limiting", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "rate_limiting", + "similarity_score": 0.357 + }, + { + "id": "auth_failures", + "similarity_score": 0.352 + }, + { + "id": "request_timeouts", + "similarity_score": 0.323 + } + ], + "ranking": [ + { + "commit_hash": "f07e927", + "confidence_score": 0.85 + }, + { + "commit_hash": "07a85f8", + "confidence_score": 0.8 + }, + { + "commit_hash": "c1fc55f", + "confidence_score": 0.05 + }, + { + "commit_hash": "429c4dd", + "confidence_score": 0.0 + }, + { + "commit_hash": "51bc043", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 7.3, + "usage": { + "input_tokens": 3682, + "output_tokens": 725 + } + }, + { + "trial": 2, + "case_id": "redis_legacy_lrem_args", + "category": "dependency_version", + "culprit_hash": "0751831", + "expected_runbook_id": "dependency_regressions", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "null_field_handling", + "similarity_score": 0.324 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.317 + }, + { + "id": "auth_failures", + "similarity_score": 0.269 + } + ], + "ranking": [ + { + "commit_hash": "0751831", + "confidence_score": 0.85 + }, + { + "commit_hash": "fd92e84", + "confidence_score": 0.55 + }, + { + "commit_hash": "3059910", + "confidence_score": 0.05 + }, + { + "commit_hash": "7b9dd2c", + "confidence_score": 0.02 + }, + { + "commit_hash": "957a42e", + "confidence_score": 0.01 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.42, + "usage": { + "input_tokens": 3032, + "output_tokens": 701 + } + }, + { + "trial": 2, + "case_id": "settings_env_quotes", + "category": "config_type_error", + "culprit_hash": "15aba07", + "expected_runbook_id": "config_parse_errors", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "db_failures", + "similarity_score": 0.374 + }, + { + "id": "config_parse_errors", + "similarity_score": 0.278 + }, + { + "id": "memory_leaks", + "similarity_score": 0.233 + } + ], + "ranking": [ + { + "commit_hash": "15aba07", + "confidence_score": 0.9 + }, + { + "commit_hash": "cae9a6c", + "confidence_score": 0.05 + }, + { + "commit_hash": "423c51f", + "confidence_score": 0.05 + }, + { + "commit_hash": "3e36aa4", + "confidence_score": 0.02 + }, + { + "commit_hash": "55cfa0d", + "confidence_score": 0.01 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.2, + "usage": { + "input_tokens": 3391, + "output_tokens": 703 + } + }, + { + "trial": 2, + "case_id": "since_filter_naive_cutoff", + "category": "timezone_mismatch", + "culprit_hash": "266c09c", + "expected_runbook_id": null, + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": true, + "culprit_in_traceback": true, + "retrieved_runbooks": [ + { + "id": "pagination_errors", + "similarity_score": 0.279 + }, + { + "id": "dependency_regressions", + "similarity_score": 0.224 + }, + { + "id": "memory_leaks", + "similarity_score": 0.21 + } + ], + "ranking": [ + { + "commit_hash": "266c09c", + "confidence_score": 0.95 + }, + { + "commit_hash": "f3ad8b7", + "confidence_score": 0.3 + }, + { + "commit_hash": "7f71cc9", + "confidence_score": 0.05 + }, + { + "commit_hash": "ef6b4a1", + "confidence_score": 0.02 + }, + { + "commit_hash": "5948e6d", + "confidence_score": 0.02 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.69, + "usage": { + "input_tokens": 3219, + "output_tokens": 679 + } + }, + { + "trial": 2, + "case_id": "worker_queue_renamed", + "category": "queue_stall", + "culprit_hash": "f8d8258", + "expected_runbook_id": null, + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": false, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "request_timeouts", + "similarity_score": 0.411 + }, + { + "id": "rate_limiting", + "similarity_score": 0.409 + }, + { + "id": "file_handle_exhaustion", + "similarity_score": 0.383 + } + ], + "ranking": [ + { + "commit_hash": "f8d8258", + "confidence_score": 0.9 + }, + { + "commit_hash": "0f497ec", + "confidence_score": 0.2 + }, + { + "commit_hash": "110eec6", + "confidence_score": 0.05 + }, + { + "commit_hash": "aacbcd7", + "confidence_score": 0.02 + }, + { + "commit_hash": "3434d6b", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 6.28, + "usage": { + "input_tokens": 2774, + "output_tokens": 728 + } + }, + { + "trial": 2, + "case_id": "worker_retains_job_payloads", + "category": "memory_growth", + "culprit_hash": "3037583", + "expected_runbook_id": "memory_leaks", + "commits_in_window": 5, + "truncated_diffs": 0, + "has_traceback": false, + "culprit_in_traceback": false, + "retrieved_runbooks": [ + { + "id": "file_handle_exhaustion", + "similarity_score": 0.48 + }, + { + "id": "memory_leaks", + "similarity_score": 0.434 + }, + { + "id": "rate_limiting", + "similarity_score": 0.424 + } + ], + "ranking": [ + { + "commit_hash": "3037583", + "confidence_score": 0.95 + }, + { + "commit_hash": "088e964", + "confidence_score": 0.15 + }, + { + "commit_hash": "947ac52", + "confidence_score": 0.1 + }, + { + "commit_hash": "15f34e0", + "confidence_score": 0.02 + }, + { + "commit_hash": "63c2633", + "confidence_score": 0.0 + } + ], + "culprit_rank": 1, + "error": null, + "rank_latency_s": 5.53, + "usage": { + "input_tokens": 3095, + "output_tokens": 597 + } + } + ], + "finished_at": "2026-10-11T19:49:07.030635+00:00" +} \ No newline at end of file