This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
REQUIRED: After every git push, immediately start a background task to
monitor the CI run for that push. If you pushed to both pgxntool and
pgxntool-test, start a background task for each repo — do not monitor them
sequentially.
The CI monitor lives in the pgxntool-test checkout: run
bash ../pgxntool-test/.claude/skills/ci/scripts/monitor-ci.sh (the /ci
skill). It monitors both repos and derives the owner from the current repo.
Pass the exact push SHA when available — gh run list --branch has a race
condition: if two pushes land close together on the same branch, --branch
may pick up the wrong run. --commit SHA targets the exact push and avoids it.
CLAUDE.md is for people USING pgxntool — extension developers who have embedded
pgxntool into their project via git subtree. It documents the build system, available
commands, and how pgxntool works.
If you are making changes to pgxntool itself, stop — you are in the wrong place.
See .claude/ in this directory for developer guidelines. More importantly, pgxntool
development must be done from the pgxntool-test repository, not from here. See the
Development Workflow section below.
Any agent working in an extension project should always defer to that project's own CLAUDE.md and instructions over anything stated here.
Guidance that is about PostgreSQL itself rather than about pgxntool lives in
CLAUDE-POSTGRES.md instead, so that this file stays about pgxntool.
CLAUDE-POSTGRES.md (next to this file) contains rules you must follow. The @ line
below imports it where that is supported; read the file directly if it did not.
@CLAUDE-POSTGRES.md
IMPORTANT: When creating commit messages, do not attribute commits to yourself (Claude). Commit messages should reflect the work being done without AI attribution in the message body. The standard Co-Authored-By trailer is acceptable.
pgxntool is NOT a standalone project. It is a meta-framework that exists ONLY to be embedded into PostgreSQL extension projects via git subtree. This repo cannot be built, tested, or run directly.
Think of it like this: pgxntool is to PostgreSQL extensions what a Makefile template library is to C projects - it's infrastructure code that gets copied into other projects, not a project itself.
This directory contains ONLY files that get embedded into extension projects. When extension developers run git subtree add, they pull the entire pgxntool directory into their project.
ABSOLUTE RULE: NO temporary files, scratch work, or development tools may be added to this directory.
Examples of what NEVER belongs here:
- Temporary files (scratch notes, test output, debugging artifacts)
- Development scripts or tools (these go in pgxntool-test/)
- Planning documents (PLAN-*.md files go in pgxntool-test/)
- Any file you wouldn't want in every extension project that uses pgxntool
CLAUDE.md exception: CLAUDE.md exists here for AI assistant guidance, but is excluded from distributions via .gitattributes export-ignore. Same with .claude/ directory.
Why this matters: Any file you add here will be pulled into hundreds of extension projects via git subtree. Keep this directory lean and clean.
CRITICAL: All development work on pgxntool should be done from the pgxntool-test repository, NOT from this repository.
For complete development workflow documentation, see: https://github.com/Postgres-Extensions/pgxntool-test
This codebase uses a two-repository pattern:
- pgxntool/ (this repo) - The framework code that gets embedded into extension projects
- pgxntool-test - The test harness that validates pgxntool functionality
For development and testing workflow, see: https://github.com/Postgres-Extensions/pgxntool-test
Extension projects include pgxntool via git subtree:
git subtree add -P pgxntool --squash git@github.com:decibel/pgxntool.git release
pgxntool/setup.shAfter setup, their Makefile typically contains just:
include pgxntool/base.mk- Processes
META.in.json(template with placeholders/empty values) - Strips lines with empty string values (this incidentally removes some, but not all,
X_commentplaceholder lines) - Produces clean
META.json
- Parses
META.json(PGXN distribution metadata) - Generates
meta.mkwith Make variables:PGXN- distribution namePGXNVERSION- version number
base.mkincludesmeta.mkvia-include
- Parses each
.controlfile (what PostgreSQL actually uses), notMETA.json - Generates
control.mkwith Make variables and rules:EXTENSIONS- list of extensions providedEXTENSION_*_VERSION- per-extension versions, from each.controlfile'sdefault_versionEXTENSION__CURRENT_VERSION__FILES- the current/most-recent auto-generated versioned SQL file for each extension (not all version files)- Rules for generating versioned SQL files
base.mkincludescontrol.mkvia-include
base.mk provides a complete PGXS-based build system:
- Auto-detects extension SQL files in
sql/ - Auto-detects C modules in
src/*.c - Auto-detects tests in
test/sql/*.sql - Auto-generates versioned extension files (
extension--version.sql) - Handles Asciidoc → HTML conversion
- Integrates with PGXN distribution format
- Manages git tagging and release packaging
Projects using pgxntool follow this layout:
project/
├── Makefile # include pgxntool/base.mk
├── META.in.json # Template metadata (customize for your extension)
├── META.json # Auto-generated from META.in.json
├── extension.control # Standard PostgreSQL control file
├── pgxntool/ # This repo, embedded via git subtree
├── sql/
│ └── extension.sql # Base extension SQL
├── src/ # Optional C code (*.c files)
├── test/
│ ├── deps.sql # Load extension and test dependencies
│ ├── sql/*.sql # Test SQL files
│ └── expected/*.out # Expected test outputs
└── doc/ # Optional docs (*.adoc, *.asciidoc)
These are the commands extension developers use (documented for context):
make # Build extension (generates versioned SQL, docs)
make test # Full test: testdeps → install → installcheck → show diffs
make results # Run tests and update expected output files
make html # Generate HTML from Asciidoc sources
make tag # Create git tag for current META.json version
make dist # Create PGXN .zip (auto-tags, places in ../)
make pgtle # Generate pg_tle registration SQL (see pg_tle Support below)
make check-pgtle # Check pg_tle installation and report version
make run-pgtle # Register extensions with pg_tle in the database
make pgxntool-sync # Update to latest pgxntool via git subtree pullNEVER use make installcheck directly. Always use make test instead. The make test target ensures:
- Correct test dependency installation (
testdeps, andtest-buildwhen enabled) - Extension is installed before tests run (
install) - Test comparison via
installcheck, with diffs shown on failure
Note: make test intentionally does not depend on clean — depending on clean caused problems with incremental/watch-based builds (see base.mk). If your tests need a clean build to pass, that's a sign of a missing dependency elsewhere, not something to fix by adding clean back.
Database Connection Requirement: PostgreSQL must be running before executing make test. If you get connection errors (e.g., "could not connect to server"), stop and ask the user to start PostgreSQL.
Claude Code MUST NEVER run make results or make build-results. Both update test expected output files and require manual human verification of test changes before execution.
Claude Code MUST NEVER modify files in test/expected/ or test/build/expected/. These are expected test outputs that define correct behavior and must only be updated through the make results/make build-results workflows.
The workflow is:
- Human runs
make testand examines diffs - Human manually verifies changes are correct
- Human manually runs
make resultsto update expected files
pgxntool uses PostgreSQL's pg_regress test framework:
- Actual test output: Written to
test/results/directory - Expected output: Stored in
test/expected/directory - Test comparison: pg_regress compares actual vs expected and generates diffs;
make testdisplays them - Updating expectations:
make resultscopiestest/results/→test/expected/
When tests fail, examine the diff output carefully. The actual test output in test/results/ shows what your code produced, while test/expected/ shows what was expected.
Exceptions to the above -- test-build and test/install (both optional, see README.asc) don't follow the test/results vs test/expected model:
- test-build runs first, in its own separate
pg_regresspass overtest/build/*.sql, and gates the main suite: if it fails,test/install/test/sqlnever run at all. It does compare actual vs expected normally (test/build/results/vstest/build/expected/) -- usemake build-resultsto refresh its expected output, notmake results. - test/install does NOT get a real diff at all: its actual output is written to the exact same file as its expected output, so a content difference can never fail the build, no matter what changed. The only thing that still fails the build is a hard SQL error, and only if the file has
ON_ERROR_STOPset (directly or via\i test/pgxntool/psql.sql) -- pgxntool checks for this by default. If atest/install/*.sqlfile is misbehaving, don't go looking for a diff; check whether it errored, and don't assume a stale-looking.outfor it means anything.
MAJORVER= version × 10 (e.g., 9.6 → 96, 13 → 130)- Tests use
--load-language=plpgsqlfor versions < 13 - Version detection via
pg_config --version
- Tests in
test/sql/*.sql, outputs compared totest/expected/*.out - Setup via
test/pgxntool/setup.sql(loads pgTap and deps.sql) .IGNORE: installcheckallowsmake testto handle errors (show diffs, then exit with error status)make resultsupdates expected outputs after test runs
- Auto-detects
asciidoctororasciidoc - Generates HTML from
*.adocand*.asciidocin$(DOC_DIRS) - HTML required for
make dist, optional formake install - Template-based rules via
ASCIIDOC_template
make distcreates../PGXN-VERSION.zip- Creates a git tag matching version, unless that tag already exists and points at HEAD (in which case tagging is skipped)
- Uses
git archiveto package - Validates repo is clean before tagging
make pgxntool-syncpulls the latest release (thereleasetag) from the canonical repopgxntool/pgxntool-sync.sh [<repo> [<ref>]]does the work and can be run without makemake pgxntool-sync-<name>pulls from thepgxntool-sync-<name>variable (<repo> <ref>)- Uses
git subtree pull --squash, thenupdate-setup-files.shfor a 3-way merge of copied files - Requires clean repo (no uncommitted changes)
pgxntool can generate pg_tle (Trusted Language Extensions) registration SQL for deploying extensions in AWS RDS/Aurora without filesystem access.
Usage: make pgtle or make pgtle PGXNTOOL_PGTLE_VERSION=1.5.0+
Output: pg_tle/{version_range}/{extension}.sql
For version range details and API compatibility boundaries, see: pgtle_versions.md
Installation targets:
-
make check-pgtle- Checks if pg_tle is installed and reports the version. Reports the version frompg_extensionifCREATE EXTENSION pg_tlehas been run in the database. Errors if pg_tle is not available in the cluster. AssumesPG*environment variables are configured. -
make run-pgtle- Registers all extensions with pg_tle by executing the generated pg_tle registration SQL files. Requires pg_tle to already be installed in the target database (CREATE EXTENSION pg_tle;) -- it does not create the extension itself, and errors out telling you to runmake check-pgtleif it's missing. Depends onpgtle, so it generates the SQL files first if needed. AssumesPG*environment variables are configured.
Version notation:
X.Y.Z+means >= X.Y.ZX.Y.Z-A.B.Cmeans >= X.Y.Z and < A.B.C (note boundary)
Key implementation details:
- Script:
pgxntool/pgtle.sh(bash) - Parses
.controlfiles for metadata (NOT META.json) - Fixed delimiter:
$_pgtle_wrap_delimiter_$(validated not in source) - Each output file contains ALL versions and ALL upgrade paths
- Multi-extension support (multiple .control files)
- Output directory
pg_tle/excluded from git - Depends on
make allto ensure versioned SQL files exist first - Only processes versioned files (
sql/{ext}--{version}.sql), not base files
SQL file handling:
- Version files (
sql/{ext}--{version}.sql): Generated automatically bymake allfrom basesql/{ext}.sqlfile - Upgrade scripts (
sql/{ext}--{v1}--{v2}.sql): Created manually by users when adding new extension versions - The script ensures the default_version file exists if the base file exists (creates it from base file if missing)
- All version files and upgrade scripts are discovered and included in the generated pg_tle registration SQL
Dependencies: Generated files depend on:
- Control file (metadata source)
- All SQL files (sql/{ext}--*.sql) - must run
make allfirst - Generator script itself
Limitations:
- No C code support (pg_tle requires trusted languages only)
- PostgreSQL 14.5+ required (pg_tle not available on earlier versions)
- Empty Variables: If
DOCSorMODULESis empty, base.mk sets to empty to prevent PGXS errors - testdeps Pattern: Never add recipes to
testdeps- create separate target and make it a prerequisite - META.json is Generated: Always edit
META.in.json, neverMETA.jsondirectly - Control File Versions: No automatic validation that
.controlmatchesMETA.jsonversion - PGXNTOOL_NO_PGXS_INCLUDE: Setting this skips PGXS inclusion (for special scenarios)
- Distribution Placement:
.zipfiles go in parent directory (../) to avoid repo clutter - Never hand-edit an old versioned SQL file:
sql/{ext}--{version}.sqlis only auto-regenerated bymakefor the extension's currentdefault_version(from its.controlfile — notMETA.json; see "PGXN Distributions vs. Extensions" in README.asc). Every othersql/{ext}--{version}.sql(any version that is no longer current) is a frozen historical record used to test update paths and PostgreSQL-version compatibility — editing one directly silently corrupts that record. If you need to change something after a version has shipped, bump the version and add asql/{ext}--{old}--{new}.sqlupgrade script instead; never editsql/{ext}--{old}.sqlin place. This applies even though the file carries a genericDO NOT EDIT - AUTO-GENERATED FILEheader — that header doesn't distinguish "regenerated every build" (current version) from "generated once, now frozen" (every other version). See "Version-Specific SQL Files" in README.asc for the full reasoning, including when it's acceptable to not commit a given version's file at all, and why that should be a plainrmrather than a.gitignoreentry.
Each script at the top level of this repository (pgxntool/) begins with a shebang followed by a short header comment describing its purpose within the first ~10 lines — read that header rather than relying on a hand-maintained list here, which would just go stale the same way this section itself once did. Exception: trivial third-party or one-line utilities may lack such a header.
- pgxntool-test - Test harness for validating pgxntool functionality: https://github.com/Postgres-Extensions/pgxntool-test