Error handling
Skill matejformanek/postgres-claude/.claude/skills/error-handling
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 error-handlingAssembled 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
Write or review a PostgreSQL backend ereport / elog call — covers ereport vs elog, picking a SQLSTATE from errcodes.txt, errcode_for_file_access, errmsg / errdetail / errhint capitalisation rules, soft errors via escontext, PG_TRY / PG_CATCH longjmp-safe cleanup blocks, and the DEBUG / LOG / NOTICE / WARNING / ERROR / FATAL / PANIC elevel ladder. Use whenever a PG patch adds, edits, or reviews C in source/src/backend or contrib/ that reports an error or logs a message — picking elevel, choosing a SQLSTATE, formatting errmsg, wiring PG_TRY/PG_CATCH around a longjmp-unsafe block, or migrating a call to soft-error style. Skip for Python try/except, Go error returns, Rust Result / anyhow / thiserror, C++ exceptions, Java checked exceptions, Sentry / pino / Winston / log4j application logging, Oracle ORA-* / MySQL error codes, and general error-handling philosophy questions.
SKILL.md
8.1 KB, as published. Nobody here has run it
Error handling — actionable rules
Reference doc: knowledge/idioms/error-handling.md (read it for the why).
When you write a new error report
- Pick
ereportfor user-visible errors,elogfor "should never happen".elog(ERROR, "cache lookup failed for relation %u", oid)is the canonical internal-error idiom.ereport(ERROR, errcode(...), errmsg(...))is for anything the user can trigger. - Always pass an
errcode()inereport. Default isERRCODE_INTERNAL_ERROR, which is rarely what you want. Pick the most specific SQLSTATE fromsrc/backend/utils/errcodes.txt. For file/socket errors useerrcode_for_file_access()/errcode_for_socket_access()right after the syscall (they consumeerrno). Pair with%min theerrmsgformat string to splicestrerror(errno)into the message, e.g.errmsg("could not open file \"%s\": %m", path). errmsgstyle — no leading capital, no trailing period, no newline, one phrase. Example:errmsg("relation \"%s\" does not exist", name).errdetail/errhint/errcontextare full sentences — capital, period, may be multi-sentence.errhintshould be actionable.- Quote identifiers as
\"%s\". Don't quote SQL keywords or numbers. - Don't put SQLSTATE or severity in the message text —
errcode()and the logger handle them. - Don't concatenate fragments into the format string — breaks gettext. Build via printf args.
- Use
errmsg_internalfor messages that should not be translated (developer-only "can't happen" cases).elogalready does this. - Don't clobber
errnobetween the failing syscall and theereport. Any palloc, syscall, or function call may overwrite it. Capture into a local (int save_errno = errno;) if you need to do work first, or restore viaerrno = save_errno;before theereport.
Picking elevel
DEBUG1..DEBUG5— verbose tracing, gated bylog_min_messages.LOG— operational events. Goes to server log, not to client by default.INFO— explicit user-requested output (e.g. VACUUM VERBOSE).NOTICE— expected events the user should know about.WARNING— unexpected non-fatal. Distinct from NOTICE.ERROR— abort current transaction, longjmp out. Most common choice.FATAL— terminate this backend process. Used for auth failure, fatal startup errors.PANIC— only if continuing would corrupt shared state (xlog write failure, shared memory invariant broken). Postmaster restarts the cluster.
Default to ERROR. Use FATAL/PANIC only with strong justification.
Critical: ereport(ERROR) does not return
It longjmps to the nearest PG_TRY or to PostgresMain. You do not write
cleanup code after it. Anything reached "after" an ERROR in the source is
dead code from a runtime perspective. Don't goto cleanup; let transaction
abort handle memory contexts, locks, buffer pins, etc.
For fds specifically, open via OpenTransientFile() (registers with the
transaction's ResourceOwner so it closes on abort) rather than raw open(2).
PG_TRY / PG_CATCH — when to use
Default answer: don't. Almost all backend code lets ERROR propagate.
Use PG_TRY only when:
- You hold a resource that won't be released by transaction abort (e.g. a Python interpreter handle in PL/Python, a libxml2 parser).
- You implement a language that must convert backend ERROR into a host exception (PL/pgSQL, SPI re-entry).
- You're at a top-level loop (PostgresMain, bgworker main) and need to resume after error.
When you do use it:
PG_CATCHmustPG_RE_THROW()or callAbortCurrentTransaction()/RollbackAndReleaseCurrentSubTransaction(). Never swallow silently.- Locals modified in TRY and read in CATCH must be
volatile. - Keep CATCH minimal — errors inside CATCH recurse on a 5-frame stack
(
ERRORDATA_STACK_SIZEinsrc/backend/utils/error/elog.c:154) before PANIC. - Prefer
PG_FINALLYoverPG_CATCHwhenever the cleanup is the same on success and error (the common case).PG_FINALLYauto-rethrows; you can't accidentally swallow the original error. UsePG_CATCHonly when the error path genuinely needs different work (e.g. converting to a host-language exception). FATALis not caught by PG_TRY. UsePG_ENSURE_ERROR_CLEANUP(storage/ipc.h) for FATAL-safe cleanup of process-external resources.
Adding "while doing X" context
Push an ErrorContextCallback rather than concatenating the context into the
message. Pop it on normal exit; PG_TRY auto-restores it on error.
ErrorContextCallback cb = { .callback = my_cb, .arg = state,
.previous = error_context_stack };
error_context_stack = &cb;
/* work */
error_context_stack = cb.previous;
The callback calls errcontext("processing row %d of \"%s\"", ...).
Soft errors
For input-parsing functions that accept an ErrorSaveContext *escontext:
use errsave(escontext, errcode(...), errmsg(...)) and check the node
afterwards. If escontext is NULL, behaves identically to ereport(ERROR, ...).
ereturn(escontext, dummy_value, ...) is the shorter form when you have no
post-report cleanup.
Checklist before committing
-
errcode()set explicitly, not relying on default INTERNAL_ERROR. -
errmsgis a string literal (for gettext extraction), lowercase start, no period. -
errdetail/errhintare complete sentences. - Identifiers quoted as
\"%s\". - No
goto cleanupafterereport(ERROR, ...). - If using
PG_TRY: catch block rethrows or aborts; modified locals arevolatile. - For "should never happen" use
elog, notereportwith hand-writtenerrmsg_internal. - No newlines in
errmsg. - No fragment concatenation.
When in doubt, cite
Reference live examples by grepping similar paths:
src/backend/commands/*.cfor user-facing DDL errors.src/backend/access/heap/heapam.cfor access-method internal errors.src/backend/utils/cache/lsyscache.cforelog(ERROR, "cache lookup failed ...").src/pl/plpgsql/src/pl_exec.cfor non-trivialPG_TRY/PG_CATCH.
Cross-references
.claude/skills/memory-contexts/SKILL.md—AbortTransactionreleases per-query contexts afterereport(ERROR);PG_TRY+volatilediscipline..claude/skills/coding-style/SKILL.md— error-message style guide (lowercaseerrmsg, complete-sentenceerrdetail/errhint)..claude/skills/debugging/SKILL.md—errfinishbreakpoint to trap anyereport/elog;\errverbosefrom psql..claude/skills/locking/SKILL.md— spinlock + error-safety: spinlocks NOT released on error; LWLocks ARE..claude/skills/wal-and-xlog/SKILL.md— redo functions mustereport(PANIC), neverereport(ERROR)(no rollback during replay).knowledge/idioms/error-handling.md— long-form idiom doc.source/src/include/utils/errcodes.txt— canonical SQLSTATE list.