
Querying Local Postgres
FreeRun read-only SQL queries against your local Postgres database.
Free · Opens the source repo
What Querying Local Postgres does
The Querying Local Postgres skill allows developers and data analysts to interact with their local Postgres database using read-only SQL commands. This skill is particularly useful for inspecting tables, debugging data issues, and analyzing query performance without the risk of altering any data. It operates by translating user queries into SQL statements that are executed in a read-only context, ensuring that no mutations can occur. This is crucial for maintaining data integrity while troubleshooting or performing analysis.
The skill is designed for use with the PostHog application, which utilizes PostgreSQL for storing various application metadata such as teams, projects, and flags. Since analytics event data is stored in ClickHouse, this skill is specifically tailored for tasks that require querying Postgres. Users can leverage commands such as SELECT, EXPLAIN, and EXPLAIN ANALYZE to gain insights into their database structure and performance. The ability to run EXPLAIN commands helps users understand query execution plans and optimize their SQL queries accordingly.
When using this skill, users should be aware that it strictly forbids any form of data modification. This means that any attempts to run INSERT, UPDATE, DELETE, or other mutation commands will be rejected. Instead, the focus is on providing a safe environment for data inspection and performance analysis. The skill is particularly beneficial during debugging sessions, where understanding the state of the database is essential for resolving issues.
Overall, the Querying Local Postgres skill is an essential tool for developers and data professionals working with Postgres databases. It streamlines the process of querying and analyzing data while ensuring a secure, read-only interaction with the database, making it an indispensable resource for anyone looking to maintain data integrity during their analysis.
When to use it
Use this skill when you need to run read-only SQL queries against your local Postgres database for debugging or performance analysis.
When not to use it
This skill is not suitable for executing any data mutations or when working with databases other than Postgres.
What you can build with it
Debugging Data Issues
When you need to investigate why specific rows in your database appear incorrect, use this skill to run SELECT queries and analyze the data.
Performance Analysis
If you're looking to optimize your SQL queries, utilize the EXPLAIN and EXPLAIN ANALYZE commands to understand execution plans and performance metrics.
Inspecting Database Structure
Use this skill to view the schema of your Postgres tables and check for constraints or duplicate keys without risking any data changes.
How to install Querying Local Postgres
View source1. Install with the skills CLI
npx skills add posthog/posthog/querying-local-postgres --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by posthogQuerying local Postgres (READ-ONLY) — PostHog repo
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.
When to use
- User asks to query the database, inspect tables, or run SQL against Postgres
- Debugging: Row-level checks (e.g. why a team/project/flag row looks wrong), migrations, constraints, duplicate keys
- Performance:
EXPLAIN/EXPLAIN (ANALYZE, …)on read-onlySELECTagainst Django or app tables
Instructions
- Strictly forbid mutations — See "Mutations strictly forbidden" below. If the user asks for any write or mutation, refuse and explain the skill is read-only.
- Translate the user's question into one or more read-only SQL statements.
- Show the SQL in a code block before running.
- Run using the command pattern below (always with
PGOPTIONS='-c default_transaction_read_only=on'to force a read-only connection). - Show results and give a brief interpretation (especially when used for debugging or plan review).
Mutations strictly forbidden
Do not run, suggest, or generate any of the following. Refuse and state that this skill is read-only.
- DML:
INSERT,UPDATE,DELETE,MERGE,TRUNCATE - DDL:
CREATE,DROP,ALTER,RENAME - Other writes:
COPY ... TO program,CALL(if it mutates),GRANT/REVOKE EXPLAIN ANALYZEon anything other than a read-onlySELECT(includingWITH … SELECT). Do not wrap DML inEXPLAIN ANALYZE— it would execute the write. The read-only connection below rejects writes, but the agent must not attempt this pattern.- Any statement that modifies data, schema, or roles
Allowed:
SELECT(includingWITH … SELECT)EXPLAIN…SELECT(estimate-only plan; no execution)EXPLAIN (ANALYZE, …) SELECT— executes theSELECTonce; use only for performance analysis. Must run on the read-only connection below.SHOW,SELECTfrom catalog views (pg_stat_*,information_schema, etc.) when read-only
If 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."
EXPLAIN and EXPLAIN ANALYZE (performance)
| 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 ANALYZEruns the query — can be slow or heavy on large scans; prefer a boundedSELECT(e.g. realisticWHERE,LIMITmatching production shape) when exploring.- Production / shared DBs — analyzing hot or wide queries can add load; prefer staging, a replica, or off-peak when the user cares about impact.
EXPLAINwithoutANALYZE— does not execute the inner statement (except some special cases); still only wrap read-only SQL.
PostHog: connection and DATABASE_URL
Local Postgres (host machine) — default for this skill
Use 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):
- Main app DB: usually
posthog - Persons DB:
posthog_persons(PERSONS_DB_WRITER_URL/PERSONS_DB_READER_URL) - Product-isolated DBs:
posthog_<name>perproducts/db_routing.yaml(created bydocker/postgres-init-scripts/create-product-dbs.sh) - Other init scripts may create additional DBs (e.g. cyclotron) — inspect
docker/postgres-init-scripts/if needed
Point 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.
Usage (command pattern)
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, notSET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY? Apsql -c "..."string with multiple statements runs as a single implicit transaction.SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLYonly sets the default for subsequent transactions — the in-progress one keeps the read-write mode it was given atBEGIN, so a write in the same-cwould not be rejected.PGOPTIONS='-c default_transaction_read_only=on'sets the GUC at connection startup, so every transaction (including the implicit-cone) starts read-only. The inline equivalent isSET TRANSACTION READ ONLY;as the first statement of the-cstring (it affects the current transaction, unlikeSET 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 ...'"
- Use single quotes for string literals in SQL inside the shell as usual; escape carefully when nesting quotes in
-c. - Default
LIMIT 100unless the user specifies otherwise. - For wide rows use
-x:psql ... -x -c "...".
Schema reference (PostHog)
- Django models → tables: see
posthog/models/(and product packages underproducts/). Table names are usually prefixed withposthog_and snake-cased (e.g.posthog_team,posthog_user). Confirm with\dt posthog_*in psql, or check the model'sMeta.db_tableif nonstandard. - Migrations:
posthog/migrations/(and product migration paths) define the authoritative DDL over time. - Person table name: configurable via
PERSON_TABLE_NAME(seedata_stores.py); defaultposthog_person.
Debugging with the query runner (PostHog-flavored)
- Confirm a row exists for a team, project, user, or feature-flag linkage; check soft-delete /
deletedfields where applicable. - Compare counts and joins to what the app assumes (e.g. membership, project access).
- Validate replica vs primary read differences only if the user is connected to the right host (replica:
POSTHOG_POSTGRES_READ_HOST). - Use
EXPLAIN ANALYZEonSELECTfor slow Django queries replicated as SQL — mind loading production-sized data.
Cross-reference
- Local setup and DB gotchas:
docs/published/handbook/engineering/developing-locally.md - Repo CLI:
hogli(see.agents/skills/hogli/SKILL.md)
Frequently asked questions about Querying Local Postgres
Similar skills
Create Data Lake Tables
Efficiently manage Iceberg tables on Amazon S3.
OneKGPd
Query individual-level data from the 1000 Genomes Project.
Database Lookup
Retrieve data from public APIs with precision and reproducibility.
BigQuery Basics
Manage datasets and run queries in BigQuery easily.
Query Data Lake
Efficiently execute SQL queries on Amazon Athena.
Find Data Lake Assets
Quickly resolve data lake asset references across AWS services.
