pg ctl start spawns visible cmd.exe console window on Windows — CreateProcessAsUser missing CREATE NO WINDOW

From: wyuebei(at)gmail(dot)com
To: <pgsql-bugs(at)lists(dot)postgresql(dot)org>
Subject: pg ctl start spawns visible cmd.exe console window on Windows — CreateProcessAsUser missing CREATE NO WINDOW
Date: 2026-06-24 06:15:40
Message-ID: 18bbefccd530e218.a6bdbcf84d30dc08.260dd5df653405e8@NBK11-64-T5J
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

Dear PostgreSQL developers,

We discovered this bug while debugging a cascading failure across three
layers of software that ultimately traced back to pg_ctl.exe on Windows.

## The discovery chain

We use a tool called pg0 (vectorize-io/pg0) which bundles PostgreSQL via
the theseus-rs/postgresql-embedded Rust crate. On every PostgreSQL start,
a cmd.exe console window flashed briefly. We traced the process tree:

parent (CREATE_NO_WINDOW) → pg0.exe → postgresql_embedded → pg_ctl start
→ cmd.exe /C "postgres.exe -D ... < nul >> start.log 2>&1" ← console window!
→ postgres.exe

The theseus-rs crate already applies CREATE_NO_WINDOW (0x08000000) to its
CreateProcess call when spawning pg_ctl.exe, so the parent→pg_ctl edge
was clean. The flash came from pg_ctl.exe itself spawning cmd.exe.

## Root cause

File: src/bin/pg_ctl/pg_ctl.c

Lines 510-511 explain the design choice:
"As with the Unix case, it's easiest to use the shell (CMD.EXE) to
handle redirection etc."

The command is constructed at lines 557-561:
cmd = psprintf("\"%s\" /C \"\"%s\" %s%s < \"%s\" >> \"%s\" 2>&1\"",
comspec, exec_path, pgdata_opt, post_opts,
DEVNULL, log_file);

It is launched via CreateRestrictedProcess() at line 1846:
r = CreateProcessAsUser(restrictedToken, NULL, cmd, NULL, NULL, TRUE,
CREATE_SUSPENDED, // <-- no CREATE_NO_WINDOW
NULL, NULL, &si, processInfo);

Since cmd.exe is a CUI binary (subsystem 3), Windows allocates a new
console for it on every CreateProcess call. The parent's own
CREATE_NO_WINDOW flag does not propagate to grandchildren.

## Proposed fix

Option A (minimal, one flag):

Add CREATE_NO_WINDOW (0x08000000) to dwCreationFlags:
r = CreateProcessAsUser(restrictedToken, NULL, cmd, NULL, NULL, TRUE,
CREATE_SUSPENDED | CREATE_NO_WINDOW,
NULL, NULL, &si, processInfo);

Option B (eliminate cmd.exe dependency):

Use native CreateProcess handle inheritance (hStdInput/hStdOutput/hStdError
in STARTUPINFO) for I/O redirection, removing the need for cmd.exe /C
entirely. This would also eliminate the dependency on COMSPEC being set.

## Impact

Any application that launches PostgreSQL via pg_ctl from a headless or
background context sees an unexpected console flash on every start.
This affects embedded databases, CI/CD pipelines, IDE-integrated
PostgreSQL instances, and tools using libraries like pg0,
theseus-rs/postgresql-embedded, and zonkyio/embedded-postgres.

## System details

- OS: Windows 11 (22H2+, ConPTY enabled)
- PostgreSQL version tested: 18 (bundled via pg0 v0.14.2)
- The code path in pg_ctl.c is substantially unchanged across versions

We are happy to test a patch if one is drafted.

Thank you for your work on this excellent project.

Best regards,
Yuebei Wang

Browse pgsql-bugs by date

  From Date Subject
Next Message PG Bug reporting form 2026-06-24 07:20:28 BUG #19531: Inconsistent Error Messages for the Same SQL Query
Previous Message Hüseyin Demir 2026-06-24 06:14:53 Re: BUG #19483: pg_upgrade fails with orphan records in pg_init_priv catalog table