perf-hunter
Skill0 starsChanged 6 months agoMITView on GitHub
Find and fix the actual bottleneck — not the one you assume. Profiles before optimizing, measures before and after, refuses to "rewrite for perf" without numbers. Covers React/Vue rendering, bundle size, database query plans, N+1 patterns, memory leaks, and Node.js event-loop lag. Use when the user says "make this faster", "why is this slow", "optimize this", "the page is laggy", "the query is slow", or "this endpoint times out".
In this repository16 other files it gives its agents.
- for-codex/AGENTS.mdAGENTS.md
- api-architectSkill
- browser-qaSkill
- code-auditorSkill
- doc-craftSkill
- git-flow-proSkill
- mcp-forgeSkill
- refactor-masterSkill
- resilience-engineerSkill
- schema-architectSkill
- security-sentinelSkill
- ship-itSkill
- test-architectSkill
- threat-modelerSkill
- type-tightenerSkill
- ui-polishSkill
The file
We split this skill into the rules it gives an agent
9 rules. About 0 are common, 6 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 18
Do *not* trigger for: code style optimizations that don't have measured impact (use refactor-master), perceived perf issues without numbers …
- Distinctive: line 43
If the user can't answer those, ask.
- Distinctive: line 110
If the after number didn't move, revert.
- Distinctive: line 114
Add a regression guard:
- Distinctive: line 131
Don't add a cache without an invalidation plan.
- Very specific: line 132
Don't useMemo everything in a React component.
- Distinctive: line 133
Don't switch from REST to GraphQL "for perf".
- Very specific: line 134
Don't rewrite in Rust/Go/etc.
- Distinctive: line 135
Don't optimize what runs once.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
perf-hunter — measure, fix, measure again
When to use this skill
Trigger when something is slow and someone wants it faster. Strong signals:
- "why is this slow?", "make this faster", "this is too slow"
- "the page takes 5 seconds to load"
- "the query times out"
- "we're hitting our function memory limit"
- A flame graph, profiler output, or DevTools screenshot pasted in
Do not trigger for: code style optimizations that don't have measured impact (use refactor-master), perceived perf issues without numbers ("it feels slow") — get numbers first.
The output contract
A perf change that:
- Has a before number — measured under realistic conditions, not synthetic best-case.
- Targets the actual bottleneck — identified by profiling, not guessing.
- Has an after number — same measurement, same conditions.
- Quantifies the trade-off — what got harder to maintain, what runtime cost moved where.
- Includes a regression test — a benchmark or a budget that fails if the perf regresses.
Without those four numbers and a regression test, it's not a perf fix — it's vibes.
Workflow
1 — Define "slow"
Get specific:
- What operation? (page load, API call, query, render, build)
- How slow? (p50, p95, p99 — averages lie)
- Compared to what? (target, prior version, competitor)
- Under what load? (1 user vs 1000, cache cold vs warm, payload size)
If the user can't answer those, ask. Optimizing the wrong p50 doesn't move the p95 the users notice.
2 — Measure before
Pick the right tool for the surface:
Frontend:
- Chrome DevTools Performance panel — record a real interaction, find the long tasks
- Lighthouse for page-load metrics (LCP, INP, CLS, TBT)
- React Profiler for component render times
webpack-bundle-analyzer/rollup-plugin-visualizer/source-map-explorerfor bundle size
Backend Node:
--proffor CPU profiling, thennode --prof-process→ flame chart with0xorclinic flameclinic doctorfor high-level (event-loop lag, GC, CPU, memory)clinic bubbleproffor async I/O patterns- For HTTP:
autocannonork6for load + percentiles
Database:
EXPLAIN ANALYZEfor the slow query (Postgres) — read the actual planpg_stat_statementsextension to find the queries burning the most time across the workloadEXPLAIN FORMAT=JSON(MySQL),db.collection.explain('executionStats')(Mongo)
Record numbers. Don't proceed without them.
3 — Diagnose
Common patterns and the smell they leave:
N+1 queries — code makes one query, then loops and queries per result. Smell: many identical queries in the log, total query count ~= result count.
- Fix:
IN (...)batch,JOIN, or a DataLoader-style batcher.
Sequential awaits when parallel works — for (const id of ids) { await fetch(id) } is N round-trips. Smell: latency scales with N.
- Fix:
await Promise.all(ids.map(fetch)). Cap concurrency withp-limitif needed.
Re-renders in React — child renders even when its props haven't changed. Smell: React Profiler shows wide flame.
- Fix: lift state,
useMemofor derived values,React.memofor components, stable references for callbacks. Don't sprinkle these — find the actual culprit first.
Bundle bloat — initial JS payload is 800kB+. Smell: Lighthouse LCP poor, FCP fine.
- Fix: dynamic
import()for non-critical routes, tree-shake (look forimport * as X), check for whole libraries imported for one function (date-fns sub-imports, lodash-es).
Missing indexes — sequential scan on a 10M-row table. Smell: EXPLAIN ANALYZE shows Seq Scan + high actual time.
- Fix: targeted index. Validate with
EXPLAIN ANALYZEthat the plan changed.
Bad index usage — index exists but planner skips it. Smell: EXPLAIN shows Filter after the scan rather than Index Cond.
- Fix: usually a type mismatch in the query (
WHERE id = '123'against integer column), or stale statistics (ANALYZE).
Memory leak — RSS climbs over hours/days, never plateaus. Smell: process restarts every N days "for hygiene".
- Fix: heap snapshots before/after a workload; look for unbounded caches, retained closures, untracked subscriptions, growing
Maps indexed by request ID.
Event-loop lag — Node app's response times balloon under load even though the work is async. Smell: clinic doctor shows lag > 50ms.
- Fix: usually a sync CPU-heavy step (JSON.parse of a huge body, regex, crypto on the request thread) — move to a worker_thread or off the request path.
4 — Fix the one thing
Apply the smallest change that addresses the diagnosis. Don't pre-optimize the next thing you "noticed while you were there".
5 — Measure after
Same tool, same scenario, same load. Record the new number. Report the delta in absolute and relative terms:
Before: p95 endpoint latency 1240ms
After: p95 endpoint latency 180ms (-85%)
Trade-off: added an in-memory index that uses ~40MB heap per worker.If the after number didn't move, revert. The fix wasn't the right one.
6 — Lock the win
Add a regression guard:
- Backend: a perf test in CI (
autocannonork6with a p95 threshold) - Frontend: bundle-size budget (
@bundle-analyzer/cli, Next.jsexperimental.bundlePagesRouterDependencies, orbundlewatch) - DB: a query that the test suite runs
EXPLAIN ANALYZEon, asserting the plan uses the new index
Without a guard, the fix decays. The next refactor will un-do it silently.
Patterns and anti-patterns
✅ Do:
- Optimize the hot path. 80% of latency typically lives in 20% of the code. Profile.
- Cache at the right layer — closest to the consumer that can tolerate the staleness. Browser, CDN, app-memory, Redis, DB materialized view.
- For DB perf: read the plan. The plan tells the truth; intuition lies.
- Save a copy of the profile/flame chart in the PR — future you will need to know what the world looked like before.
❌ Don't:
- Don't add a cache without an invalidation plan. The bugs you create with a stale cache are worse than the latency you save.
- Don't
useMemoeverything in a React component. Memoization itself costs CPU and memory. Profile first. - Don't switch from REST to GraphQL "for perf". The wire format isn't usually the bottleneck.
- Don't rewrite in Rust/Go/etc. before you've fixed the obvious things in the current stack.
- Don't optimize what runs once. A 10x speedup on a script that runs at deploy time is worth nothing.
Example invocation
User: "Our
/api/dashboardendpoint takes 4 seconds. Make it fast."
- Get numbers: p95 is 4.2s, p99 is 7s, cache cold. Target: p95 < 500ms.
- Profile: turn on Postgres
log_min_duration_statement = 100. Make one request, check the log. - Diagnose: 47 queries logged. Looks like N+1 — for each org member, the code fetches their last 5 activities individually.
- Fix: replace with one batched query joining users + activities + filtering for "last 5 per user" via a window function. Single query, ~20ms.
- Measure after: p95 down to 180ms. Trade-off: query is longer to read, added a comment explaining the window function.
- Lock: add a
EXPLAIN ANALYZEassertion in the integration test that the plan uses the(user_id, created_at DESC)index. - Report: -96% latency, single query replaces 47, regression test in place.
See also
code-auditor— to find the N+1 patterns and unbounded loops before they're slowschema-architect— when the fix is an index that the schema is missingrefactor-master— when the perf fix is structural (e.g., pulling I/O out of a hot loop)
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

