feat(hermes): file the issue an escalated incident never received
All checks were successful
Tests / Declarative: Post Actions passed: 1196

An incident triaged before issue filing existed, or while its repository was
unmapped, is deduped on every later tick and so never reaches the service's
tracker even though the job is still red. soteria and metis sat in exactly
that state: broken, escalated, and permanently unable to file.

The backfill reuses the classification the original diagnosis recorded, so the
existing dedupe applies unchanged - one open issue per job and classification,
refiled only if it is closed while the job still fails. Filing under a guessed
label would miss the open issue and duplicate it, which is what the dedupe
exists to prevent, so an incident with no recorded classification is skipped
rather than guessed at.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
codex 2026-08-06 12:30:51 -03:00
parent 281f06dccd
commit c9c205484e
3 changed files with 122 additions and 0 deletions

View File

@ -145,6 +145,7 @@ def _handle_failure( # noqa: PLR0913 - the tick's issue budget travels with the
incident_id = f"{job}/{number}"
existing = incidents.get(incident_id)
if existing is not None and existing.get("status") != "detected":
_backfill_issue(storage, job, number, incident_id, existing, tick_state)
return {"status": "deduped", "incident_id": incident_id}
stale = _awaiting_rebuild_incident(incidents, job, incident_id)
if stale is not None:
@ -153,6 +154,33 @@ def _handle_failure( # noqa: PLR0913 - the tick's issue budget travels with the
return _run_pipeline(storage, incident_id, job, last_build, tick_state)
def _backfill_issue( # noqa: PLR0913 - the tick's issue budget travels with the incident
storage: Any, job: str, number: int, incident_id: str, existing: dict, tick_state: dict[str, Any]
) -> None:
"""File the issue an escalated incident never received.
An incident triaged before issue filing existed, or while its repository
was unmapped, is deduped on every later tick and would otherwise never
reach the service's tracker even though the job is still red.
Reuses the classification the original diagnosis recorded so the existing
dedupe applies: one open issue per job and classification, refiled only if
it is closed while the job still fails. Skipped when no classification was
recorded, because filing under a guessed label would duplicate rather than
match. Additive and never raises.
"""
if existing.get("status") != "human_required":
return
classification = hermes_events.recorded_classification(storage, incident_id)
if not classification:
return
base = {"incident_id": incident_id, "job": job, "build_number": number}
bundle = {"jenkins": {"job": job, "build_number": number, "console_failures": [], "console_tail": ""}}
diagnosis = {**_diagnosis(bundle, None, "issue_backfill", None), "classification": classification}
_file_incident_issue(storage, base, diagnosis, tick_state)
def _awaiting_rebuild_incident(
incidents: dict[str, dict[str, Any]], job: str, exclude_id: str
) -> dict[str, Any] | None:

View File

@ -208,3 +208,29 @@ def _int_value(value: Any) -> int:
return int(value)
except (TypeError, ValueError):
return 0
def recorded_classification(storage: Any, incident_id: str) -> str | None:
"""Return the classification a past diagnosis recorded for an incident.
Inputs: the event storage and an incident id. Outputs: the classification
string, or None when no diagnosis event carries one.
Issue dedupe matches on job and classification, so a later filing must
reuse the label the original diagnosis produced. Filing under a different
one would not match the open issue and would duplicate it, which is
precisely what the dedupe exists to prevent - hence None rather than a
guess when nothing was recorded.
"""
rows = storage.list_events(limit=_EVENT_SCAN_LIMIT, event_type=DIAGNOSIS_EVENT_TYPE)
for row in rows:
detail = event_detail(row) or {}
if str(detail.get("incident_id") or "") != incident_id:
continue
outcome = detail.get("outcome")
if isinstance(outcome, dict):
classification = str(outcome.get("classification") or "").strip()
if classification:
return classification
return None

View File

@ -0,0 +1,68 @@
"""Tests for filing the issue an escalated incident never received."""
from __future__ import annotations
from ariadne.services import hermes_autotriage as module
from tests.hermes_autotriage_harness import (
INCIDENT_ID,
JOB,
_build,
_prepare_code,
_seed_incident,
)
def _diagnosis_event(storage, incident_id: str, classification: str) -> None:
storage.record_event(
module.DIAGNOSIS_EVENT_TYPE,
{"incident_id": incident_id, "job": JOB, "outcome": {"classification": classification}},
)
def test_a_deduped_escalation_files_the_issue_it_never_got(monkeypatch) -> None:
"""The job is still red; the tracker should not stay empty forever."""
env = _prepare_code(monkeypatch, last_build=_build(12, "FAILURE"))
_seed_incident(env.storage, "human_required")
_diagnosis_event(env.storage, INCIDENT_ID, "coverage_quality_gate_failure")
summary = module.run_hermes_autotriage(env.storage)["jobs"][JOB]
assert summary == {"status": "deduped", "incident_id": INCIDENT_ID}
filed = env.calls["issues"]
assert len(filed) == 1
assert filed[0]["classification"] == "coverage_quality_gate_failure"
def test_backfill_reuses_the_recorded_classification(monkeypatch) -> None:
"""A different label would miss the open issue and duplicate it."""
env = _prepare_code(monkeypatch, last_build=_build(12, "FAILURE"))
_seed_incident(env.storage, "human_required")
_diagnosis_event(env.storage, INCIDENT_ID, "pytest_test_failure")
module.run_hermes_autotriage(env.storage)
assert env.calls["issues"][0]["classification"] == "pytest_test_failure"
def test_no_recorded_classification_means_no_backfill(monkeypatch) -> None:
"""Guessing a label would duplicate rather than dedupe."""
env = _prepare_code(monkeypatch, last_build=_build(12, "FAILURE"))
_seed_incident(env.storage, "human_required")
module.run_hermes_autotriage(env.storage)
assert env.calls["issues"] == []
def test_only_human_required_incidents_are_backfilled(monkeypatch) -> None:
"""A resolved or in-flight incident needs no issue."""
for status in ("resolved", "awaiting_rebuild", "repairing"):
env = _prepare_code(monkeypatch, last_build=_build(12, "FAILURE"))
_seed_incident(env.storage, status)
_diagnosis_event(env.storage, INCIDENT_ID, "pytest_test_failure")
module.run_hermes_autotriage(env.storage)
assert env.calls["issues"] == [], status