patch
Generate candidate fixes for verified security findings. Consumes TRIAGE.json (preferred), VULN-FINDINGS.json, INCIDENTS.json, or a vuln-pipeline results directory. Pipeline input is delegated to the execution-verified `vuln-pipeline patch` ladder; static-analysis input gets a per-finding patch suba
By anthropics · 978 installs
npx skills add anthropics/defending-code-reference-harness --skill patch
Source repository · Upstream listing
patch
Third leg of the static pipeline ( /vuln scan → /triage → /patch ).
Turns a ranked list of verified findings into candidate diffs.
The skill never applies a diff to the target repo. Output is inert text
in ./PATCHES/ for a human to review and apply out of band — see
docs/patching.md reviewing generated patches . There is no apply or
approve flag by design: the capability isn't present, so it can't be
prompt injected into use.
Invoke with /patch <findings path [ repo PATH] [ top N] [ id fNNN]
[ model M] [ fresh] .
Arguments (parse from $ARGUMENTS ):
findings path (first positional, required): TRIAGE.json ,
VULN FINDINGS.json , a pipeline results/<target /<ts / directory, or any
JSON the /triage ingest table recognizes.
repo PATH : target codebase, read only (default cwd). Required for
static mode; the skill stops if cited files don't resolve under it.
top N : patch only the N highest severity true positives (static mode).
id fNNN : patch only the finding with this id.
model M : passed through to vuln pipeline patch in execution verified
mode. Ignored in static mode (subagents inherit the orchestrator's model).
fresh : ignore ./.patch state/ checkpoint and start over.
Tools. Prefer Read, Glob, Grep, Write, Task. Some sessions do not
provision Glob or Grep; allowed tools is a permission filter, not a loader.
When they are unavailable, fall back to the read only Bash commands
whitelisted above: rg / grep for search, ls for enumeration,
head / file / wc for sniffing, jq for JSON ingest. Bash is otherwise
permitted only for python3 .claude/skills/ lib/checkpoint.py (state I/O)
and vuln pipeline patch (execution verified delegate). find is NOT
permitted.
Write scope. The Write tool may target ONLY paths under ./PATCHES/ and
./.patch state/ . Never write into repo , never git apply , never
patch , never edit target source. If a step seems to require it, the step is
wrong.
Checkpointing (runs before Phase 0 and after every phase)
State persists to ./.patch state/ so a fresh /patch session resumes
without re spawning patch or reviewer subagents. All checkpoint I/O goes
through python3 .claude/skills/ lib/checkpoint.py (atomic, JSON validated).
The Write→ from pattern keeps repo derived bytes out of Bash argv; never
pass payload via heredoc or stdin.
State files: progress.json (single source of truth: {"status":
"running" "complete", "phase done": N, "shards done": [...]} ),
phaseN.json , chunk.tmp .
Start of run. Bash:
python3 .claude/skills/ lib/checkpoint.py load ./.patch state
status == "absent" OR "complete" , OR fresh in $ARGUMENTS →
fresh start. Bash:
python3 .claude/skills/ lib/checkpoint.py reset ./.patch state ,
proceed to Phase 0.
status == "running" with phase done == N → resume. Read
phase0.json .. phaseN.json in order (and any shard .json listed in
shards done ), merge into working state, print
Resuming from checkpoint: Phase N complete , skip to Phase N+1. Do not
re spawn any subagent whose output is already checkpointed.
End of every phase N. Write tool → ./.patch state/ chunk.tmp with the
phase's JSON, then Bash:
python3 .claude/skills/ lib/checkpoint.py save ./.patch state <N <name from ./.patch state/ chunk.tmp
End of run. After writing PATCHES.md and PATCHES.json , Bash:
python3 .claude/skills/ lib/checkpoint.py done ./.patch state 4
Phase 0: Parse arguments and detect mode
0a. Parse $ARGUMENTS
Extract findings path (first positional), repo (default . ), top ,
id , model , fresh . If no findings path, stop and ask.
0b. Detect mode
Inspect the findings path:
execution verified mode when the path is a directory containing
reports/manifest.jsonl OR found bugs.jsonl OR run /result.json
(pipeline output) — unless an INCIDENTS.json is present anywhere in it:
D&R run dirs also contain run /result.json , and their incidents route
static. The findings have PoC bytes + ASAN traces + reproduction
commands; the pipeline's verification ladder applies.
static mode otherwise: TRIAGE.json , VULN FINDINGS.json ,
INCIDENTS.json (the /dnr output), generic finding JSON, or markdown.
No PoC; the oracle is a fresh context reviewer.
Record mode in working state. The two modes share Phase 1 ingest then fork
at Phase 2.
Checkpoint: Write tool → ./.patch state/ chunk.tmp :
{"phase": 0, "mode": "exec" "static", "args": {repo, top, id, model, findings path}}
Then Bash:
python3 .claude/skills/ lib/checkpoint.py save ./.patch state 0 mode from ./.patch state/ chunk.tmp
Phase 1: Ingest and normalize
Same input contract as /triage Phase 1. Normalize every input format to a
flat findings[] of dicts. Pull what's present; never guess what's absent.
1a. Recognized containers (priority order)
1. TRIAGE.json — read .findings[] . Filter to verdict ==
"true positive" . This is the canonical input: already verified,
deduped, ranked, owner tagged.
2. VULN FINDINGS.json — read .findings[] . Unverified; print
Warning: VULN FINDINGS.json is unverified scanner output. Consider
/triage first. and continue.
3. INCIDENTS.json (the /dnr hunt / /dnr respond output) — same
mapping as /triage 1a: one finding per .incidents[] entry, nested
vuln.file / vuln.line / vuln.type → file / line / category ,
description ← vuln.mechanism plus impact , and keep poc — a
confirmed exploited incident's poc.command is the regression check
for its fix. The top level ruled out array is not findings. Print
Warning: INCIDENTS.json has not been triaged. Consider /triage first.
and continue. No file incidents follow 1c like any other finding.
4. Pipeline results directory — one finding per reports/bug NN/ .
Map report.json → description , crash.crash type → category ,
ASAN top frame → file / line . Record bug id = NN for the
bug N delegate flag.
5. Generic .json with a top level list or a findings / results /
issues / vulnerabilities array.
1b. Field aliases (canonical ← also accept)
Canonical Also accept
file path , location.file , filename
line line number , location.line , lineno
category type , cwe , rule id , crash type
severity severity rating , level , priority
title name , summary , message
description details , report , body , evidence , rationale
recommendation fix , remediation , mitigation
owner hint owner , component
Attach id ( f001 , f002 , ... in ingest order; preserve existing ids from
TRIAGE.json) and source (relative path of the file it came from).
1c. Filter and order
If id fNNN : keep only that finding.
If top N (static mode): sort by severity HIGH MEDIUM LOW then
confidence desc, keep the first N.
Drop findings with no file (cannot patch what cannot be located). Record
them as skipped with reason "no source location" .
1d. Locate the target codebase (static mode)
Resolve repo . For the first 5 findings with a file , check the path
resolves under repo (try as given, then with common prefixes stripped). If
none resolve, stop : tell the user the cited files aren't reachable and
suggest a repo value.
Checkpoint: Write tool → ./.patch state/ chunk.tmp :
{"phase": 1, "mode": ..., "findings": [...], "skipped": [...], "repo": ...}
Then Bash:
python3 .claude/skills/ lib/checkpoint.py save ./.patch state 1 ingest from ./.patch state/ chunk.tmp
Phase 2: Generate patches
Forks on mode .
2A. Execution verified mode — delegate to the pipeline
The pipeline already implements the build → reproduce → regress → re attack
ladder with executable oracles. Do not reimplement it.
For each finding (or once for the whole directory if no id / top
filter), Bash:
The pipeline writes <findings path /reports/bug NN/{patch.diff,
patch result.json} itself. After it returns, Read each patch result.json
and copy verdict + rationale into working state. Set
verified: "ladder passed" when verdict.passed == true , else
verified: "ladder failed" .
If the CLI exits non zero (no build command , missing target config), record
the stderr as the finding's error and continue with remaining findings.
Skip Phase 3 (the ladder is the verifier). Proceed to Phase 4.
Checkpoint per finding: Write tool → ./.patch state/ chunk.tmp =
{"id": ..., "verified": ..., "verdict": ..., "diff path": ...} , then Bash:
python3 .claude/skills/ lib/checkpoint.py shard ./.patch state <id from ./.patch state/ chunk.tmp .
After all findings, write the consolidated phase payload to chunk.tmp then:
python3 .claude/skills/ lib/checkpoint.py save ./.patch state 2 generate from ./.patch state/ chunk.tmp
2B. Static mode — one patch subagent per finding
One Task per finding, all in a SINGLE assistant message for parallel
execution. subagent type: "general purpose" . Never set
run in background — you need the diff text, not an async handle.
Each subagent has read only access to repo . It cannot modify the target;
it emits the diff as text in its response. The orchestrator writes that text
to PATCHES/bug NN/patch.diff .
Patch subagent prompt (assemble once, reuse per finding)
fences.
<patch diff
a/path/to/file
+++ b/path/to/file
@@ ... @@
context line
removed line
+added line
</patch diff
<rationale what changed and why, mechanically — file:line of root cause,
what the change enforces</rationale
<variants checked file:function pairs you grepped for the same
pattern, and whether each needed the fix</variants checked
<bypass considered the input variation you tried in step 5 and why it
no longer reaches the bad state</bypass considered
<test note where the regression test landed, or why none was
added</test note
If you determine the finding is NOT fixable as described (wrong file, code
already patched, finding is a false positive), emit:
<patch diff NONE</patch diff
<rationale why no patch is appropriate</rationale
fences, and HTML escaped entities ( <
> & — some runtimes escape angle brackets in notification
payloads; unescape before writing the diff). If <patch diff is NONE or
empty,
mark status: "no patch" . Otherwise write the diff text to
./PATCHES/bug NN/patch.diff (NN = zero padded index in sorted order) and
record rationale , variants checked , bypass considered , test note .
Checkpoint per finding: Write tool → ./.patch state/ chunk.tmp =
{"id": ..., "bug nn": "NN", "status": ..., "rationale": ..., ...} , then Bash:
python3 .claude/skills/ lib/checkpoint.py shard ./.patch state <id from ./.patch state/ chunk.tmp .
After all findings, write the consolidated phase payload to chunk.tmp then:
python3 .claude/skills/ lib/checkpoint.py save ./.patch state 2 generate from ./.patch state/ chunk.tmp
Phase 3: Independent review (static mode only)
One reviewer subagent per generated diff, all in ONE message,
subagent type: "general purpose" .
The reviewer never sees the finding's description , recommendation , or
the patch author's rationale . It gets only {file, line, category}
plus the raw diff bytes, and re derives whether the diff is a minimal,
in scope fix by reading the source itself. This keeps any instructions
embedded in finding prose from reaching both the author and the gate.
Reviewer prompt (assemble once, reuse per diff)
Spawn and parse
One Task per finding with status != "no patch" . Parse the trailing block.
Attach review , style score , out of scope h