---
name: perf-leak-review
description: >
Point at performance-defect and memory-leak candidates in a diff — a
read-only LLM reviewer reads the change plus the repository around it and
reports candidates in a closed list of five classes. Report-only: nothing is
executed, nothing is written, no finding is a proven defect.
when_to_use: >
Triggers on "perf review", "leak review", "performance defect", "memory
leak", "성능 결함 점검", "메모리 누수 점검". Do NOT activate on
"프로파일링", "벤치마크", "성능 측정", or any request to measure runtime —
this skill never runs the code it reviews.
allowed-tools:
- Read
- Grep
- Glob
- Bash(git rev-parse *)
- Bash(git ls-files *)
- Bash(git status *)
verified-against-runtime: true
runtime-verified-at: 2026-10-16
runtime-verified-note: "Claude Code Agent(general-purpose) — 7 read-only delegations over the fixture corpus returned schema-valid envelopes, 5/5 planted classes recalled, both clean controls empty, every prepared tree still porcelain-clean; a third of envelopes arrived inside a ```json fence; git 2.50.1 (2026-10-16) — the repo-wide sweep's sizing reads (`git ls-files | wc -l`, `git ls-files -z | xargs -0 cat | wc -l`) and its `rev-parse` / `status --porcelain` snapshot ran on a scratch repo — forcing two-file batches, `xargs wc -l | tail -1` printed one batch's count while the `-0 cat | wc -l` form printed the full 50/60 lines, a path with a space included; the inside-the-repo scratch guard rejected `/sub` (exit 1) and accepted an outside path, with the repo reached through a symlink; the sweep's AskUserQuestion not round-tripped"
---
# perf-leak-review
## Overview
A session writes code and the next thing that reads it is a reviewer looking for
correctness. Performance defects and leaks survive that pass: they are correct
code, they pass the tests, and the cost shows up in production instead. praxis
has no axis that looks for them — [`retrospect`](../retrospect/SKILL.md) grades
workflow friction, and its one "performance" word means a slow *tool call*, not
slow code.
This skill hands a unified diff and the repository around it to a **read-only
LLM reviewer** and asks one question: does this change introduce a candidate in
one of five defect classes? It is **report-only** — it runs none of the
project's code, writes nothing into the repository, and grades every finding
`candidate` rather than defect, the same read-oracle lineage
[`codex-review-wrap`](../codex-review-wrap/SKILL.md) sits in.
**Core principle:** a reader can point at a candidate; only an execution can
prove a defect — so the output says `candidate` and never more.
**References:** [`references/reviewer-brief.md`](references/reviewer-brief.md)
is the delegated reviewer's prompt (Step 3);
[`references/repo-wide-sweep.md`](references/repo-wide-sweep.md) is the
area fan-out for a request with no diff. The skill ends at the candidate list;
proving a candidate by execution is outside it.
## When to Use
- A change touches loops, I/O, handles, subscriptions, caches, or query paths
and you want the leak axis read before it merges.
- You have a diff (any repository, any language) and want its perf/leak
candidates listed with file and line.
- Do **not** use it to measure anything. No profiler, no benchmark, no
before/after comparison — those need execution, which this skill refuses.
Proving or refuting a candidate by running it is separate work that starts
from this skill's report.
- No diff — the user wants the whole repository swept. That changes this
skill's contract (Step 1 requires `--diff`); follow
[`references/repo-wide-sweep.md`](references/repo-wide-sweep.md), which
reuses the reviewer brief per area and reports area sampling, not
exhaustive coverage.
## The closed list
Five classes, and nothing outside them is reported. The list is the spec:
widening it is a separate change to
[`references/reviewer-brief.md`](references/reviewer-brief.md) and
`validate_findings.py` together.
| # | Class |
|----|------------------------------------------------------------|
| C1 | N+1 / remote call inside a loop |
| C2 | Unreleased resource (file, socket, connection, subprocess) |
| C3 | Unbounded global container or cache |
| C4 | Load-everything-then-filter-in-memory |
| C5 | Unreleased listener, callback, or timer |
## Caller-agnostic by contract
The skill takes `--diff --repo ` and returns a finding envelope. It
does not decide **when** a review runs or **where** the findings go — those are
the caller's, and they are deliberately unsettled. So no step here reads a
session transcript, posts a comment, opens an issue, or blocks anything; a
trigger surface added later attaches by passing the two arguments.
## Process
### Step 1: Resolve the two inputs
```bash
git rev-parse --show-toplevel
```
`--diff ` is a unified diff file and `--repo ` is the root the diff
applies to. **`--diff` is required.** Step 3 hands the reviewer a path, and this
skill cannot produce one from `git diff`: that writes to stdout, and the
`allowed-tools` above carry no `Write` and no redirect, deliberately — a skill
whose whole claim is that it does not write to the repository does not get a
file-creating step. A caller with no diff file on hand makes one itself
(`git diff > `) and passes it.
With `--diff` given and `--repo` omitted, the root comes from
`git rev-parse --show-toplevel`. Both paths are read-only for the rest of the
run.
Stop if `--diff` is absent and report
`perf-leak-review: --diff is required — this skill does not create one`.
Stop if the diff is empty: there is nothing to review, and an empty envelope
from an empty diff says nothing about the reviewer.
### Step 2: Check the host can delegate
This step needs the `Agent` tool. On a host that does not provide it — the Codex
host is **UNKNOWN** here, never verified — **stop** and report
`perf-leak-review: this host cannot delegate a read-only reviewer — unverified
here`. Do not review the diff inline instead: the session that wrote the diff is
the one context that cannot read it independently (CLAUDE.md, *the author-exempt
trap*), and an inline fallback would silently trade that away.
### Step 3: Delegate the review
Read [`references/reviewer-brief.md`](references/reviewer-brief.md) and pass it
as the prompt, verbatim, followed by the two resolved paths:
```text
Agent(
subagent_type="general-purpose",
prompt=
+ "\n\n--diff \n--repo \n"
)
```
One delegation per diff. The brief, not this file, is the source of truth for
what the reviewer is told — the class definitions, the read budget, the tool
restriction, and the output schema all live there, so they cannot drift from
what the validator enforces.
The reviewer is told to use Read, Grep, and Glob only. That instruction is the
whole enforcement: `allowed-tools` above is an allow list for *this* skill's
steps and does not bind a subagent, so "it did not write" is a claim the
caller's own after-the-fact check has to settle (see Limitations).
### Step 4: Validate the envelope
Pipe the reviewer's JSON through the validator; it exits non-zero and prints
every violation:
```bash
printf '%s' "$envelope" | "${CLAUDE_SKILL_DIR}/validate_findings.py"
```
A rejected envelope is **not** repaired and re-emitted. Report the violations as
they are: a class outside C1..C5 is a false positive that the count has to see,
and quietly rewriting it into a compliant finding is what the closed list exists
to prevent.
### Step 5: Report
Print the validated envelope, then one summary line:
```text
perf-leak-review: candidate(s) — C1: C2: C3: C4: C5: (candidates, not verified by execution)
```
Zero findings is a result, not a failure — say `0 candidates` plainly. Every
finding carries `file`, `line`, and the quoted `evidence`, so the reader can go
to the line without re-deriving anything.
The report — the validated envelope and the candidate list above — is the
whole output. This skill runs nothing and hands off to nothing; whether a
candidate gets proven by execution is the reader's decision.
## Error Handling
| Error | Recovery |
|------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `--diff` absent, or its path missing or empty | Abort and report it. There is no fallback: this skill cannot create a diff file (Step 1) |
| `--repo` is not a directory | Abort with the path |
| `Agent` tool unavailable on this host | Stop with the unverified-host message (Step 2); never review inline |
| Reviewer wrapped the envelope in a ```` ```json ```` fence, or put prose around it | Strip the fence and extract the single JSON object; if there is not exactly one, report the raw output and stop. Observed on a third of live envelopes, so treat it as normal rather than as a failure |
| Validator exits 1 | Report its violation lines verbatim; do not repair the envelope |
| Reviewer reports a class outside C1..C5 | The validator rejects it — report it as an out-of-list finding, which is a false positive |
## Limitations
- **Candidates, not defects.** Nothing is executed, so a finding is a reading of
the code, and a false positive is expected rather than exceptional. Driving
and measuring a candidate happens outside this skill.
- **Read-only is an instruction, not a structure.** `allowed-tools` does not
constrain the delegated reviewer. A harness that needs proof runs the review
against a write-protected copy and checks `git status --porcelain` afterwards
— which is what `scripts/perf-leak-review-eval.sh` does, and even that
observes only traces left *inside* the prepared tree.
- **Non-deterministic.** Two runs over one diff can differ. `scripts/run-tests.sh`
never executes a skill, so CI gates the schema, the fixture inventory, and the
scorer; recall and the clean controls are measured live and transcribed into
the PR verification anchor.
- **Five classes only.** Algorithmic complexity, concurrency, and security are
out of scope by construction.
- **Claude host only.** The `Agent` delegation is verified on the Claude host;
the Codex host is UNKNOWN and the skill stops there rather than guessing.