fix(hermes): stop telling the patcher a build failed when none did
All checks were successful
Tests / Declarative: Post Actions passed: 1355

The patch prompt was written for one entry point and inherited whole by the
other. Rendered for a quality sweep it opened with "Use
$triage-titan-test-failures", called the findings "defects the build
reported", and labelled the payload "Failing test evidence bundle" - three
false statements in a row, on a green build, with an empty jenkins section
sitting right below them. The instruction two lines later already said the
build was not failing, so the prompt contradicted itself.

Wrong in the first line the model reads is the expensive kind of wrong: it
frames everything after it. The skill mention is the concrete cost - it sends
Hermes to investigate a build that did not fail, spending a tool call to
explain a failure that does not exist, when the finding and the file are
already inlined below it.

The framing now follows the bundle's origin. A bundle carrying both static
analysis and build evidence is treated as build-driven, because a real failure
is the more urgent framing of the two.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
codex 2026-08-06 22:16:36 -03:00
parent 63a8abb743
commit ca545fa75f
3 changed files with 109 additions and 5 deletions

View File

@ -342,7 +342,16 @@ def defect_instruction(defects: list[dict[str, Any]]) -> str:
if not defects:
return ""
lines: list[str] = ["The build reported these specific defects; address exactly these:"]
# A static-analysis sweep runs against a green build. Saying "the build
# reported" there would be a plain falsehood in the first line the model
# reads, and the instruction two lines later already contradicts it.
from_build = any(defect["category"] != SONARQUBE_ISSUE for defect in defects)
lead = (
"The build reported these specific defects; address exactly these:"
if from_build
else "Static analysis reported these findings; address exactly these:"
)
lines: list[str] = [lead]
for defect in defects:
lines.append(_defect_line(defect))
if any(d["category"] == LINT_VIOLATION for d in defects):

View File

@ -12,7 +12,7 @@ import json
from . import hermes_code_defects
_PATCH_PROMPT_TEMPLATE = """Use $triage-titan-test-failures.
_PATCH_PROMPT_TEMPLATE = """__PREAMBLE__
You are proposing a MINIMAL source fix for incident __INCIDENT_ID__.
The repository is __OWNER__/__REPO__ branch __BASE_BRANCH__.
Return ONLY a single JSON object with exactly these keys and no others:
@ -24,18 +24,55 @@ Change as few lines as possible; do not reformat; do not add dependencies.
Ariadne validates and pushes the change you do not execute anything.
Set human_required to true if the fix is not a small localized source change.
__DEFECT_INSTRUCTION__
Failing test evidence bundle:
__BUNDLE_LABEL__
__BUNDLE__
__FILE_SECTIONS__"""
_FILE_SECTION_TEMPLATE = "Current content of the candidate file {path}:\n{contents}"
# A build-driven repair starts from a failure worth investigating, so the
# triage skill earns its cost. A quality sweep does not: no build failed, there
# is no console to read, and the file is already inlined below. Sending Hermes
# to investigate a green build would waste a tool call and invite it to explain
# a failure that does not exist.
_BUILD_PREAMBLE = "Use $triage-titan-test-failures."
_SWEEP_PREAMBLE = (
"A static-analysis finding, not a build failure. Nothing is broken and no "
"investigation is needed: the finding and the file are below."
)
_BUILD_BUNDLE_LABEL = "Failing test evidence bundle:"
_SWEEP_BUNDLE_LABEL = "Static analysis evidence bundle:"
def is_quality_sweep(bundle: dict) -> bool:
"""Report whether a bundle came from a quality sweep rather than a build.
Inputs: the evidence bundle. Outputs: True when it carries SonarQube
findings and no build evidence. A bundle with both is treated as
build-driven, since a real failure is the more urgent framing.
"""
if not isinstance(bundle, dict):
return False
sonar = bundle.get("sonarqube")
if not isinstance(sonar, dict) or not sonar.get("issues"):
return False
jenkins = bundle.get("jenkins") if isinstance(bundle.get("jenkins"), dict) else {}
return not (
jenkins.get("console_failures") or jenkins.get("console_tail") or jenkins.get("failed_tests")
)
def build_patch_prompt(
incident_id: str, bundle: dict, code_cfg: dict, fetched: dict[str, str]
) -> str:
"""Render the frozen patch prompt with incident, bundle, and file context."""
"""Render the frozen patch prompt with incident, bundle, and file context.
The framing follows the bundle's origin. A prompt that opens by telling the
model it is looking at a failing test, when the build is green and the
evidence is a static-analysis finding, is wrong in the first line it reads.
"""
compact = json.dumps(bundle, separators=(",", ":"), ensure_ascii=True)
listing = "\n".join(f"- {path}" for path in fetched)
@ -46,8 +83,13 @@ def build_patch_prompt(
_FILE_SECTION_TEMPLATE.format(path=path, contents=contents)
for path, contents in fetched.items()
)
sweep = is_quality_sweep(bundle)
return (
_PATCH_PROMPT_TEMPLATE.replace("__INCIDENT_ID__", incident_id)
_PATCH_PROMPT_TEMPLATE.replace(
"__PREAMBLE__", _SWEEP_PREAMBLE if sweep else _BUILD_PREAMBLE
)
.replace("__BUNDLE_LABEL__", _SWEEP_BUNDLE_LABEL if sweep else _BUILD_BUNDLE_LABEL)
.replace("__INCIDENT_ID__", incident_id)
.replace("__OWNER__", str(code_cfg.get("owner") or ""))
.replace("__REPO__", str(code_cfg.get("repo") or ""))
.replace("__BASE_BRANCH__", str(code_cfg.get("base_branch") or ""))

View File

@ -307,3 +307,56 @@ def test_the_scheduled_entry_point_runs_the_sweep_when_enabled(monkeypatch) -> N
"api_key": "k",
"total_timeout_seconds": 420.0,
}
def test_the_sweep_prompt_never_claims_a_build_failed() -> None:
"""A green build framed as a failing test is wrong in the first line read."""
from ariadne.services import hermes_code_prompt
cfg = {"owner": "bstein", "repo": "ariadne", "base_branch": "master"}
prompt = hermes_code_prompt.build_patch_prompt(
"sonar/ariadne/AZ-1",
module.bundle_for("ariadne", _issue()),
{**cfg, "allowed_path_prefixes": ["ariadne/"], "allowed_suffixes": [".py"]},
{"ariadne/services/hermes_code_defects.py": "x"},
)
assert prompt.startswith("A static-analysis finding, not a build failure.")
assert "Failing test evidence bundle" not in prompt
assert "Static analysis evidence bundle:" in prompt
assert "The build reported these specific defects" not in prompt
# The triage skill investigates a failed build; there isn't one.
assert "$triage-titan-test-failures" not in prompt
def test_a_build_driven_prompt_is_unchanged() -> None:
from ariadne.services import hermes_code_prompt
bundle = {"jenkins": {"console_tail": "AssertionError", "console_failures": [], "failed_tests": []}}
prompt = hermes_code_prompt.build_patch_prompt(
"ariadne/9", bundle, {"owner": "bstein", "repo": "ariadne"}, {"ariadne/a.py": "x"}
)
assert prompt.startswith("Use $triage-titan-test-failures.")
assert "Failing test evidence bundle:" in prompt
@pytest.mark.parametrize(
("bundle", "expected"),
[
({"sonarqube": {"issues": [{"path": "a.py"}]}, "jenkins": {}}, True),
({"sonarqube": {"issues": []}, "jenkins": {}}, False),
({"jenkins": {"console_tail": "boom"}}, False),
# Both present: a real failure is the more urgent framing.
(
{"sonarqube": {"issues": [{"path": "a.py"}]}, "jenkins": {"console_tail": "boom"}},
False,
),
("not-a-dict", False),
],
)
def test_a_bundles_origin_decides_the_framing(bundle, expected) -> None:
from ariadne.services import hermes_code_prompt
assert hermes_code_prompt.is_quality_sweep(bundle) is expected