pgbot
A single static Go binary that connects to PostgreSQL read-only, reads the server’s own statistics views and prints a graded, findings-first health report — and, because every run saves a local baseline, tells you what changed since last time; the same deterministic findings are served to AI agents over MCP, and the optional AI layer may only explain them.

What it is
A read-only PostgreSQL diagnostic that ships as one static Go binary. It connects with a pg_monitor role, reads the server’s own statistics views — pg_stat_activity, pg_stat_statements, pg_stat_database, WAL, IO, checkpoints, replication slots, locks, table and index sizes — and prints a graded report: a four-row gauge strip of vital signs, a checked line naming the subsystems that came back clean, a health score out of 100, then findings bucketed CRITICAL, WARNING and NOTE. Every run also writes a local SQLite baseline, so from the third run on it can say what changed and by how much, and pgbot why chains a symptom to its mechanism and antecedent out of that stored history. Output fans out to a versioned, PII-free JSON contract with a published JSON Schema, SARIF for the GitHub Security tab, JUnit, Prometheus textfile format and a self-contained HTML page. pgbot mcp exposes the same findings to an agent over the Model Context Protocol, while ask and explain let a model read them, never generate them.
Who built itThe repository belongs to the pgrundev organization, and 252 of its 310 commits carry Shapalov’s account, name and email; he also wrote the pull request that swapped the Postgres driver and the one that put the mark into the README header. Dependabot accounts for 22 commits, and twelve other people appear in the contributor list: 10xdev4u-alt and DivyamTalwar with eight each, lofoneh with seven, DiegoDAF with three, edwardsb and GitHub’s Copilot with two, and six contributors with one apiece. All 310 commits resolve to a linked account, and 65 carry co-author trailers — 21 Claude Opus 4.8, thirteen dependabot, ten Claude Fable 5, nine Claude Fable 5.1 and eight the-ai-developer.
How it is put together
The parts · 6One static Go binary with a hard boundary drawn around what it may do. It connects as a pg_monitor role, pins every session read-only through default_transaction_read_only, statement_timeout=15s and lock_timeout=2s, and wraps each probe in its own BEGIN READ ONLY … COMMIT; the SQL itself lives in 25 files under internal/collect/sql/ and is loaded by the collectors. Counters are sampled twice across an interval so the report can print live rates, and everything else is trended against a local SQLite baseline. Findings are computed in Go — 115,563 bytes of them in internal/findings/findings.go alone — and emitted into a versioned, PII-free JSON Context in which every section carries an exactness label of sampled, cumulative, scraped or unavailable. From that one document the renderers produce the terminal dashboard, SARIF, JUnit, Prometheus textfile output and a self-contained HTML page; pgbot mcp serves the same document to an agent over stdio; pgbot why reads the snapshot history through a pure engine that opens no database of its own. The AI layer sits strictly on top and may only explain what the engine already found.
- cmd/pgbot/
- Fifty files, 249 KB — one file per command:
inspect(13,565 bytes),advise(12,664),alldbs(12,829),why(11,449),waits(10,486), andlogs,erd,init,vacuum,tune,config,baselinesand the rest; the MCP server ismcp.go(17,998) with its tool definitions inmcp_tools.go(10,868). Twenty of the fifty files are tests. - internal/findings/, internal/collect/ and the engines
- The diagnostic core: 33 files and 236 KB of findings, of which a single file —
findings.go— is 115,563 bytes, with the catalogue metadata in a 25,745-bytecatalog.go; and 67 files and 179 KB of collectors, whose SQL sits beside them as 25.sqlfiles underinternal/collect/sql/. Around them:render(24 files, 116 KB, led by a 21,924-byte terminal renderer),conn(21 files, 115 KB, led by a 19,676-byte SSH tunnel),ai(15 files, 98 KB),why(6 files, 39 KB), a 20,896-bytecorrelate, anderd,advisor,diff,mcp,report,events,rate,pglogandconfig. - internal/store/, internal/model/ and schema/
- The memory and the contract. Six numbered migrations —
001_snapshots.sqlthrough006_index_verdicts.sql— under a 6,616-byte store, with retention, waits, events, suppressions and index verdicts beside them;internal/model/context.gois 37,050 bytes of the document everything else reads and writes.schema/holds six published JSON Schema files, 175 KB, frompgbot-context-1.1.0.jsonto1.5.0.jsonpluspgbot-advise-1.0.0.json, emitted by a 1,512-bytetools/schemagen/main.go. - docs/
- Sixty-one finding pages and their index under
docs/findings/(296 KB), a README that browses them by symptom, plusproviders.md(9,300 bytes) with the per-provider notes and a live-verification checklist,configuration.md(6,887) with the suppression contract and a per-finding object-identity table,release.md(5,306), and one design document atdocs/superpowers/specs/2026-08-23-pgbot-why-design.md(6,715). - Build, release and packaging
- A 14,833-byte CI workflow and an 8,941-byte release workflow, a 5,577-byte GoReleaser configuration, a 4,176-byte
action.ymlfor the GitHub Action, a 7,796-byteinstall.sh, aDockerfile, aMakefile,docker-compose.test.ymlfor the PostgreSQL matrix,scripts/gate.sh(2,408 bytes) as the local gate before pushing, and Nix files inflake.nixandpackage.nix. The npm path is a 3,420-byte build script, a 3,473-byte wrapper that locates the prebuilt binary, and a 9,978-byte wrapper test. - The agent surface
.claude-plugin/marketplace.jsonandplugin.jsonmake the repository its own Claude Code plugin marketplace;commands/holds three slash commands (pg-health,pg-slow,pg-indexes);skills/postgres-diagnostics/SKILL.md(7,027 bytes) is the playbook shipped with a 1,211-byte installer;.mcp.jsonregisters the server. The README introduces all of them from one section.
Choices, and what they beat
Read-only by role, not by flag over a flag, or a superuser connection with a promise attached
The guarantee is a login role holding
pg_monitorwith no write grants, and the session pinning plusBEGIN READ ONLYare described as defence in depth on top of it. Withoutpg_monitora non-superuser sees only its own sessions, so pgbot detects that at connect time and names the exact GRANT to run rather than silently reporting partial data.Commit the read-only probes rather than roll them back over rolling each probe back when it finishes
A read-only transaction writes nothing either way, but a rollback would inflate the
xact_rollbackcounter pgbot itself reports. The same instinct makes it exclude its own sessions, transactions and temp usage from the numbers it prints, so it never measures its own footprint as the database’s.The AI explains findings, it never generates them over letting a model produce the findings, or the causal chains
Stated in the README’s non-goals and again in the design document, which rejects LLM-generated causation outright. The model receives the same PII-free Context that
--jsonprints, is instructed to carry every caveat into any recommendation, and its text is printed under a rule labelled as generated and to be verified; if the model errors or no key is set, the deterministic report still stands.Rule-based causal chains over the stored history over generic statistical cross-correlation
The design document calls cross-correlation “spurious, unexplainable — off-brand”, and caps a chain at symptom, mechanism and antecedent, each hop carrying its numbers and onset time, with temporal alignment as a hard gate rather than a score that can be averaged away.
whyruns fully offline over connecting to fingerprint the database and take a fresh snapshot as the last point of the seriesA deviation recorded in the design document a day after approval: the store already holds a fresh snapshot from the user’s last inspect, so the connection “added a failure mode without adding signal”. The spec’s
--offlineflag became unnecessary and was dropped with it.Hypothetical indexes, validated by the planner over recommending indexes inferred from query text
Candidates come from the planner’s own sequential-scan filters, then each one is created with hypopg and the query re-planned; it is reported only if the planner switches to it and the estimated cost drops. Nothing is built, and the query is only ever planned with
EXPLAIN (GENERIC_PLAN), never executed.Suppression stays visible over letting a
.pgbot.tomlrule remove a finding from the outputSuppressed findings remain in the JSON with their reason, never move the exit code, and a suppressed critical still renders, because “a config must not hide
checksum_failures”. The file also refuses to read any credential-shaped key.
Read fromdocs/superpowers/specs/2026-08-23-pgbot-why-design.md (6,715 bytes, read in full), the cmd/ and internal/ trees with per-file sizes, schema/ (six JSON Schema files, 175 KB), the numbered migrations under internal/store/migrations/, the README sections on the read-only role, the --json contract, the baseline store and the MCP tools, and the pull-request threads cited above.
Build log
6 stages- 01
Twelve minutes from an empty repository to the first commit
The repository was created at 23:41:52 on 2026-08-11 and its oldest commit —
Add files via upload— lands twelve minutes later, at 23:54:02 on the same day, so the project begins as an upload rather than a first push. Nine weeks on it holds 310 commits, 229 of them in August and 81 in September, and twenty releases fromv0.2.0on 2026-08-17 tov0.8.1on 2026-09-06. The rhythm is bursty rather than steady: four releases inside 25 minutes on the evening of 2026-08-31, fromv0.6.0at 19:09:30 tov0.6.3at 19:34:56; three more across 2026-09-01; four within eighteen hours on 2026-08-18. Around that sit 1,348 stars, 66 forks, four watchers and 32 open issues, and a tag list that carries thev1the GitHub Action pins as well as av0.8.0that appears in no release entry. Fourteen accounts have contributed: Shapalov with 252 commits, dependabot with 22, and DivyamTalwar and 10xdev4u-alt with eight each. - 02
A specification approved in chat, then edited by its own implementation
docs/superpowers/specs/2026-08-23-pgbot-why-design.mdis the repository’s one architecture document — 6,715 bytes — and it opens by dating itself: approved in chat on 2026-08-23. It designspgbot whyas rule-based causal chains over the stored snapshot history, and it names what it rejected: generic statistical cross-correlation, because that is “spurious, unexplainable — off-brand”, and any LLM-generated causation, because it “violates AI never generates findings”. The document specifies five mechanism rules, an onset detector, a confidence score whose hard gate is temporal alignment, a single new store read, a new MCP tool, and a testing plan that starts every rule red. Then comes the section this archive rarely gets to read: “Implementation deviations (v1, 2026-08-24)”, written the next day.whyshipped fully offline, with no--offlineflag, following the repository’s existingdiffconvention — the store already holds a fresh snapshot from the user’s last inspect, so connecting added “a failure mode without adding signal”. One of the five rules shipped and the other four are follow-ups on the same engine; thetargetargument was dropped because the report already caps at the three worst chains. - 03
Replacing the Postgres driver, and a test that failed for the right reason
Pull request 114, from the maintainer on 2026-09-30, takes pgbot off
pgxand ontopggov0.1.0 — a dependency-free wire-protocol client from the same organization — so thatpgx,pgpassfile,pgservicefileandpuddleleave the module graph. The connection layer went first: pool, session pins, read-only transactions, the SSHDialFunc, the pooler and PgDog probes, the Aurora--all-instanceshost override. Then the collectors,erd,logs,advisewith itsEXPLAIN (GENERIC_PLAN), the MCP tools and the integration tests, verified against PostgreSQL 13 through 18 and 19beta1 over TLS. The first CI run failedTestIntegration_ratesArePresenton PostgreSQL 15 and up while 14 passed, and the maintainer’s comment on the pull request is why this is worth recording: since PostgreSQL 15 a backend flushes its counters topg_stat_databaseonly when it goes idle, and CI’s write load was one longDOloop, so those commits were invisible while the test sampled. Reproduced locally,xact_commitmoved by exactly one per sample — the sampler counting itself. Under that load the old build passed by measuring its own traffic, and the maintainer writes that it “would also have passed with the stats-caching bug the guard exists to catch”; the new build, leaving less self-traffic in the window, correctly saw zero transactions per second. - 04
Twenty issue numbers in one afternoon
Between 00:18 and 19:44 on 2026-09-22 a contributor named DivyamTalwar opened twenty consecutive numbered items — 85 through 104 — nine of them an issue paired with the pull request that fixes it, plus two lone pull requests. All but the first are still open on the last day of the record. The pairs read like a method: reproduce against a named upstream commit, say which parts of the test are real (the actual Cobra command, the real file-backed SQLite store, the real npm layout) and which are injected, record the verified commit hash, and show
bash scripts/gate.shor the race suite passing on that committed HEAD. The defects are one theme — an observation that was never made is indistinguishable from a measured zero. A failed collector returns a section marked unavailable, andstore.Savelifted its zero-valued fields into trend columns, inventing a database size of zero; unavailable sections in a stored baseline resurfaced as false connection drops and standby losses; WAL archiving findings were measured against the viewer’s clock instead of the snapshot’s collection time, so an unchanged serialized Context changed its verdict when recomputed later. The fix proposed in each case has the same shape: leave unknown as SQL NULL, keep a measured zero, and refuse to compare sections whose provenance is not observable. - 05
A schema version is not a free number
The JSON contract is published as one schema file per version —
schema/pgbot-context-1.1.0.jsonthrough1.5.0.json, 175 KB in total, generated by a 1,512-bytetools/schemagen/main.go— and because the file name carries the version, two pull requests that both bump it collide. That is what happened to the replica-identity finding in pull request 113: it began as 1.4.0, another pull request took the number, and the contributor’s last comment reports the conflicts resolved and the schema regenerated as 1.5.0. The maintainer’s side of that exchange is one line — “@lofoneh, tnx, pls resolve conflicts” — and the same line appears under DivyamTalwar’s advisor pull request at 04:27:01 on 2026-09-30, one minute after the newest commit in the material, a dependabot bump. That contributor had already answered his own CI failure in the thread: the PostgreSQL 16 to 18 containers lacked the HypoPG server package the new regression needed, a follow-up commit installs it and documents the prerequisite, and he notes that no assertion or integration check was disabled. The finding itself was established by running the realUPDATEon each published table of awal_level=logicalserver, and it reports the case he “wouldn’t have guessed”: afterDROP INDEX,relreplidentstaysiwith nothing behind it. - 06
Documentation treated as an artifact, down to the alpha channel
The README alone is 60,582 bytes, and the repository’s prose is not an afterthought: 61 finding pages under
docs/findings/, 296 KB, from a 2,126-byte note on double-logged pgaudit output to an 8,364-byte page on a blocked vacuum horizon, with an index that browses them by symptom and a template for the next one. Nor is it prose that nothing checks:internal/findings/docs_catalog_test.goandinternal/collect/docverify_integration_test.gohold the catalogue against the findings the engine actually emits, andpgbot explain-finding <id>serves the same page from the binary, so an agent explains a recommendation in pgbot’s words instead of inventing them. The same instinct covers the invariants the README calls load-bearing — read-only, deterministic findings, PII-free output — which are held by tests rather than by review: areadonly_sql_test.gofor the collected SQL, a 1,877-byteno_explain_analyze_test.gokeeping the executing form ofEXPLAINout of the collectors, a 10,195-byte redaction test, and safety-surface tests in the findings and render packages. It shows in a one-file change too: pull request 106 puts the project’s mark in the README header and explains it in three paragraphs — the image is committed rather than hotlinked, because the avatar URL is bound to the organization avatar and its?s=sizes are too small for a HiDPI display; the dark frame stays, because the uploaded file is RGB with no transparency, so the rounded corners visible on GitHub are GitHub’s own crop, and dropped in as-is the mark rendered as a hard black square on the light theme; and cutting out just the circle was tried first and failed, because the face is white and vanishes against the light theme. A rounded rectangle was masked into the alpha channel instead.
Adjacent records
All records →No. 070
OpenChatCut
A local-first video editor whose editing surface is a conversation: the built-in agent and external Codex or Claude Code sessions call the same editing tools the interface itself uses, so every change lands on a real multi-track timeline as a clip, transition, caption, effect or audio item that can still be dragged, undone and exported. Projects and media stay on the machine, and preview and final render both come out of Remotion.
No. 064
delegate-skills
A skills package in which every coding-agent CLI gets its own delegation skill: the orchestrating agent writes a self-contained brief, a separate CLI edits a real working tree, and the human keeps the review and the commit.
No. 061
Reticle
An MCP server and a dev-only SDK that let a coding agent read and drive a running web or desktop app from the inside, then answer with a verdict and the file and line to fix instead of a screenshot.