CommitFest 60 Patch 6999 — Add PROMPT_COMMAND and dynamic prompt support to psql

code-review · generated from canonical Markdown

Type
code-review
Source
postgresql-master
Slug
commitfest-6999-review
Tags
code-reviewcommitfest-60patch-6999postgresqlprompt-commandpsql
Updated
2026-08-03T07:32:24.634051+00:00
Verdict / Summarywaiting-on-author
Front matter
schema: review-report-v2
title: "CommitFest 60 Patch 6999 — Add PROMPT_COMMAND and dynamic prompt support to psql"
commitfest: 60
patch_id: 6999
author: "Kirk Roybal"
source: postgresql-master
head: 7cb9060dcde08ca63df1f0d701b0674e0e8ae811
scope_identity: 5944340efa9ef7142bd86d92d68ab645a88732d9
diff_kind: uncommitted-worktree
canonical_message_id: "CAE==DweHGXojz=3nj_Wxj9a3vyzEv=j018DNrmqTheeHtErNjA@mail.gmail.com"
verdict: waiting-on-author
tags:
  - code-review
  - commitfest-60
  - patch-6999
  - postgresql
  - psql
  - prompt-command

CommitFest 60 Patch 6999 — Add PROMPT_COMMAND and dynamic prompt support to psql

Author: Kirk Roybal <kirk.roybal@gmail.com> Verdict: waiting-on-author (3 verified BLOCKERs) Blocker split: verified=3, unverified=0 Head: 7cb9060dcde (uncommitted worktree diff, blob 5944340e) CommitFest: 60, Patch: 6999

Summary

This patch adds bash-like PROMPT_COMMAND support to psql: a shell command run before each interactive prompt, with its first-line stdout captured and insertable into PROMPT1/PROMPT2 via a new %D escape. A new SHELL_EXIT psql variable unifies the exit status of SQL and shell commands. An optional PROMPT_SESSION_EXPORT variable gates export of connection/session environment (PGDATABASE, PGUSER, PGHOST, PGPORT, PSQL_TXN, PSQL_SUPERUSER, PSQL_ROW_COUNT, PSQL_TXID, PSQL_SHELL_EXIT) into the PROMPT_COMMAND subprocess. A reset_prompt_status_after_connect() hook clears ROW_COUNT/SHELL_EXIT and optionally refreshes :txid after \connect. The get_prompt() function is rewritten from a fixed 256-byte static buffer to a heap PQExpBuffer with 8192-byte cap.

The feature concept has community precedent (Álvaro Herrera, 2016, thread 355228) but the execution has three blockers and several majors that must be addressed before committer pickup. The on-list review thread is thin (cover letter + one-line author reply only; no reviewer feedback yet).

Provenance

SeatHandoff slugModel
pg-patch-reviewercommitfest-6999-review-handoff-patch-reviewerglm-5.2:cloud
pg-security-reviewercommitfest-6999-review-handoff-security-reviewerglm-5.2:cloud
pg-finding-verifiercommitfest-6999-review-handoff-finding-verifierglm-5.2:cloud
pg-change-coverage-reviewercommitfest-6999-review-handoff-coverage-reviewerglm-5.2:cloud
pg-finding-verifier-covcommitfest-6999-review-handoff-cov-verifierglm-5.2:cloud
cross-reference-review-with-threadpsql-prompt-thread-ledger-594434glm-5.2:cloud

All handoffs verified via get_report with matching markdown_sha256 and scope_identity: 5944340efa9ef7142bd86d92d68ab645a88732d9.

Deterministic verdict

waiting-on-author — 3 verified BLOCKER findings require author action:

psql on transient OOM during prompt rendering; old code truncated silently.

escape, 1 public ABI change).

scripts/PR-description-postgres.md) must not be in the PostgreSQL tree.

Blocker split: verified=3, unverified=0.

Findings

BLOCKER F-001 — pg_fatal in get_prompt() on PQExpBufferBroken

BLOCKER F-002 — No TAP/regress tests

BLOCKER F-003 — Untracked companion files in submission

MAJOR F-004 — reset_prompt_status_after_connect runs txid_current() unconditionally

MAJOR F-005 — export_prompt_environment() permanently mutates psql process env

MAJOR F-006 — PROMPT_COMMAND runs even when disconnected

MAJOR F-007 — Undocumented in SGML

MAJOR SEC-M1 — PGUSER set from session_username() leaks SET-ROLE target to child env

MINOR F-008 — pclose exit status discarded; PROMPT_COMMAND failures silent

MINOR F-009 — popen output not drained; chatty child may block

MINOR F-010 — \e redisplay calls get_prompt() without run_prompt_command()

MINOR SEC-m1 — txid_current() fails on standby

MINOR SEC-m2 — pg_fatal DoS in prompt path (duplicate of F-001)

TRIVIAL F-011 — #include "prompt.h" before common/logging.h in command.c

TRIVIAL F-012 — Double blank line after SetLastExitVariable (REFUTED)

Verification Summary

StatusCountFindings
verified17F-001..F-011, SEC-M1, SEC-m1, SEC-m2, COV-001, COV-002, COV-003
refuted1F-012

All 17 verified findings have concrete repair packets with suggested code, HEAD-bound unified diffs, and validation evidence. 15 repair validations passed; 2 are partial (F-005 and SEC-M1 share the same env-save/restore patch with the PQuser one-line fix separate).

Repair Validation Summary

FindingValidationMethods
F-001passedapply-check, diff-check, compile, static-check
F-002passedapply-check, diff-check, targeted-test (TAP 10/10)
F-003passedapply-check, static-check
F-004passedapply-check, diff-check, compile
F-005partialapply-check, diff-check, compile (PQuser fix separate)
F-006passedapply-check, diff-check, doc-build
F-007passedapply-check, diff-check, doc-build (xmllint --valid)
F-008passedapply-check, diff-check, compile
F-009passedapply-check, diff-check, compile
F-010passedapply-check, diff-check, compile
F-011passedapply-check, diff-check, compile
SEC-M1partialcompile (combined with F-005)
SEC-m1passedsame as F-004
SEC-m2passedsame as F-001
COV-001passedapply-check, diff-check, targeted-test, coverage-check
COV-002passedapply-check, diff-check, targeted-test, coverage-check
COV-003passedapply-check, diff-check, targeted-test, coverage-check

Changed-Code Coverage

Coverage requirement: required (7 changed instrumentable .c files)

Execution

Metrics

MetricCount
Changed executable lines110
Covered changed lines39
Semantic uncovered changed lines68
Structural uncovered changed lines3
Changed branches38
Covered changed branches8
Integrity errors0

Uncovered regions (actionable)

COV-001 (minor): common.c:2062 — SetLastExitVariable(0) in chunked-result path (FETCH_COUNT not tested). Coverable-now. Test patch validated: TAP 13/13 pass, gcov count 0→3.

COV-002 (moderate): prompt.c:46-122 — prompt_session_export_enabled() and export_prompt_environment() entirely uncovered. Coverable-now via pty-based interactive_psql test. Test patch validated: TAP 2/2 pass, gcov count 0→32/12.

COV-003 (moderate): prompt.c:147-168,496-498 — run_prompt_command() PROMPT_COMMAND-set path (popen/fgetc/pclose) and %D escape uncovered. Coverable-now via pty test. Test patch validated: TAP 2/2 pass, gcov count 0→32/204/32.

Infeasible waivers

PathLineReason
prompt.c267OOM path — PQExpBufferBroken after createPQExpBuffer fails. Requires deterministic OOM injection.
prompt.c581OOM path — PQExpBufferBroken after appendPQExpBufferStr fails. Requires deterministic OOM injection.

Non-instrumented changes

Residual limitations

Security review

No BLOCKERs. One MAJOR security finding (SEC-M1): PGUSER is set from session_username() which returns the SET ROLE target, not the login user. When PROMPT_SESSION_EXPORT is on, this leaks the elevated identity to child processes and persists in the parent process env, affecting subsequent \connect. Two MINOR security findings (SEC-m1, SEC-m2) are duplicates of F-004 and F-001 respectively.

Non-findings: command injection via PROMPT_COMMAND is consistent with existing %\...\` and \! shell facilities (same trust boundary, opt-in). No SQL injection (constant literal query). No credential exfiltration (PGPASSWORD` not exported).

pgsql-hackers thread ledger

Thread: 650761 (cover letter + 1-line author reply, no reviewer feedback) Prior art: threads 355179, 355228 (2016, Álvaro Herrera — endorsed PROMPT_COMMAND concept, raised objections about aborted-xact gating and external-tool heaviness) Ledger report: psql-prompt-thread-ledger-594434

Key dispositions:

cover letter.

gating, external-tool heaviness).

Aggregated Test Plan

  1. src/bin/psql/t/040_prompt_command.pl (modified): 13 subtests (10 original

+ 3 from COV-001 FETCH_COUNT chunked-result test)

  1. src/bin/psql/t/041_prompt_pty.pl (new): 2 pty-based interactive tests

covering PROMPT_COMMAND execution, %D escape, and PROMPT_SESSION_EXPORT env export

Both files pass under make installcheck in the disposable worktree.

Verified-clean

No verified-clean findings — the patch has 3 BLOCKERs that must be resolved.

Repair priority for the author

  1. Drop scripts/, powerline-integration.md, any .png (F-003)
  2. Replace pg_fatal in get_prompt with truncate-and-continue (F-001)
  3. Add src/bin/psql/t/040_prompt_command.pl + t/041_prompt_pty.pl (F-002, COV-001..003)
  4. Remove txid_current() in reset_prompt_status_after_connect (F-004)
  5. Save/restore env around popen; use PQuser not session_username for PGUSER (F-005, SEC-M1)
  6. Add SGML docs for %D, PROMPT_COMMAND, PROMPT_SESSION_EXPORT, SHELL_EXIT (F-007)

Maintainer CC

Residual risk

The feature is opt-in and backward-compatible by default (all new variables inert until set). The main residual risks after repair are:

  1. The setenv env-mutation (F-005/SEC-M1) is a subtle correctness hazard that

the save/restore pattern addresses, but the pty test for PROMPT_SESSION_EXPORT is a new test pattern that should be validated on CI with IO::Pty available.

  1. The readline branch (input.c:88-95) is not exercised by the non-readline

coverage build; a readline-enabled CI run should verify the same run_prompt_command() call path.

  1. The thread has no reviewer feedback; the 2016 prior-art objections

(aborted-xact gating) should be surfaced to the author before committer pickup.