The sweep opens a pull request only when everything lines up: mapped
repository, file inside the write allowlist, and a change expressible as one
anchored snippet. Most of this instance's backlog fails the last of those -
the bulk of it is cognitive-complexity refactors - so those findings produced
nothing at all. That is backwards. A finding nobody can patch automatically is
precisely the one a maintainer has to do by hand, which is when knowing the
intended fix is worth the most.
Such findings now become an issue carrying the finding, links to both the
SonarQube entry and the Hermes run, and the code Hermes believes would resolve
it. A declined pull request falls through to the same path rather than ending
the attempt.
Not a patch, and the issue says so: nothing here is anchored, validated
against the file, or pushed. That is what lets a suggestion describe work too
large or too diffuse for the patcher, which is the whole point of the path.
Deduped on the rule through the existing issue marker, so one root cause
yields one issue however many files it spans. Off by default: it writes to
real repositories, so an operator turns it on deliberately.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One SonarQube rule is usually one root cause spread across many files. S2208
appears in three Ariadne modules and the cognitive-complexity rule in dozens,
and a sweep with no memory of what it already proposed would open a
near-identical pull request for every instance. Thirty of those get read as
none, which costs more than proposing nothing.
The sweep now skips any rule that already has an open proposal for that
project. The rules under review are read back from the open pull requests'
own titles rather than from a stored index: the pull requests are the thing
that actually exists, an index could disagree with them, and disagreeing is
the one failure mode that matters here. Once the open one is dealt with, the
next instance of that rule becomes eligible again.
This is not the root-cause collapse - it does not make one pull request fix
every instance of a rule, it just stops proposing the same rule repeatedly.
The collapse needs multi-file patch sets, which the frozen patch contract
cannot express yet.
Fails open like every other duplicate check here: an unreadable list yields no
known rules, so a lookup failure costs one extra proposal rather than
silently dropping a whole rule.
Issues now link their run id into the Hermes console, matching what pull
requests already do. Both artifacts claim a model made the call; both should
let a reader open the page where that call is visible.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Triage has only ever entered on a failure. Static analysis is the opposite
shape - a standing backlog that never fails a build and so never asks anyone
for attention. On this instance that backlog is 139 open findings on Ariadne
alone, each already naming its file, its line, its rule and what is wrong.
That is better-located evidence than the console text the code-repair flow
normally mines, and it was being thrown away.
This is a second way into the same flow, not a second flow. A scheduled sweep
picks one finding and hands it to the existing proposal path, which is
unchanged: Hermes returns a patch as data, Ariadne validates it against the
file it names, pushes a branch, opens a pull request nobody merges. A finding
arriving from outside the build is not a reason to relax the gates that make a
proposal worth reading, so it does not.
Three deliberate limits. Security hotspots are never fetched: SonarQube models
them as needing human review, the quality gate here fails on exactly that
condition, and an automation that resolved them would be marking them
reviewed without review - defeating the control rather than satisfying it.
Findings already marked won't-fix carry a judgement someone made, and
reopening it produces pull requests that argue with a person. And the sweep
proposes one fix per run by default, because 139 pull requests nobody reads
would make the review gate theatre.
Selection is by SonarQube's own effort estimate rather than severity: effort
is the closest available proxy for the one-anchor change the patch validator
can actually check, so a trivial CRITICAL beats an involved MINOR. An
unparseable estimate is treated as ineligible, not as free.
Off by default. Triage reacts to a failure someone already cares about; this
opens pull requests nobody asked for, and that is a decision an operator makes
deliberately rather than inherits on upgrade.
Branch naming now sanitizes its token, since it arrives from a finding key as
well as a build number and a ref is one of the few places where an unexpected
character stops being cosmetic.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>