Coding style
Skill matejformanek/postgres-claude/.claude/skills/coding-style
Turn Claude Code into a long-term collaborator on PostgreSQL internals — cited knowledge corpus, agent skills, slash commands, and task-shaped scenarios for backend hacking.
npx -y skills add matejformanek/postgres-claude --skill coding-styleAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Format C code to upstream PostgreSQL house style for src/backend / src/include — covers hard tabs at width 4, BSD braces, postgres.h-first include order, C99 subset rules (no // comments, no VLA, no mid-block declarations), naming conventions (struct typedef + field naming, function names matching typedef names), function-header comment format, ~78-char line length, and pgindent expectations. Use whenever a PG patch edits, adds, or reviews .c / .h files under source/src/ or dev/src/, or when a reviewer flags pgindent churn on a posted patch. Skip for Linux-kernel style (CodingStyle), clang-format / rustfmt / prettier / black / shfmt configurations, non-PG C / C++ style (Google style, LLVM style, Mozilla style), Java checkstyle, JavaScript ESLint, EditorConfig tuning, and general "what's a good C style" advice.
SKILL.md
9.9 KB, as published. Nobody here has run it
PostgreSQL coding-style — operational rules
When you touch any .c or .h under source/src/, the file must end up
looking like the surrounding code. Follow this checklist; full reasoning
and citations live in knowledge/conventions/coding-style.md.
Hard rules (must not violate)
1. Indentation: hard tabs, width 4
- Literal
\t, not spaces. One tab per nesting level. - Verify your editor:
.editorconfigcovers it; if you write a file with spaces, pgindent will produce a noisy diff.
2. #include "postgres.h" is line 1 of every backend .c file
Order (each group separated by a blank line):
#include "postgres.h"(backend) or"postgres_fe.h"(frontend) or"c.h"(shared) — before any system header<system headers>(<stdio.h>,<unistd.h>, …)"project headers"grouped, alphabetical-ish
Headers under src/include/ must compile standalone (make headerscheck) and as C++ (make cpluspluscheck).
3. C99 subset only — these are banned even though C99 has them
- No
//line comments (use/* … */) - No variable-length arrays
- No declarations interleaved with statements — declare locals at the
top of the block before any statement. This includes
for (int i = 0; …)— declareiat the top of the enclosing block. - No universal character names (
\uXXXX) - Newer features (
_Static_assert, GCC builtins) require a fallback.
4. No raw malloc / free / strdup in the backend
Use palloc / pfree / repalloc / pstrdup / psprintf. Allocations
live in CurrentMemoryContext. Frontend/common code uses pg_malloc
etc. from src/common.
5. Errors via ereport, not fprintf(stderr, …)
ereport(ERROR,
errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero"));
Use elog(level, …) only for internal / "cannot happen" / debug
messages (no SQLSTATE, no translation). ereport(ERROR, …) does not
return — never write code after it. Memory and resource cleanup
after ereport(ERROR, …) is also unnecessary — AbortTransaction()
releases the per-query memory context, locks, buffers, and open file
descriptors.
6. Assertions never have side effects
Assert(cond) compiles away in non-cassert builds. Move any side effect
out of the macro. Prefer StaticAssertDecl/StaticAssertStmt for
compile-time checks.
7. File header block is mandatory and copyrights are exact
Every .c and most .h start with this format-preserving block. Both
copyright lines, verbatim:
/*-------------------------------------------------------------------------
*
* filename.c
* one-line description
*
* Portions Copyright (c) 1996-<year>, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/path/to/filename.c
*
*-------------------------------------------------------------------------
*/
The leading /*------- makes pgindent leave it alone.
Formatting checklist (pgindent will fix these — do them anyway)
| Rule | Detail |
|---|---|
| Brace style | BSD/Allman — opening brace on its own line at same indent |
| Function definition | Return type alone on a line; name(args) on the next; { in column 1 |
| Pointer star | Binds to the variable: char *p. Function return type: Foo * with one space |
Single-statement if | No braces; body on next line indented +1 |
else | On its own line after the closing } |
| Line length | Target ~80 cols; pgindent uses -l79. Don't fracture translatable strings just to fit |
| Trailing whitespace | Stripped everywhere except a few data files (see .editorconfig) |
| Final newline | Required (except a few data files) |
Comment style
Standard multi-line block:
/*
* comment text begins here
* and continues here
*/
Format-preserving block (pgindent won't reflow):
/*---------
* keep these line breaks
*---------
*/
Function header comments sit directly above the function, start with the function name, then describe what it does and any non-obvious preconditions. They are anchored in column 1 and not reflowed.
Naming
| Thing | Convention | Examples |
|---|---|---|
| Types / typedefs | PascalCase, no _t suffix | HeapTuple, MemoryContext |
| Functions (verbs on a subsystem) | lower_snake_case | heap_fetch_next_buffer, log_heap_update |
| Functions ("MethodOnType") | PascalCase | MultiXactIdGetUpdateXid |
| Locals & struct fields | lower_snake_case, often with a 2-3 char subsystem prefix (rs_…) | scan->rs_cbuf |
| Globals | mixed; match surrounding module | error_context_stack, Log_line_prefix |
| Macros & constants | ALL_CAPS_SNAKE for action macros, PascalCase for values of Pascal-typed constants | CHECK_FOR_INTERRUPTS(), InvalidBlockNumber |
| SQLSTATE codes | ERRCODE_* | ERRCODE_DIVISION_BY_ZERO |
Two operational rules:
- If you add a new typedef, also add it to
source/src/tools/pgindent/typedefs.list— otherwise pgindent mis-spaces uses of the new type. - Don't give a function the same name as a typedef. pgindent will mangle both.
Error message style (when wrapping in errmsg/errdetail/errhint)
| Slot | First letter | Terminal punctuation | Sentence shape |
|---|---|---|---|
errmsg (primary) | lower-case | none | fragment, one line |
errdetail, errhint | Capital | . | complete sentences |
errcontext | lower-case | none | fragment |
- Active voice. Past tense ("could not …") for recoverable; present tense ("cannot …") for permanent.
- No contractions. Use "cannot", never "can't".
- Avoid "unable", "illegal", "bad", "unknown" → prefer "cannot/could not", "invalid", "unrecognized".
- Quote user-supplied identifiers, file names, GUC names with
"%s". %mexpands tostrerror(errno).- State the reason.
could not open file "%s": %m— notopen() failed. - Wrap user-visible literal strings in
_("…")soxgettextextracts them.errmsgalready does this internally;errmsg_internaldoes not. - For file/socket failures, prefer
errcode_for_file_access()/errcode_for_socket_access()— they pick the rightERRCODE_*fromerrnoso you don't have to.
PG_TRY / PG_CATCH rules
PG_TRY();
{
/* code that might ereport(ERROR) */
}
PG_CATCH();
{
/* release resources held across the TRY */
PG_RE_THROW();
}
PG_END_TRY();
- Don't use
PG_TRYfor ordinary control flow. - Always
PG_RE_THROW()fromCATCHunless you have a specific reason to swallow (e.g. a PL exception handler). - Don't
return/goto/break/continueout of theTRYblock. - Any resource (lock, buffer, file, palloc'd memory you can't let
context cleanup handle) acquired inside the
TRYmust be released in theCATCHbefore re-throwing.
Before you commit
Format-check (pgindent / pgperltidy) and a scoped meson test run
automatically: the PostToolUse hook (.claude/hooks/pg-format.sh)
rewrites C/H/Perl files on edit, and the git pre-commit hook
(.claude/hooks/pg-precommit.sh) re-verifies on commit and runs an
R13-scoped suite list. If the hook isn't firing, run
/pg-install-hooks (the marker is # pg-precommit-guard v1 in
dev/.git/hooks/pre-commit). Manual headerscheck / cpluspluscheck
is still the user's call for public-header changes.
If pgindent insists on changes outside your patch:
- Did you add a typedef? Put it in
src/tools/pgindent/typedefs.list. - Did you name a function the same as a typedef? Rename one.
- Mismatched braces inside an
#if? Restructure.
Other committing-checklist items relevant to style:
*printfcalls: check trailing newlines.- Catalog change? Bump
CATALOG_VERSION_NO. - WAL/control change? Bump
PG_CONTROL_VERSIONetc. - Regression-test names:
regress_*pattern. EXPLAINin tests: useCOSTS OFFso output is portable.
When in doubt: imitate the neighbours
"Make the new code look like the existing code around it." — PG docs, Source Formatting
If two patterns exist in the tree (snake_case vs PascalCase for
functions, parenthesized vs un-parenthesized ereport aux calls, etc.),
match the file you are editing. Consistency within a file matters more
than picking the globally "right" answer.
Cross-references
knowledge/conventions/coding-style.md— long-form rationale and citations..claude/skills/error-handling/SKILL.md—ereport/elogrule (§5) deep-dive..claude/skills/memory-contexts/SKILL.md—pallocvsmallocrule (§4) deep-dive..claude/skills/patch-submission/SKILL.md— pgindent + check-world before submission..claude/skills/commit-message-style/SKILL.md— upstream-style commit message for the diff that comes out of this skill..claude/skills/build-and-run/SKILL.md— runningheaderscheck/cpluspluscheckunder the dev build.