Find latent bugs in a local PostgreSQL source tree (REL_xx_STABLE branch or HEAD) the way a core hacker does: build a heavily-poisoned debug instance (cassert + cache-discard + -O0/-ggdb3 + core dumps), fuzz it (sqlsmith) until the backend crashes, triage the core into a minimal reproducer (MRE), git-bisect the introducing commit, and render a community-style pgsql-bugs markdown report. The instance never auto-shuts-down. Use when the user says "find a bug in postgres", "fuzz postgres", "hunt for a crash", "test a recent commit", "write a repro", "bisect this crash", "把 PG 跑起来找 bug", "fuzz 一下 postgres", "看看这个 commit 会不会出问题", etc. Triggers whenever the user points at a PG source dir and wants to hunt bugs.
77
96%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Crash-first bug-hunting pipeline for a local PostgreSQL source tree. The order matters and is the whole point:
poison the build → fuzz it → triage the core → minimise to an MRE
→ bisect the commit → report (do NOT self-patch)The leverage is front-loaded. A poisoned build does ~90% of the work by turning latent bugs into immediate crashes; the fuzzer then finds inputs no human would hand-write. Reading commits by eye is a secondary, targeted probe — used to aim the fuzzer, not as the primary search.
Source layout assumed: the standard PG tree (src/backend/,
src/include/, contrib/, src/test/, …) at $PG_SRC_DIR.
Three hard rules:
scripts/stop_pg.sh
explicitly when done.export PG_SRC_DIR=/path/to/postgres
# 1. POISON: configure+build+initdb+start with cassert, cache-discard,
# -O0 -ggdb3, core dumps enabled. The 90% lever.
./scripts/build_and_start_pg.sh
# 2. SEED + FUZZ: load some tables, then let sqlsmith run (overnight is best)
psql -h /tmp -p 55432 -d postgres -f your_schema.sql # populate the catalog
./scripts/fuzz_sqlsmith.sh 3600 postgres # 1h; use 28800 overnight
# 3. TRIAGE: a crash leaves a core under $PGDATA
./scripts/triage_core.sh /path/to/$PGDATA/core.NNNN
# 4. MINIMISE: cut the failing query down, confirm it crashes on demand
$EDITOR repro/mybug.sql
./scripts/run_repro.sh repro/mybug.sql mybug # "SERVER IS DOWN" = good
# 5. BISECT: find the commit that introduced it
./scripts/bisect_run.sh repro/mybug.sql REL_17_STABLE HEAD
# 6. REPORT: render markdown for pgsql-bugs (no patch)
$EDITOR /tmp/bug.yaml
./scripts/gen_bug_report.sh /tmp/bug.yaml
# when finished:
./scripts/stop_pg.shIf a real crash surfaces, the markdown is ready for [pgsql-bugs]. If the fuzzer runs clean, broaden the seed schema / run longer / switch branch.
Each step has a success criterion to verify before moving on.
Before building or investing in any candidate, rule out the two biggest time-wasters: a dirty working tree, and a "bug" that HEAD already fixed.
cd "$PG_SRC_DIR"
git status --short # any M/MM/A? the tree is NOT as-committed
git stash list # stashed reverts hide here toogit commit
could re-introduce a fixed bug. Surface this to the user and do not
build on top of it. Never git commit, git checkout, git restore,
or git stash away the user's changes without explicit permission.When the target is a specific commit (the user said "test this commit" or you picked one in Step 6), check it is not already superseded:
./scripts/check_already_fixed.sh <hash> # is it in HEAD? any later "Fix"/revert touching the same files?Success: git status is clean (or the user has explicitly accepted a
dirty tree), and the candidate commit is confirmed not already fixed or
reverted upstream. Only then proceed to Step 1. Details and edge cases:
references/already_fixed.md.
export PG_SRC_DIR=/path/to/postgres
./scripts/build_and_start_pg.shRuns ./configure --enable-debug --enable-cassert --enable-debug-symbols --enable-tap-tests with CFLAGS="-O0 -ggdb3",
auto-detects the cache-poison mechanism by grepping the tree (PG14+:
debug_discard_caches=1 GUC; older: -DCLOBBER_CACHE_ALWAYS), enables
core dumps, and starts on port ${PG_PORT:-55432}. Idempotent.
Read references/pg_build_options.md for
what each flag buys and how to add valgrind / ASan / RELCACHE_FORCE_RELEASE
when chasing memory bugs.
Success: pg_isready -p $PG_PORT returns 0; the start banner shows
Cache poison: guc (or macro), not none. If it says none,
poisoning was disabled — you'll find far fewer bugs.
# populate the catalog first — sqlsmith is far better against real tables
psql -h /tmp -p $PG_PORT -d postgres -f some_schema.sql
./scripts/fuzz_sqlsmith.sh 3600 postgresThe fuzzer generates valid random SQL until the backend crashes. Read references/fuzzing.md for seeding strategy, targeted fuzzing (aim it at a specific commit's new objects/GUC), and alternatives (SQLancer for logic bugs, amcheck for corruption).
Success: either a new core* appears under $PGDATA (→ Step 3), or
the run is clean — in which case broaden the seed schema, run longer, or
switch to a riskier branch/area (see Step 6).
./scripts/triage_core.sh <core-file>Produces a full backtrace (lldb on macOS, gdb on Linux). Read the top
non-library frames for the failing function + line. If there's no core,
the crash was likely an Assert trip — find TRAP: and the last
STATEMENT in the server log. See
references/triage_and_bisect.md.
Success: you can name the failing function and the query that triggered it.
Reduce the fuzzer's giant query to the fewest self-contained lines that
crash a fresh backend every time. Save as repro/<name>.sql.
./scripts/run_repro.sh repro/<name>.sql <name>run_repro.sh now detects a crash (server gone / PANIC / TRAP), not just
SQL ERRORs — it prints SERVER IS DOWN and exits non-zero when the MRE
works. Patterns by subsystem are in
references/repro_patterns.md; log reading
in references/log_indicators.md.
Success: a 3–10 line .sql crashes the backend deterministically
from a clean database. Until this holds, do not theorise about root
cause — it's a guess.
./scripts/bisect_run.sh repro/<name>.sql <good-ref> <bad-ref>Drives git bisect run: each step rebuilds, re-initdbs, runs the MRE,
and reports crash-vs-clean. Confirm <good-ref> is genuinely clean
first, or bisect will blame an innocent commit. Details + decision tree
in references/triage_and_bisect.md.
Success: git bisect names a first-bad commit; the MRE crashes at
that commit and not at its parent.
If overnight fuzzing finds nothing, aim it. List recent risky commits:
./scripts/find_suspicious_commits.sh 30 HEADRead references/commit_risk_indicators.md:
bias toward parser/planner/executor/storage/replication paths and toward
repeatedly-fixed areas (Fix/Undo thinko/back-patch). Then
pre-create the objects that commit cares about and re-fuzz, or
hand-write a focused repro for its new code path.
./scripts/analyze_commit.sh <hash> 600 # message + diff sliceBefore spending any time on a candidate, screen it (Step 0 rule 3):
./scripts/check_already_fixed.sh <hash>Fix/Revert/Undo is itself the
fix — the bug it describes is already gone at HEAD. Don't try to
reproduce that bug; the repro will pass and you'll have proven nothing.Success: you reproduced a crash on the unmodified HEAD build, or confirmed the candidate path is clean for the inputs you exercised. A crash that needs a reverted fix does not count.
Fill references/bug_report_template.md into YAML, then:
./scripts/gen_bug_report.sh /tmp/<name>.yamlWrites ./markdown/find_postgres_bug-<slug>-<date>.md with environment,
MRE, actual/expected, backtrace/log tail, why-it's-a-bug, and bisect
result. Leave "suggested fix" as an explicitly-unverified direction at
most.
Success: the markdown has all required blocks (Environment / Summary
/ Repro / Actual / Why / Fix) and a log/backtrace snippet containing the
TRAP:/PANIC/ERROR: line.
Only render a bug report for a crash that reproduces on the unmodified
HEAD build. If the investigation ended in "already fixed", "already
reverted", or "only crashes after I undid a committed fix", do not
produce a pgsql-bugs report — write a short findings note instead
(markdown/find_postgres_bug-no-new-bug-<slug>-<date>.md) stating what was
ruled out and why. Reporting an already-fixed bug upstream wastes
maintainers' time.
./scripts/stop_pg.shThe only script that stops the instance. Nothing else will.
One markdown file per bug:
markdown/find_postgres_bug-<slug>-<YYYY-MM-DD>.md. Template in
assets/bug_report.md.template; field
reference in
references/bug_report_template.md.
Required sections (pgsql-bugs expectations):
.sql.| Script | Purpose |
|---|---|
scripts/pg_env.sh | Sourceable env (paths, port, PG_CFLAGS, PG_POISON_CACHE) |
scripts/build_and_start_pg.sh | Poisoned configure+make+install+initdb+start; idempotent, never auto-stops |
scripts/fuzz_sqlsmith.sh | Drive sqlsmith at the instance; detect new cores + crash markers |
scripts/triage_core.sh | Backtrace a core (lldb/gdb) |
scripts/run_repro.sh | Run a .sql; detect crash (server-down/PANIC/TRAP), capture log slice |
scripts/bisect_run.sh | git bisect run an MRE across good..bad, rebuilding each step |
scripts/find_suspicious_commits.sh | List recent risky commits (targeted probe) |
scripts/check_already_fixed.sh | Screen a commit: is it in HEAD / itself a Fix / later reverted-or-refixed? |
scripts/analyze_commit.sh | Show stat + diff slice for one commit |
scripts/capture_log.sh | Grep $PG_LOG (tail / errors / full) |
scripts/gen_bug_report.sh | Render markdown from a YAML inputs file |
scripts/stop_pg.sh | Explicit teardown |
Override any default by exporting before running:
PG_PORT=55433 PG_POISON_CACHE=1 ./scripts/build_and_start_pg.shLoad only what you need; each file is self-contained.
git bisect run, the
"report don't patch" rule, triage decision tree.fuzz_sqlsmith.sh prints install hints and exits.PG source dir?
├─ NO → ask for it; suggest $HOME/postgres
└─ YES → git status --short (Step 0 pre-flight)
├─ dirty / staged revert? → STOP, ask the user; never commit it
└─ clean → build_and_start_pg.sh (poisoned: cassert+cache-discard+-O0)
├─ banner says "Cache poison: none"? → re-enable PG_POISON_CACHE=1
└─ up → seed schema → fuzz_sqlsmith.sh (long run)
├─ crash (new core / PANIC)?
│ ├─ reproduces on UNMODIFIED HEAD? ── no ─→ not a finding
│ ├─ triage_core.sh → backtrace
│ ├─ minimise → MRE (run_repro.sh: "SERVER IS DOWN")
│ ├─ bisect_run.sh good..bad → first-bad commit
│ └─ gen_bug_report.sh → post upstream (NO patch)
└─ clean → targeted probe:
find_suspicious_commits → check_already_fixed.sh (skip if fixed)
→ seed those objects → re-fuzz / focused repro on HEAD
(NEVER revert a fix to reproduce); or run longer / switch branch3b9c83d
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.