Run read-only SQL against the local Postgres app database (SELECT, EXPLAIN, EXPLAIN ANALYZE on SELECT). Default local URL postgres://posthog:posthog@localhost:5432/posthog; else DATABASE_URL. Use when querying the local DB, inspecting tables, debugging data, or analyzing query plans. Mutations are strictly forbidden.
npx skills add https://github.com/PostHog/posthog --skill querying-local-postgres
User's query: $ARGUMENTS
Scope: This repo uses PostgreSQL for app metadata (teams, projects, flags, Django models, etc.). Analytics event data lives in ClickHouse, not Postgres — use HogQL / ClickHouse tools for events-style questions unless the user explicitly wants Postgres.
EXPLAIN / EXPLAIN (ANALYZE, …) on read-only SELECT against Django or app tablesPGOPTIONS='-c default_transaction_read_only=on' to force a read-only connection).Do not run, suggest, or generate any of the following. Refuse and state that this skill is read-only.
INSERT, UPDATE, DELETE, MERGE, TRUNCATECREATE, DROP, ALTER, RENAMECOPY ... TO program, CALL (if it mutates), GRANT/REVOKEEXPLAIN ANALYZE on anything other than a read-only SELECT (including WITH … SELECT). Do not wrap DML in EXPLAIN ANALYZE — it would execute the write. The read-only connection below rejects writes, but the agent must not attempt this pattern.Allowed:
SELECT (including WITH … SELECT)EXPLAIN … SELECT (estimate-only plan; no execution)EXPLAIN (ANALYZE, …) SELECT — executes the SELECT once; use only for performance analysis. Must run on the read-only connection below.SHOW, SELECT from catalog views (pg_stat_*, information_schema, etc.) when read-onlyIf the user requests a write operation, say: "This skill is read-only. I can't run INSERT/UPDATE/DELETE or other mutations. Use a DB client or migration tool for writes."
| Goal | What to use |
| ----------------------------------------- | ------------------------------------------------------------------------------- |
| Plan shape, estimated costs, no execution | EXPLAIN (FORMAT TEXT, COSTS) or add VERBOSE |
| Actual timings, row counts, buffer hits | EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) on the SELECT |
| Buffer + WAL stats | BUFFERS requires ANALYZE; WAL requires ANALYZE (PostgreSQL 13+) |
Safe pattern: the analyzed statement must be only a SELECT (or WITH … SELECT), run on the read-only connection (see Usage below). Example:
PGOPTIONS='-c default_transaction_read_only=on' psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "
EXPLAIN (ANALYZE, BUFFERS, VERBOSE, FORMAT TEXT)
SELECT … LIMIT 100;"
Optional flags (when useful): SETTINGS (show non-default GUCs), WAL (with ANALYZE), TIMING (default on in recent versions for ANALYZE).
Caveats:
EXPLAIN ANALYZE runs the query — can be slow or heavy on large scans; prefer a bounded SELECT (e.g. realistic WHERE, LIMIT matching production shape) when exploring.EXPLAIN without ANALYZE — does not execute the inner statement (except some special cases); still only wrap read-only SQL.DATABASE_URLUse this hardcoded URL for day-to-day local queries (matches typical Docker Compose + port 5432 on localhost, SSL off):
| Setting | Value |
| -------- | ----------- |
| Host | localhost |
| Port | 5432 |
| User | posthog |
| Password | posthog |
| Database | posthog |
| SSL | off |
# Prefer this unless the user says their local password/db differs
LOCAL_POSTGRES_URL='postgres://posthog:posthog@localhost:5432/posthog'
Equivalent: postgresql://posthog:posthog@localhost:5432/posthog
Other local DBs on the same server: swap the path only, e.g. ...5432/posthog_persons.
Configuration source of truth (app): posthog/settings/data_stores.py (Django DATABASES, optional replica POSTHOG_POSTGRES_READ_HOST, direct POSTHOG_POSTGRES_DIRECT_HOST, PERSONS_DB_WRITER_URL, product DB routing from products/db_routing.yaml).
When not using the hardcoded URL: Connecting from the host with the same credentials is documented in Developing locally (fe_sendauth troubleshooting). Ensure containers are running.
Default env when DEBUG is on: Django builds a default DATABASE_URL from PGHOST (default db), PGUSER / PGPASSWORD, PGPORT, PGDATABASE — matching in-container hostnames. From the host, use localhost and the same user/password/database name unless your shell already exports DATABASE_URL.
Multiple PostgreSQL databases (same server in local compose; separate logical DBs):
posthogposthog_persons (PERSONS_DB_WRITER_URL / PERSONS_DB_READER_URL)posthog_<name> per products/db_routing.yaml (created by docker/postgres-init-scripts/create-product-dbs.sh)docker/postgres-init-scripts/ if neededPoint psql at the right database by changing the path in DATABASE_URL (e.g. .../posthog_persons).
Rust / sqlx: Some services use rust/.env for DATABASE_URL when working from posthog/rust — see rust/README.md.
Always force the connection read-only via PGOPTIONS='-c default_transaction_read_only=on' so Postgres rejects writes even if the generated SQL is wrong.
> Why PGOPTIONS, not SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY?
> A psql -c "..." string with multiple statements runs as a single implicit transaction.
> SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY only sets the default for _subsequent_
> transactions — the in-progress one keeps the read-write mode it was given at BEGIN, so a write
> in the same -c would not be rejected. PGOPTIONS='-c default_transaction_read_only=on' sets
> the GUC at connection startup, so every transaction (including the implicit -c one) starts
> read-only. The inline equivalent is SET TRANSACTION READ ONLY; as the first statement of the
> -c string (it affects the current transaction, unlike SET SESSION CHARACTERISTICS).
Run from the PostHog repo root so relative env paths resolve.
Default — local hardcoded URL (posthog / posthog @ localhost:5432 / db posthog):
PGOPTIONS='-c default_transaction_read_only=on' psql "postgres://posthog:posthog@localhost:5432/posthog" -v ON_ERROR_STOP=1 -c "SELECT 1;"
Option A — DATABASE_URL already in the shell (e.g. after flox activate or manual export):
PGOPTIONS='-c default_transaction_read_only=on' psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "SELECT 1;"
Option B — load from a gitignored env file at repo root (if DATABASE_URL is set there):
npx dotenv -e .env -- bash -c "PGOPTIONS='-c default_transaction_read_only=on' psql \"\$DATABASE_URL\" -c 'SELECT ...'"
-c.LIMIT 100 unless the user specifies otherwise.-x: psql ... -x -c "...".posthog/models/ (and product packages under products/). Table names are usually prefixed with posthog_ and snake-cased (e.g. posthog_team, posthog_user). Confirm with \dt posthog_* in psql, or check the model's Meta.db_table if nonstandard.posthog/migrations/ (and product migration paths) define the authoritative DDL over time.PERSON_TABLE_NAME (see data_stores.py); default posthog_person.deleted fields where applicable.POSTHOG_POSTGRES_READ_HOST).EXPLAIN ANALYZE on SELECT for slow Django queries replicated as SQL — mind loading production-sized data.docs/published/handbook/engineering/developing-locally.mdhogli (see .agents/skills/hogli/SKILL.md)Efficient database search tool for bioRxiv preprint server. Use this skill when searching for life sciences preprints by keywords, authors, date ranges, or categories, retrieving paper metadata, downloading PDFs, or conducting literature reviews.
Access BRENDA enzyme database via SOAP API. Retrieve kinetic parameters (Km, kcat), reaction equations, organism data, and substrate-specific enzyme information for biochemical research and metabolic pathway analysis.
Access ClinPGx pharmacogenomics data (successor to PharmGKB). Query gene-drug interactions, CPIC guidelines, allele functions, for precision medicine and genotype-guided dosing decisions.
Query NCBI ClinVar for variant clinical significance. Search by gene/position, interpret pathogenicity classifications, access via E-utilities API or FTP, annotate VCFs, for genomic medicine.
Access COSMIC cancer mutation database. Query somatic mutations, Cancer Gene Census, mutational signatures, gene fusions, for cancer research and precision oncology. Requires authentication.
Query Ensembl genome database REST API for 250+ species. Gene lookups, sequence retrieval, variant analysis, comparative genomics, orthologs, VEP predictions, for genomic research.
Query openFDA API for drugs, devices, adverse events, recalls, regulatory submissions (510k, PMA), substance identification (UNII), for FDA regulatory data analysis and safety research.
Query NCBI Gene via E-utilities/Datasets API. Search by symbol/ID, retrieve gene info (RefSeqs, GO, locations, phenotypes), batch lookups, for gene annotation and functional analysis.
Take posthog/querying-local-postgres from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npx.
Without those the skill loads but fails at the first command.