Skip to content

Windows: app-server daemon opens a visible console window for every hook and shell command it runs #44768

Closed

Description

@saltenhof

Summary

On Windows, once the shared app-server daemon is running (codex app-server daemon start), any Codex TUI session in the same CODEX_HOME silently attaches to it. From then on every hook (pre_tool_use, post_tool_use, user_prompt_submit, …) and every shell command of that session is spawned by the daemon instead of by the TUI — and each one opens a visible console window for as long as the child runs. With per-tool-call hooks configured, that is several windows per tool call, i.e. dozens per minute during normal agent work.

The same session without the daemon (embedded app-server inside the TUI) shows no windows at all.

Environment

  • Windows 11 Pro 10.0.26200, Windows Terminal 1.24.11911 set as default terminal
  • codex-cli 0.154.0 (standalone install, ~/.codex/packages/standalone/current/bin/codex.exe)
  • Source referenced below: commit ddea03ad049142943bdbf13e937b1d67e8c1ba0c (2026-09-10)
  • Hooks configured in <project>/.codex/hooks.json (PreToolUse/PostToolUse command hooks), shell pwsh

Reproduction

  1. codex app-server daemon start
  2. Open a Codex TUI in a project that has command hooks (or just run any shell command); make sure the TUI attaches to the daemon (default launch, no -c/--worktree/--oss).
  3. Send a prompt that runs a shell command.

Observed: for each hook and for the shell command a console window flashes up (Windows Terminal, "OpenConsole.exe -Embedding"), visible for the lifetime of the child (typically 0.5–2 s; longer for long-running commands). Process chain captured with a monitor:

window "C:\Program Files\PowerShell\7\pwsh.exe"
  <- OpenConsole.exe -Embedding
  <- conhost.exe
  <- pwsh.exe -Command "..."          (the hook / the command)
  <- codex.exe app-server --listen unix://   (the daemon; its parent has exited)

codex app-server daemon stop makes the windows stop immediately; the TUI falls back to its embedded server.

Cause (from the source)

  1. The daemon is launched with no console at all:
    codex-rs/app-server-daemon/src/backend/pid_start.rs:207
    command.creation_flags(DETACHED_PROCESS | CREATE_BREAKAWAY_FROM_JOB);
  2. Hook and command children are console programs spawned with only CREATE_SUSPENDED — no CREATE_NO_WINDOW:
    codex-rs/hooks/src/engine/command_runner.rs:234-241 (JobObject::spawn_contained, fallback creation_flags(0)), and
    codex-rs/utils/pty/src/win/job.rs:126
    command.creation_flags(CREATE_SUSPENDED).kill_on_drop(true);
    A console application whose parent has no console gets a fresh one from Windows, and with Windows Terminal as the default terminal that fresh console is a visible window.

Other daemon-side spawns already avoid this correctly, e.g. managed_install.rs:79,106 and update_loop.rs:448 use CREATE_NO_WINDOW. The hook/command path does not.

What did not help

Starting the daemon process with CREATE_NO_WINDOW instead of DETACHED_PROCESS (so it owns a hidden console) keeps the daemon and its long-lived children (MCP servers, codex-code-mode-host.exe) invisible, but the hook/command pwsh children still get visible windows. So inheriting the parent's hidden console is not sufficient; the flag has to be set on the hook/command spawn itself.

Expected

Hooks and shell commands run by the app-server daemon should not create visible console windows — i.e. spawn them with CREATE_NO_WINDOW (as the updater and installer paths already do), or otherwise ensure they attach to a hidden console.

Impact

Any Windows user who starts the shared daemon (needed for codex agents, remote control, or any external client of the control socket) and has command hooks configured gets a window storm proportional to the agent's tool-call rate, and the only remedy is to stop the daemon.

Activity

  1. added
    bugSomething isn't working
    CLIIssues related to the Codex CLI
    windows-osIssues related to Codex on Windows systems
    app-serverIssues involving app server protocol or interfaces
    hooksIssues related to event hooks
    on Sep 11, 2026
  2. argszero commented on Sep 11, 2026

    @argszero

    Thanks — this is a well-instrumented report, and your diagnosis of the mechanism (spawn-flags) is essentially right. I verified every line you cited on current main and they all check out. Two things may still be missing from the fix list, both of which matter for the "every hook and every shell command" half of the symptom.

    Your citations, re-verified

    • app-server-daemon/src/backend/pid_start.rs:169 — creation_flags(DETACHED_PROCESS | CREATE_BREAKAWAY_FROM_JOB): confirmed (imports at :110-111). The daemon really does launch with no console.
    • hooks/src/engine/command_runner.rs:234-241 — JobObject::create(), job.spawn_contained(&mut command), fallback command.creation_flags(0): confirmed (:234/:237/:241).
    • utils/pty/src/win/job.rs:126 — prepare_suspended_spawn → creation_flags(CREATE_SUSPENDED): confirmed.

    So the hook path is exactly as you describe, and your negative control (starting the daemon with CREATE_NO_WINDOW leaves the pwsh children visible) is the right experiment — it correctly locates the fix on the child spawn rather than on the daemon.

    1. The fix belongs in prepare_suspended_spawn, not per-caller

    CREATE_SUSPENDED is applied by the shared job helper (utils/pty/src/win/job.rs:126), which is reached through spawn_contained (:169-185). Every caller that goes through that helper has the same gap, and the ones you cited are not the whole set:

    • hooks/src/engine/command_runner.rs:237 (your case)
    • git-utils/src/git_process.rs:44-50
    • rmcp-client/src/stdio_server_launcher.rs:290-295 (MCP stdio servers)
    • rmcp-client/src/http_headers.rs:464-468

    A per-caller edit fixes the instance; editing prepare_suspended_spawn to CREATE_NO_WINDOW | CREATE_SUSPENDED fixes the class, and the combination is already the in-repo precedent for a suspending spawn — windows-sandbox-rs/src/setup_launch.rs:30 does exactly creation_flags(CREATE_NO_WINDOW | CREATE_SUSPENDED).

    2. The shell-command path does not use that helper at all

    This is the part I think is missing. For exec_command the agent tool's tty argument defaults to false (core/src/tools/handlers/unified_exec.rs:70-72), so a default shell command does not go through a ConPTY. It goes:

    core/src/unified_exec/process_manager.rs:1337 → sandboxing/src/spawn.rs:110-139 → for the non-tty branch utils/pty/src/pipe.rs (spawn_process :330, spawn_process_no_stdin :353, both exported at utils/pty/src/lib.rs:17-19).

    And in pipe.rs the creation flags are simply never set — the job containment there is created around an already-started child:

    // utils/pty/src/pipe.rs:190-192
    #[cfg(windows)]
    let job = crate::win::JobObject::create().map(Arc::new);
    let mut child = command.spawn()?;           // no creation_flags at all
    // job assignment happens afterwards, on the returned handle (:200-205)

    prepare_suspended_spawn/spawn_contained are not called on this route, so adding CREATE_NO_WINDOW to the job helper alone would not silence the shell-command windows — the second half of your symptom would survive the fix. This also explains why your CREATE_NO_WINDOW-on-the-daemon experiment left the pwsh children visible: they are not inheriting a console through a job-assigned suspended spawn, they are being created by a path that has no console flags whatsoever.

    Two notes on this route:

    • It may be more than cosmetic. Because spawn_contained is not used, there is an explicit accepted race between command.spawn() (:192) and the job assignment on the returned handle (:200-205; the comment at :195-196 says as much). For a hook the job matters for tree termination; doing CREATE_NO_WINDOW here would need to keep the post-spawn assignment semantics intact.
    • The sandboxed variant of the same route has an unrelated asymmetry that is probably deliberate, but worth a maintainer's confirmation: windows-sandbox-rs/src/process.rs:109-114 only maps CREATE_NO_WINDOW when stdio is supplied ((Some(_), ConsoleMode::NoWindow) => CREATE_NO_WINDOW, all other arms 0), and the one non-helper caller passes ConsoleMode::Inherit (windows-sandbox-rs/src/unified_exec/backends/legacy.rs:120, reached from spawn_process_with_pipes :109). The NoWindow case is currently reserved for the FS helper (windows-sandbox-rs/src/bin/command_runner/win.rs:359-361). Note also that the default WindowsSandboxLevel::Disabled (protocol/src/config_types.rs:297-299) means the unsandboxed pipe.rs route is the one most users actually hit.

    Suggested split

    1. CREATE_NO_WINDOW | CREATE_SUSPENDED in utils/pty/src/win/job.rs:126 — covers hooks, git, MCP stdio and the HTTP-headers helper in one edit.
    2. Set CREATE_NO_WINDOW on the non-suspending spawn in utils/pty/src/pipe.rs:192 (and the no-stdin sibling if it is a separate body) — covers agent shell commands.
    3. Optionally, decide whether ConsoleMode::Inherit at legacy.rs:120 should follow the same policy.

    A regression test is awkward without a real console, but a Windows-only unit assertion that the built Command's creation flags include CREATE_NO_WINDOW for each of the three spawn sites would at least lock the policy in.

  3. JerrySkywalker commented on Sep 14, 2026

    @JerrySkywalker

    I investigated this further on native Windows and built a local implementation to validate the spawn-flag diagnosis at runtime.

    A few findings may be useful for the internal fix:

    • CREATE_NO_WINDOW needs to reach the final child CreateProcess boundary. Applying it only to an intermediate shell Command does not cover unified execution.
    • For JobObject-contained children, the caller's presentation flags need to be composed with CREATE_SUSPENDED; setting the no-window flag earlier is insufficient if a later creation_flags(...) replaces it.
    • The same visible-terminal presentation class was reproduced across:
      • redirected / pipe-backed shell execution,
      • command hooks,
      • internal Git,
      • automatic/plugin Git,
      • local stdio MCP.
    • Containment fallback paths also need to preserve no-window semantics after suspension is removed or the command is rebuilt.

    I validated the local implementation with:

    • 3,291 focused/unit/integration tests passing,
    • strict Clippy and full CLI build passing,
    • 29 deterministic Windows background runtime phases,
    • zero newly visible terminal/console windows,
    • zero foreground-stealing terminal events,
    • an interactive positive control that remained normally visible and functional.

    I also manually tested the resulting Windows UX: the positive control produced an intentional console window, while the patched background workload and TUI shell commands produced no window flash and no focus steal.

    Two existing ConPTY failures were reproduced against baseline source and were unchanged by the fix.

    The implementation that tested cleanly used a flag-aware JobObject spawn path that composes caller flags with CREATE_SUSPENDED, plus CREATE_NO_WINDOW at the separate non-suspending pipe-backed spawn boundary. Hook/Git/MCP fallback paths retained the no-window flag as well.

    The fully validated version was tested before my final upstream rebase. The same patch subsequently rebased cleanly onto current main (374c4b2d828321cfa3d3fc69ce66870f8b7fd01f), but I did not rerun the full suite after that last rebase.

    I have the exact tested diff and detailed runtime evidence locally if maintainers want any specific part of it reproduced or shared.

  4. i-dedova commented on Sep 15, 2026

    @i-dedova

    Reproduced on 0.154.0 standalone (Windows 11 Pro, Windows Terminal as the default terminal) — adding one manifestation and two data points I don't think are captured above.

    cua_node hosted runtimes: with the daemon running, a TUI session immediately spawns visible consoles for AppData\Local\OpenAI\Codex\runtimes\cua_node\<hash>\bin\node_repl.exe and ...\bin\node.exe. A Win32_Process parent walk shows both are children of the daemon process itself (node_repl.exe → the daemon's node.exe → daemon), created right at TUI session start — so the daemon-hosted code-mode/REPL runtimes hit the same flag gap as the paths enumerated above.

    Severity during agent turns: during tool-call-heavy work the spawns repeat per tool call at 10+ visible windows per second, which makes the machine effectively unusable until daemon stop.

    Control: the same TUI session performs the same tool calls with zero visible windows once the daemon is stopped, so the running daemon is the necessary condition for the storm in interactive use — consistent with the daemon-side spawn sites rather than TUI-local spawning.

  5. JerrySkywalker commented on Sep 16, 2026

    @JerrySkywalker

    Follow-up after completing the Windows runtime validation and deploying the local compatibility build. I also found and corrected one flaw in my earlier acceptance harness, so this supersedes the presentation-specific part of my previous comment.

    Final root-cause / implementation model

    The reliable rule I ended up with is: background presentation state is not transitive on Windows; CREATE_NO_WINDOW has to reach each final CreateProcess boundary that creates a console-subsystem child.

    The implementation that held up in testing uses two distinct patterns:

    1. JobObject-contained spawns: compose the caller's presentation flags with CREATE_SUSPENDED rather than replacing them. In practice the suspended launch needs CREATE_NO_WINDOW | CREATE_SUSPENDED when the caller is a background/non-interactive path. If containment setup falls back and suspension is removed, the no-window bit must remain.
    2. Non-suspending pipe-backed execution: set CREATE_NO_WINDOW directly on the actual Command::spawn() boundary. The JobObject assignment happens after process creation on this route, so fixing only prepare_suspended_spawn does not cover ordinary non-TTY shell execution.

    That same policy had to be preserved across rebuilt/fallback commands rather than applied only at one higher-level caller.

    The audited spawn families included redirected/pipe-backed shell execution, command hooks, internal Git, automatic/plugin Git, local stdio MCP, the HTTP-header helper path, Code Mode host launch, and relevant Windows helper/fallback paths. Interactive/PTY paths were intentionally left interactive.

    Correction to my earlier runtime evidence

    I later performed a separate provenance audit of the acceptance harness. It found one real problem: the harness began the root app-server startup observation phase and then launched codex.exe app-server --stdio with harness-supplied DETACHED_PROCESS.

    That means my earlier root app-server startup presentation result was not authoritative: the harness itself suppressed that root console. Specifically, the old legacy-background/startup and code-mode/startup presentation claims had to be withdrawn/qualified.

    The provenance audit also established that this did not substitute the wrong Codex binary or production daemon: source→binary binding, daemon identity, isolated control state, package coherence, and phase attribution were all independently proven. It also did not invalidate the independently attributed product-created shell/hook/Git/MCP/Code Mode-host/fallback child evidence.

    I rebuilt the relevant acceptance path so that the harness applies no presentation-changing flags (DETACHED_PROCESS, CREATE_NO_WINDOW, SW_HIDE, etc.) to the product process being measured.

    Decisive unmasked A/B on 0.154.0

    With that corrected harness I ran the same real daemon-backed workload against two isolated 0.154.0 subjects:

    Official 0.154.0:

    • daemon started normally through the product lifecycle;
    • shell + command-hook workload was functional;
    • relevant visible terminal/console presentation was reproduced.

    Patched R4C 0.154.0:

    • same workload was functional;
    • newly visible presentation events: 0;
    • foreground/focus presentation events: 0;
    • unresolved relevant window events: 0;
    • ambiguous HWND-generation events: 0;
    • observer errors: 0.

    Direct app-server --stdio startup was also rerun unmasked for both subjects and initialized successfully. The key bug-control result, however, is the real daemon-backed official-vs-patched shell/hook A/B above.

    I then activated the complete managed 0.154.0 patched package on the Windows machine used for testing. The running shared daemon resolves to the patched managed release and its executable hash matches the active current release. A normal TUI production smoke (including a real PowerShell command with a deliberate delay and the existing hook path) completed without popup/focus-steal behavior.

    Forward applicability

    I also semantically forward-ported the same source-level policy rather than treating the built 0.154 binary as reusable across versions.

    A validated port was frozen on an upstream-main base at 83dc7d11e873f43533dc898d50fae71cf4d55dc5. Subsequent drift through 8f38d5a877da8c5c2c0b5158e72bb5f2290a3157 had zero direct changes to the patch-touched source paths, and the exact sealed patch applied cleanly without manual production-source edits.

    Focused clean-vs-patched checks at 8f38… completed as follows:

    • Guardian: 89/89 clean and patched;
    • protocol: 339/339 clean and patched;
    • tcp-tunnel: 5/5;
    • CLI-routing focused check: 1/1;
    • all 14/14 adjacent runtime touches mapped;
    • historical ConPTY baseline failures reconciled identically.

    One limitation: I could not complete the 8f38… Code Mode runtime execution because the matching local rusty_v8 build artifact required by codex-code-mode-host.exe was unavailable, and I deliberately did not substitute a mismatched host/archive or start a V8-from-source build just to make the matrix green. The selected app-server Code Mode tests consequently failed on the common missing host binary prerequisite, not on observed patched behavior. I therefore classify current-main Code Mode runtime coverage there as build-prerequisite-blocked, not passed.

    cua_node / node_repl.exe report

    I also tried to qualify the later cua_node / node_repl.exe manifestation reported in this thread. On my machine no real cua_node runtime existed in either expected local runtime root, so I did not synthesize or install one just for the test.

    Therefore my result for that manifestation is explicitly:

    • local runtime present: no;
    • locally reproduced: no;
    • coverage: unknown / not reproduced.

    I am not claiming this patch covers the cua_node descendant path described by @i-dedova. Their report remains useful evidence of another final-boundary manifestation that should be checked where that hosted runtime is available.

    Practical takeaway

    For an upstream fix, I would still recommend treating this as a spawn-policy class rather than a one-off hook fix:

    • compose CREATE_NO_WINDOW with CREATE_SUSPENDED in the shared JobObject path;
    • independently apply CREATE_NO_WINDOW at the non-suspending pipe-backed spawn boundary;
    • preserve the presentation bit through containment/fallback rebuilds;
    • audit other daemon-owned console-subsystem final spawn boundaries under the same rule;
    • keep interactive PTY/TTY behavior unchanged.

    I have kept the exact validated source patch and detailed process/window evidence in a public fork/local evidence set and can provide a smaller diff or reproduce a particular spawn family if maintainers want one. I am not opening a PR because the repository's contribution guidance does not accept external code PRs.

  6. ncuJack12 commented on Sep 25, 2026

    @ncuJack12

    Confirming a continuing report on a newer release from Windows 11 Home 25H2 (OS build 26200.9457).

    • Codex CLI: codex-cli 0.157.0 (global npm installation).
    • Managed app-server daemon: 0.157.0-x86_64-pc-windows-msvc.
    • Windows Terminal: 1.24.11911.0.
    • Codex Desktop 26.917.9434.0 is also installed, but the process chain below belongs to the active CLI session.

    During normal Codex use, the user sees terminal windows repeatedly popping up. Read-only process inspection while this session executes shell tools shows the active codex.exe --yolo CLI attached to a codex.exe app-server --listen unix:// --managed-daemon child. The shell tool's powershell.exe is a child of that daemon and has a conhost.exe child:

    codex.exe --yolo
      -> codex.exe app-server --listen unix:// --managed-daemon
         -> powershell.exe
            -> conhost.exe
    

    This matches the daemon-side spawn path described in this issue and shows the symptom is still relevant on 0.157.0. I did not stop the daemon or run a daemon-off control, since that would interrupt the active session. No personal paths or raw logs are included.

  7. tnisizawa commented on Sep 26, 2026

    @tnisizawa

    Confirming this is still reproducible in the Windows Codex Desktop app:

    • Codex Desktop: 26.917.9434.0
    • Managed app-server daemon: 0.157.1-x86_64-pc-windows-msvc
    • PowerShell: 7.6.5
    • Platform: Microsoft Windows NT 10.0.26200.0 x64

    Every shell tool call opens a visible console window titled C:\Program Files\PowerShell\7\pwsh.exe. In repeated controlled tests, five separate trivial shell calls produced five visible windows. Process inspection showed the pwsh.exe parent is the managed codex.exe app-server --listen unix:// --managed-daemon process.

    Controls tested, each followed by a full app restart where applicable:

    • [features] unified_exec = true: no change
    • sandbox_mode = "workspace-write" with windows.sandbox = "elevated" and sandbox_private_desktop = true: no change
    • Windows Terminal versus Windows Console Host as the default terminal application: no change
    • PowerShell profile files: no process-launching logic found

    All experimental settings were restored afterward. This confirms the daemon-side child-window behavior remains present in 0.157.1 and is not specific to an old PowerShell version or the selected terminal host.

  8. GinnieII commented on Sep 26, 2026

    @GinnieII

    Same issue on Windows 11 Pro (26200), Codex CLI 0.157.1 via npm, launched inside Orca terminal panes.

    • After the auto-update installed the managed app-server daemon (app-server --listen unix:// --managed-daemon, detached, no console), every hook commandWindows entry (run as powershell.exe -NoProfile -Command "...") opens a new console. Windows Terminal is the default terminal app, so each one becomes a WT window showing error 2147942632 (0x800700e8) (pipe closed) because the hook has already exited.
    • With ~56 UserPromptSubmit hooks, a single prompt opens dozens of windows.
    • Workaround confirmed: launching with --no-daemon and running codex app-server daemon stop → 0 new console windows for the same prompt and hook set.
    • Expected: hook/shell children spawned by the daemon should use CREATE_NO_WINDOW (or equivalent), as the in-process TUI path effectively did.
  9. fidonan commented on Sep 26, 2026

    @fidonan

    Adding a native Windows 0.157.1 reproduction, one additional sandbox-helper spawn site, and a small locally tested patch.

    Environment and reproduction

    • Codex CLI and managed daemon: 0.157.1, initially installed via npm.
    • Windows x64, build 26200; Windows Terminal 1.24.11911.0 as the default terminal.
    • Elevated Windows sandbox enabled.
    • Ordinary agent commands such as Write-Output hello and git --version caused transient console windows even when the commands succeeded.

    A process/window trace captured a visible PseudoConsoleWindow belonging to codex-windows-sandbox-setup.exe, alongside a new Windows Terminal window, before the command runner and PowerShell started:

    codex.exe (managed daemon)
      -> codex-windows-sandbox-setup.exe
         -> conhost.exe
    
    then:
    codex.exe
      -> codex-command-runner-0.157.1.exe
         -> powershell.exe
    

    Additional source finding

    In run_setup_refresh_payload at rust-v0.157.1, stdin/stdout/stderr are redirected to null, but the helper is started without CREATE_NO_WINDOW. This refresh path is distinct from the setup launch path that already supplies the no-window flag.

    The same refresh omission exists in the source tags 0.155.0 and 0.156.0. Separately, daemon_auto_start is absent in 0.155.0, experimental/default-off in 0.156.0, and stable/default-on in 0.157.1. That is consistent with this becoming noticeable after upgrading; I did not run the older binaries.

    Local patch and observed results

    The attached patch, based on rust-v0.157.1 (36650394c5b38c2990ccf2a3457165ca3e9d9726), changes four files: 8 insertions, 5 deletions.

    1. Explicitly apply CREATE_NO_WINDOW to sandbox refresh.
    2. Launch the managed daemon with CREATE_NO_WINDOW | CREATE_BREAKAWAY_FROM_JOB.
    3. Match the suspended launch preflight and its residual-job test to those flags.

    Validation:

    • Local CLI build passed.
    • Four existing Windows daemon backend tests passed, covering stdio lifetime, process query rights, restrictive jobs, and residual-job breakaway.
    • An isolated real app-server initialize + command/exec comparison reproduced visible Terminal/Git windows with a harness-supplied DETACHED_PROCESS launch; the CREATE_NO_WINDOW launch returned the expected output without a new terminal window. This is a controlled launch-mode experiment, not independent proof of production daemon startup behavior.
    • Separately, after installing the complete patched package through the normal daemon updater and restarting, the running daemon executable hash matched the patched package. Actual sandboxed pipe, TTY, and unsandboxed pipe tool calls all succeeded, and post-restart window sampling found no new visible windows. The user then watched the three-command check and confirmed no popups.

    This does not establish that changing the daemon flag alone fixes every descendant. In particular, the hook/MCP/fallback paths discussed above were not covered by our acceptance check, and I am not claiming to invalidate the earlier final-spawn-boundary findings.

    Test limitations: a broader sandbox library run had 244 passed, 1 failed, 5 skipped. Its elevated setup test failed in an incomplete local fixture (missing helper; ShellExecuteExW returned 1223), so that suite is not claimed as passing. Full workspace tests were not run. The patch has not been rebased onto or tested against current main.

    Locally tested source patch against rust-v0.157.1
    diff --git a/codex-rs/app-server-daemon/src/backend/pid_start.rs b/codex-rs/app-server-daemon/src/backend/pid_start.rs
    index 1c286a8..7d85e7e 100644
    --- a/codex-rs/app-server-daemon/src/backend/pid_start.rs
    +++ b/codex-rs/app-server-daemon/src/backend/pid_start.rs
    @@ -148,7 +148,7 @@ impl PidBackend {
             #[cfg(windows)]
             {
                 use windows_sys::Win32::System::Threading::CREATE_BREAKAWAY_FROM_JOB;
    -            use windows_sys::Win32::System::Threading::DETACHED_PROCESS;
    +            use windows_sys::Win32::System::Threading::CREATE_NO_WINDOW;
                 // Preserve process-scoped paths before changing cwd; CA names match CUSTOM_CA_ENV_KEYS.
                 for name in [
                     "CODEX_HOME",
    @@ -206,7 +206,9 @@ impl PidBackend {
                 );
                 // Never retry inside the parent's Job Object: that would report a
                 // successful launch that dies when the terminal/SSH session closes.
    -            command.creation_flags(DETACHED_PROCESS | CREATE_BREAKAWAY_FROM_JOB);
    +            // DETACHED_PROCESS lets console children allocate visible windows.
    +            // Keep the daemon and its background descendants windowless.
    +            command.creation_flags(CREATE_NO_WINDOW | CREATE_BREAKAWAY_FROM_JOB);
                 if matches!(self.command_kind, PidCommandKind::UpdateLoop { .. }) {
                     match fs::remove_file(self.pid_file.with_extension("ready")).await {
                         Ok(()) => {}
    diff --git a/codex-rs/app-server-daemon/src/backend/windows.rs b/codex-rs/app-server-daemon/src/backend/windows.rs
    index 3b8b91a..b166f7c 100644
    --- a/codex-rs/app-server-daemon/src/backend/windows.rs
    +++ b/codex-rs/app-server-daemon/src/backend/windows.rs
    @@ -39,8 +39,8 @@ use windows_sys::Win32::System::JobObjects::JOBOBJECT_EXTENDED_LIMIT_INFORMATION
     use windows_sys::Win32::System::JobObjects::JobObjectExtendedLimitInformation;
     use windows_sys::Win32::System::JobObjects::SetInformationJobObject;
     use windows_sys::Win32::System::Threading::CREATE_BREAKAWAY_FROM_JOB;
    +use windows_sys::Win32::System::Threading::CREATE_NO_WINDOW;
     use windows_sys::Win32::System::Threading::CREATE_SUSPENDED;
    -use windows_sys::Win32::System::Threading::DETACHED_PROCESS;
     use windows_sys::Win32::System::Threading::GetCurrentProcess;
     use windows_sys::Win32::System::Threading::GetProcessId;
     use windows_sys::Win32::System::Threading::GetProcessTimes;
    @@ -118,7 +118,7 @@ pub(crate) fn ensure_not_elevated() -> Result<()> {
     // whether it will terminate the daemon. Suspend the probe before cleanup.
     pub(crate) fn ensure_detached_launch(executable: &Path) -> Result<()> {
         let mut child = Command::new(executable)
    -        .creation_flags(CREATE_SUSPENDED | DETACHED_PROCESS | CREATE_BREAKAWAY_FROM_JOB)
    +        .creation_flags(CREATE_SUSPENDED | CREATE_NO_WINDOW | CREATE_BREAKAWAY_FROM_JOB)
             .stdin(Stdio::null())
             .stdout(Stdio::null())
             .stderr(Stdio::null())
    diff --git a/codex-rs/app-server-daemon/src/backend/windows_tests.rs b/codex-rs/app-server-daemon/src/backend/windows_tests.rs
    index ed9044b..647c64b 100644
    --- a/codex-rs/app-server-daemon/src/backend/windows_tests.rs
    +++ b/codex-rs/app-server-daemon/src/backend/windows_tests.rs
    @@ -212,7 +212,7 @@ fn detached_launch_preflight_allows_residual_job() {
             let mut probe = std::process::Command::new(&executable)
                 .creation_flags(
                     super::CREATE_SUSPENDED
    -                    | super::DETACHED_PROCESS
    +                    | super::CREATE_NO_WINDOW
                         | super::CREATE_BREAKAWAY_FROM_JOB,
                 )
                 .spawn()
    diff --git a/codex-rs/windows-sandbox-rs/src/setup.rs b/codex-rs/windows-sandbox-rs/src/setup.rs
    index ad9ed0d..4384ff5 100644
    --- a/codex-rs/windows-sandbox-rs/src/setup.rs
    +++ b/codex-rs/windows-sandbox-rs/src/setup.rs
    @@ -388,6 +388,7 @@ fn run_setup_refresh_payload(b64: &str, codex_home: &Path) -> Result<()> {
         // Refresh should never request elevation; ensure verb isn't set and we don't trigger UAC.
         let mut cmd = Command::new(&exe);
         cmd.arg(b64)
    +        .creation_flags(windows_sys::Win32::System::Threading::CREATE_NO_WINDOW)
             .stdin(Stdio::null())
             .stdout(Stdio::null())
             .stderr(Stdio::null());
  10. Datakeysystems commented on Sep 26, 2026

    @Datakeysystems

    Additional Windows 0.157.1 reproduction with a failed rollback/stop path (26 Sep 2026):

    • Windows Terminal 1.24.11911.0 is the default terminal. Visible extra terminal tabs appear when Codex runs shell tools; Claude's command execution on the same machine does not show the symptom.
    • The global npm CLI is already @openai/codex@0.155.0 (npm ls -g @openai/codex --depth=0 and codex --version both confirm it).
    • codex app-server daemon version still reports status: running, cliVersion: 0.155.0, appServerVersion: 0.157.1, and managedCodexVersion: null. Two live codex.exe processes resolve to ~/.codex/packages/app-server-daemon/releases/0.157.1-x86_64-pc-windows-msvc/bin/codex.exe.
    • While this Codex session invokes command tools, one of those 0.157.1 processes spawns multiple powershell.exe children. A timed process snapshot observed new OpenConsole.exe processes during the same burst.
    • The documented recovery command fails: codex app-server daemon stop exits 1 with Error: app server is running but is not managed by codex app-server daemon. The immediately following version query still reports the 0.157.1 app-server running.

    This leaves command-heavy Codex sessions effectively unusable even after the npm CLI rollback. The unmanaged-server error also resembles #46468 and #37893. A supported way to stop or replace the unmanaged app-server, plus the child-process no-window fix discussed here, would help. I have not edited Codex configuration or binaries.

  11. 6 remaining items

  12. ThisCCL commented on Sep 28, 2026

    @ThisCCL

    Additional reproduction from a Windows 11 user running Codex CLI 0.157.1 with the OmO/LazyCodex 5.0.1 plugin (2026-09-28).

    • The active backend is the managed daemon at .../.codex/packages/app-server-daemon/releases/0.157.1-x86_64-pc-windows-msvc/bin/codex.exe app-server --listen unix:// --managed-daemon (observed PID 35408). plugin_hooks = true, and omo@sisyphuslabs is enabled.
    • During normal agent work, multiple visible Windows Terminal windows repeatedly appear and steal focus. The user supplied screenshots showing stacked blank terminal windows and a tab identified as pwsh.exe.
    • The user first launched codex through the global Node/pnpm entry point, then launched the same 0.157.1 package's bin/codex.js with an absolute Bun executable path. The visible-window symptom persisted. This comparison changes the JavaScript launcher runtime but keeps the native Codex version, CODEX_HOME, managed daemon, and plugin; it does not establish a clean Bun installation or an independent daemon comparison.
    • OmO 5.0.1 currently declares frequent Windows command hooks such as pre-tool-use-recommending-git-bash-mcp.json and post-tool-use-checking-lsp-diagnostics.json. Both use powershell -NoProfile -ExecutionPolicy Bypass -File .../node-dispatch.ps1 ... via commandWindows; the configured Git Bash executable (D:\Git\bin\bash.exe) is only for the Git Bash MCP and does not replace these hook commands. This increases the number of daemon child launches per tool call.

    Source checked against current openai/codex main (1cc7e2361237ce7244430ee1d581c77f95c57ac8): pid_start.rs:209 still uses DETACHED_PROCESS | CREATE_BREAKAWAY_FROM_JOB; command_runner.rs:227-231 uses spawn_contained and resets creation flags to zero on fallback. Current LazyCodex source (8cbc467bb3845f0ec74c80a6e0dd573204909ff1) retains the two PowerShell hook declarations: pre-tool, post-tool.

    The local screenshots establish the visible symptom, but this report did not capture each window's process parent at creation time or run a --no-daemon control. The managed-daemon spawn path is a strong match to the process traces already in this issue; this comment does not claim that every visible window on the machine came from a hook.


    This issue or PR was generated by LazyCodex.
    Tag: lazycodex-generated

  13. SinkaiZhang commented on Sep 28, 2026

    @SinkaiZhang

    Additional confirmed A/B reproduction on Windows 11 Pro (OS build 26100) with Codex CLI 0.157.1, launched from the VS Code integrated terminal using PowerShell:

    • In ordinary codex mode (shared background server), the agent ran Write-Output POPUP_TEST. The command succeeded and printed POPUP_TEST, but a separate, blank black console window visibly popped up.
    • In codex --no-daemon, the same command succeeded and printed POPUP_TEST without any popup.
    • I also resumed the same prior conversation via codex --no-daemon resume; Write-Output RESUMED_SESSION_EXEC_OK completed with exit code 0 and no popup.
    • Before this A/B test, I repaired a separate local project .codex directory ACL problem. Commands then executed successfully in both modes, so this is not the earlier sandbox setup failure.

    This comparison narrows the popup to the shared-daemon execution path in this setup; it does not prove the exact child process or process-creation flags responsible. Expected: agent-run, noninteractive shell commands should not open a visible console window. --no-daemon is an effective workaround here. No raw logs, private paths, or account details are attached.

  14. genebukh-npm commented on Sep 28, 2026

    @genebukh-npm

    I can reproduce this on Windows 11. When my Codex session uses the shared daemon, each pwsh command opens a visible console window. Running Codex with --no-daemon avoids the popups while the same commands still work. I now use --no-daemon for my Windows Codex sessions.
    Started with 0.157, now on 0.158

  15. ScienHAC commented on Sep 28, 2026

    @ScienHAC

    Same root cause as #48826: JobObject::prepare_suspended_spawn in codex-rs/utils/pty/src/win/job.rs (used by the hooks command runner) only sets CREATE_SUSPENDED, never CREATE_NO_WINDOW, so hook/shell commands spawned via spawn_contained on Windows always show a console window. Full diagnosis + fix: #48826 (comment)

  16. brunocouri commented on Sep 29, 2026

    @brunocouri

    Additional confirmation from another Windows 11 system.

    I originally noticed this while using Codex through Orca, so I first suspected Orca hooks / terminal integration. I then reproduced the same visible console flashing with Codex launched directly from PowerShell, outside Orca.

    Environment:

    • Windows 11 x64, build 10.0.26200
    • PowerShell 7.6.5
    • Native Windows Codex CLI

    A/B result on the same machine:

    codex-cli 0.156.1 -> no visible console flashing
    codex-cli 0.157.0 -> affected
    codex-cli 0.157.1 -> affected
    

    During affected runs I observed short-lived process chains such as:

    codex.exe
      -> pwsh.exe
         -> conhost.exe
    

    With the managed app-server path there were also additional helper / command child processes receiving console hosts.

    The key finding for me was the rollback test: after uninstalling the newer build and installing:

    npm install -g @openai/codex@0.156.1

    the flashing stopped immediately on the same Windows environment.

  17. sonofsmog commented on Sep 29, 2026

    @sonofsmog

    Follow-up from the Windows machine in my earlier 0.157.1 report: updating the npm CLI to 0.158.0 has not fixed the daemon-associated terminal-window spawning. During ordinary interaction with Codex—even simply sending a message—roughly five extra terminal windows flicker across the screen as Codex runs its background processes. This makes normal use disruptive; it is not confined to a special command-heavy task. Another Windows user in this thread also reports the symptom persisting on 0.158.

    Read-only checks on 2026-09-28:

    codex --version
    codex-cli 0.158.0
    
    codex app-server daemon version
    {"status":"running","backend":"pid","managedCodexVersion":"0.158.0","cliVersion":"0.158.0","appServerVersion":"0.157.0"}
    

    The live codex.exe app-server --listen unix:// --managed-daemon process resolves to ~/.codex/packages/app-server-daemon/releases/0.157.0-x86_64-pc-windows-msvc/bin/codex.exe; its pid-update-loop companion is from that same release. Thus the 0.158.0 CLI upgrade left the symptom in place and left an older app-server process running. This local check does not isolate how a freshly started 0.158.0 app-server behaves. The version mismatch also continues the daemon lifecycle problem in my earlier rollback/stop report.

    Please keep this issue open and test an ordinary interactive turn on Windows, including the background child processes it starts, along with daemon version replacement after a CLI upgrade.

  18. ScienHAC commented on Sep 29, 2026

    @ScienHAC
  19. yushizhan commented on Sep 29, 2026

    @yushizhan

    Still reproducible on codex-cli 0.159.0 (npm install, Windows 11 Enterprise 10.0.26200, Windows Terminal 1.24.11911, default terminal = "Let Windows decide"). Adding a concrete trigger and a verified workaround.

    Symptom

    Just launching codex from PowerShell (no prompt submitted) flashes 3 console windows during startup.

    What flashes

    I hooked EVENT_OBJECT_SHOW (SetWinEventHook) and resolved each shown window's owning process and parent:

    18:56:44.643 SHOW ppid=44460(codex) pid=23052 proc=git class=PseudoConsoleWindow  C:\Program Files\Git\cmd\git.exe
    18:56:44.931 SHOW ppid=44460(codex) pid=2120  proc=git class=PseudoConsoleWindow  C:\Program Files\Git\cmd\git.exe
    18:56:45.033 SHOW ppid=44460(codex) pid=72768 proc=git class=PseudoConsoleWindow  C:\Program Files\Git\cmd\git.exe
    18:56:45.084 SHOW ppid=2096(svchost)          proc=OpenConsole class=PseudoConsoleWindow   (+ Windows Terminal window activated)
    

    44460 is the managed daemon, not the TUI:

    <CODEX_HOME>\packages\app-server-daemon\releases\0.159.0-x86_64-pc-windows-msvc\bin\codex.exe app-server --listen unix:// --managed-daemon
    

    At the same instant the daemon logs (logs_2.sqlite) show the TUI asking it to run the status-line branch probe:

    18:56:44.434 app-server request: command/exec request_id="workspace-command-…"
      ExecOneOffCommand params: CommandExecParams { command: ["git", "-c", "safe.bareRepository=explicit", "branch", "--show-current"],
      tty: false, timeout_ms: Some(5000), cwd: Some("D:\\…"), env: Some({"GIT_OPTIONAL_LOCKS": Some("0")}), … }
    

    So even command/exec one-off commands (not only hooks / shell tool calls) spawned by the daemon get a fresh console. The daemon has no console of its own, so each console-subsystem child (git.exe) is allocated a new one, which gets handed off to Windows Terminal via default-terminal delegation → visible flash.

    Workaround (verified)

    codex --no-daemon → same window hook captures zero git/console windows on startup (the embedded app-server inherits the TUI's console).

    Expected

    Children spawned by the daemon on Windows (command/exec, hooks, shell tools, stdio MCP servers) should use CREATE_NO_WINDOW (or be attached to a hidden console / ConPTY), matching the TUI-embedded behavior.

    Related: #48074 (same CLI+daemon scenario, root cause not identified there), #48023 (stdio MCP servers), #46949.

  20. action-control commented on Sep 29, 2026

    @action-control

    Additional data point from a Windows user (Codex app 26.924, app-server-daemon 0.157.1, Windows 11 26200, Windows Terminal as default terminal).

    Measured with a 15 ms poll of top-level visible windows, correlated with a process-creation log (parent = the resident codex.exe app-server --managed-daemon):

    • Startup: 4 git windows (rev-parse --git-dir, rev-parse HEAD, rev-parse --abbrev-ref HEAD, remote get-url origin), more during work (status --porcelain, remote -v).
    • While a task runs: about 12–17 pwsh.exe -NoProfile -Command "node -e ..." windows in ~7 minutes (plugin hooks), plus one for a stop-review-gate hook per turn end.
    • On every turn end: one window from the notify executable (codex-computer-use.exe turn-ended), see also [bug] Computer Use helper's black console briefly appears when a turn completes #33254. It is started by absolute path, so it is not covered by fixes that only touch git or shell commands.

    Switching the default terminal to Windows Console Host only turned Windows Terminal windows into classic console windows.

    Workarounds that removed windows for us (not a fix, and they do not cover hooks):

    • git: a tiny GUI-subsystem git.exe wrapper placed next to codex.exe (Windows searches the application directory first), which runs the real git with CREATE_NO_WINDOW and passes stdio, args and the exit code through. Startup windows went from 4 to 0. It stops working when the daemon package directory changes on update.
    • notify: a launcher that starts the target hidden. Windows from the notify executable went to 0.

    The hook pwsh.exe windows remain, which matches the analysis above.

  21. Rcarraway3 commented on Sep 29, 2026

    @Rcarraway3

    Additional Windows reproduction with process-parent evidence and mitigation results.

    Environment:

    • Windows x64, build 10.0.26200.
    • npm Codex CLI 0.158.0.
    • Initial process capture identified the managed app-server daemon executable under the 0.158.0 release directory. A later inspection showed running daemon processes under 0.159.0; this was not a controlled version comparison.
    • Command hooks configured at both user and project scope; Windows PowerShell is the hook shell.

    Observed:
    Submitting a prompt causes many visible PowerShell windows. During the initial capture, eight hook PowerShell processes were started in the same second, all parented directly to the managed Codex app-server process. Each had its own conhost.exe child. Sanitized example:

    codex.exe app-server --listen unix:// --managed-daemon
      -> powershell.exe -NoProfile -Command "node <hook-directory>/memory-search.mjs"
         -> conhost.exe
    

    The other captured hook commands included prompt checks, skill synchronization, and lifecycle dispatch. Ordinary assistant shell-tool execution also appeared as a PowerShell child of that same daemon.

    Mitigation attempted:

    • Disabled six redundant user-scope hook registrations specifically in the project, preserving the project handlers. Verified the effective configuration through app-server config/read: disabled in the project and still enabled outside it.
    • Added missing Node windowsHide: true options to two hook-internal child launches. The lifecycle dispatcher already used windowsHide: true.
    • The user still reports many visible PowerShell popups on subsequent prompts. We have not independently established whether every running session reloaded the scoped config, so this is not a measured before/after window-count comparison.

    This supports investigating console suppression on the daemon's outer hook/shell child spawn: hiding grandchildren cannot suppress a console already created for the outer PowerShell process. CREATE_NO_WINDOW is a candidate fix for non-interactive launches, consistent with the investigation above; we have not patched or independently verified the native launcher implementation.

    Expected: background command hooks and non-interactive shell tools should preserve stdin/stdout/stderr and exit status without opening visible console windows. The current repeated popups significantly disrupt normal use.

  22. tstager commented on Sep 30, 2026

    @tstager

    Remaining console window: persistent PowerShell parser

    Thanks for #49164 and #49308. After comparing a locally verified Windows console-suppression patch against current main, the persistent PowerShell parser is the one remaining launch path from that patch that is still missing suppression.

    Verified base: bd5185820d (current main when tested), Windows 11 build 26200, using Windows PowerShell (powershell.exe) as selected by the existing test helper.

    In PowershellParserProcess::spawn, the code constructs a plain std::process::Command and calls spawn() without CREATE_NO_WINDOW. It does not use the new background-command or Job Object helpers.

    Reproduction and results

    I added the regression below to the existing Windows-only tests module in that file. It starts an isolated test process with DETACHED_PROCESS, launches the actual persistent parser, verifies that parsing Get-Location works, and checks the parser console window from the isolated process.

    just test --release -p codex-shell-command --lib -E 'test(parser_process_has_no_console_window_from_detached_parent)'

    On the unchanged production code, the test failed on both nextest attempts with:

    PowerShell parser created a console window
    

    Adding a Windows-only CREATE_NO_WINDOW flag immediately before command.spawn() made the same regression pass. The full affected unit suite then passed:

    just test --release -p codex-shell-command --lib
    174 tests run: 174 passed, 0 skipped
    

    The minimal local fix is:

    #[cfg(windows)]
    {
        use std::os::windows::process::CommandExt;
        const CREATE_NO_WINDOW: u32 = 0x0800_0000;
        command.creation_flags(CREATE_NO_WINDOW);
    }

    Reusing the new shared background_command helper would also fit the upstream implementation if preferred. This parser is a background helper with piped stdin/stdout; interactive ConPTY creation is separate.

    Detached-parent regression added to the existing Windows test module
        #[test]
        fn parser_process_has_no_console_window_from_detached_parent() {
            use std::os::windows::process::CommandExt;
    
            const CHILD_MARKER: &str = "CODEX_POWERSHELL_PARSER_CONSOLE_TEST";
            if std::env::var_os(CHILD_MARKER).is_none() {
                let output = Command::new(std::env::current_exe().unwrap())
                    .args([
                        "parser_process_has_no_console_window_from_detached_parent",
                        "--test-threads=1",
                        "--nocapture",
                    ])
                    .env(CHILD_MARKER, "1")
                    .creation_flags(0x00000008)
                    .output()
                    .unwrap();
                assert!(
                    output.status.success(),
                    "detached parser test failed: {}{}",
                    String::from_utf8_lossy(&output.stdout),
                    String::from_utf8_lossy(&output.stderr),
                );
                return;
            }
    
            let powershell = try_find_powershell_executable_blocking()
                .expect("PowerShell is required for the detached parser regression");
            let mut parser =
                PowershellParserProcess::spawn(powershell.as_path().to_str().unwrap()).unwrap();
            assert_eq!(
                parser.parse("Get-Location").unwrap(),
                PowershellParseOutcome::Commands(vec![vec!["Get-Location".to_string()]]),
            );
    
            #[link(name = "kernel32")]
            unsafe extern "system" {
                fn AttachConsole(process_id: u32) -> i32;
                fn FreeConsole() -> i32;
                fn GetConsoleWindow() -> *mut std::ffi::c_void;
            }
    
            // CREATE_NO_WINDOW may still provide an attachable headless console.
            // Inspect its window from this isolated process after the parser responds.
            let attached = unsafe { AttachConsole(parser.child.id()) };
            if attached == 0 {
                assert_eq!(std::io::Error::last_os_error().raw_os_error(), Some(6));
            } else {
                let window = unsafe { GetConsoleWindow() };
                unsafe { FreeConsole() };
                assert!(
                    window.is_null(),
                    "PowerShell parser created a console window"
                );
            }
        }

    The test intentionally checks GetConsoleWindow() rather than requiring AttachConsole() to fail: a headless PowerShell process can still expose an attachable console with no window.

    This is a focused reproduction on the source commit above, not a full runtime validation of the official 0.159.2 binary. Sharing it here in accordance with the contribution guide's request for issue reports and root-cause analysis instead of external PRs. Related: #48074.

  23. babelllll commented on Sep 30, 2026

    @babelllll

    Windows Console Popup Fix for Codex App-Server Daemon

    This note records a local workaround for the Windows console windows opened by the Codex app-server daemon. It is a technical summary, not an upstream patch.

    Verified result

    The useful verified result is narrower than --no-daemon:

    Keep the app-server daemon running, but start it with its own hidden console. Hook, PowerShell, and command children then share that hidden console instead of allocating new visible windows.

    This was verified without disabling daemon_auto_start. A normally launched console remained visible in the control test, while the daemon-owned console stayed hidden and the visible-console count returned to zero during hook and command execution.

    Environment

    • OS: Windows 11
    • Codex stable CLI observed during recurrence: 0.158.0
    • Manually deployed app-server daemon: 0.159.0-alpha.9
    • Related upstream issue: openai/codex#44768
    • Related upstream issues: #46949, #48074, #18984
    • Related upstream PRs: #48138, #48238, #48483

    Timeline

    September 27: first diagnosis

    After updating Codex, every user message caused a burst of visible pwsh.exe windows.

    The first suspected causes were scheduled health checks, terminal-host settings, and hook commands. Those checks found a separate once-per-minute popup source, but disabling it did not remove the burst caused by sending a message.

    Process tracing showed that the app-server daemon launched the console processes. The relevant behavior was:

    1. The daemon itself was started with CREATE_NO_WINDOW.
    2. Its child console processes did not inherit that flag.
    3. Each hook was wrapped as pwsh -Command ....
    4. One message could launch the UserPromptSubmit and Stop hook batches together.

    Stopping the daemon removed the windows but also dropped the active app-server session, so that was rejected as the normal fix.

    The 0.159 alpha contained upstream fixes for some MCP, code-mode, and pipe subprocesses, but the hook path still flashed. The local workaround was a small Node launcher that respawns every configured hook with both windowsHide and CREATE_NO_WINDOW (0x08000000) while preserving stdio and exit codes. All hook entries were routed through it.

    Verification showed that the hook-process burst disappeared. A single-flash remained: the app-server still created an outer PowerShell process for command execution, and that process was outside the hook configuration.

    September 29: regression after the stable update

    The CLI on PATH was updated to stable 0.158.0 while the manually deployed daemon remained 0.159.0-alpha.9.

    Two failures returned:

    • Hook commands failed with exit code 1 because Node spawn() cannot directly launch .cmd files on Windows (spawn EINVAL).
    • Console popups returned because 0.158.0 executed hooks through cmd /C.

    The launcher was changed so .cmd and .bat commands go through cmd.exe /d /s /c, while still using CREATE_NO_WINDOW. Both affected hooks then returned exit code 0.

    That fixed the hook layer only. The remaining outer PowerShell window required a different boundary: the daemon needed its own hidden console so its children could share it instead of allocating new visible consoles.

    Hidden daemon console

    The daemon startup was changed from the detached/no-console mode to a hidden-console process:

    • Start codex app-server --listen unix:// --managed-daemon with a hidden window.
    • Keep a scheduled fallback that restores this hidden daemon after login or an unexpected restart.
    • Verify with Win32 window enumeration (EnumWindows, GetClassName, IsWindowVisible) rather than Process.MainWindowHandle, which is unreliable for console windows.
    • Confirm that a normally launched console is visible while the daemon-owned console remains hidden.

    After this change, the previously visible console burst was no longer observed during hook and command execution.

    Verification record

    • Hook burst after nowin routing: no new visible terminal in the test window.
    • Hook exit status after .cmd forwarding: both previously failing hooks returned 0.
    • Hidden-console A/B test: normal start reported visible; hidden start reported not visible.
    • Window census after the daemon swap: daemon console existed but was hidden, and the visible-console count returned to zero.

    Operational caveat

    The hidden daemon must remain running for this workaround. Starting Codex with --no-daemon, or disabling daemon_auto_start, avoids one popup path but also removes the shared daemon used by the rest of the local workflow. It is not the fix used here.

    Suitable upstream report

    The useful report for Codex is narrow:

    On Windows, an app-server daemon started without a console causes child console programs (pwsh, cmd, hooks, and tool shells) to allocate their own visible console windows. CREATE_NO_WINDOW needs to cover hook execution and the outer command wrapper, not only MCP/code-mode/pipe subprocesses. A local hidden daemon console avoids the popup without disabling the daemon.

    No credentials, local paths, or private configuration are required for that report.

  24. etraut-openai commented on Sep 30, 2026

    @etraut-openai
    Contributor

    This should now be fixed as of hotfix release 0.159.2. Your daemon (background app server) should update automatically, but you can also update it manually using the /daemon slash command in the CLI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLIIssues related to the Codex CLIapp-serverIssues involving app server protocol or interfacesbugSomething isn't workinghooksIssues related to event hookswindows-osIssues related to Codex on Windows systems

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions