---
name: create-no-window-inert-under-detached-process
description: CREATE_NO_WINDOW is IGNORED when DETACHED_PROCESS is set — a flag can be present in the constant and do nothing; source reading cannot decide it
metadata: 
  node_type: memory
  type: project
  originSessionId: fa318e90-f559-40d6-8edd-bb4863516cb6
  modified: 2026-08-04T22:14:59.341Z
---

`CREATE_NO_WINDOW` (0x0800_0000) is **ignored by Win32 when combined with `DETACHED_PROCESS`**
(0x8) or `CREATE_NEW_CONSOLE`. spt-core's `BASE_FLAGS` (`crates/spt-daemon/src/daemon.rs:1105`) is
`DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW`, and its comment claims "no popup
window" — **the bit is set and inert**. Consequence: a detached-spawned service has NO console, so
any console-subsystem GRANDCHILD it spawns allocates a NEW one, and Windows 11 defterm hands that to
Windows Terminal → a visible window per child. Measured 2026-08-03, raw `CreateProcessW`, 4 arms:

    A DETACHED|GROUP|NOWIN  0x08000208  => WINDOW APPEARED
    B NOWIN alone           0x08000000  => headless
    C DETACHED alone        0x00000008  => WINDOW APPEARED   (control: A ≡ C)
    D GROUP|NOWIN           0x08000200  => headless          (A vs D differ ONLY by DETACHED)

Fix (narrow, doyle-ruled, todlando's lane, NOT the v0.52 batch): `CREATE_NO_WINDOW` **without**
`DETACHED_PROCESS` on the service-spawn path — prior art already shipped in
`adapters/mock/src/shell.rs:27-38`. BROAD (editing `BASE_FLAGS`) is refused: it falsifies
`daemon.rs:893`'s "DETACHED_PROCESS ⇒ the child's std handles are simply absent", so the
capture/`STARTF_USESTDHANDLES` path must be re-read first.

**Why:** ⭐⭐ **flag PRESENCE is not flag EFFECT.** I falsified board #131's filed remedy at source
("it ORs an already-set bit") and was RIGHT — but for a shallower reason than the truth, and doyle
re-verified at source and inherited the same wrong premise. Source is not where flag interaction is
decidable; only an OS-level differential is. `INFRA-REGISTER.md:487-496` still carries the wrong
premise (correction queued to the next base — head is under deployah's audit, and changing a head
mid-audit invalidates someone's live verification work).

**How to apply:** when a constant's COMMENT asserts a behavior, the comment is a claim, not
evidence — run the differential.

**M2 EXPLAINED (2026-08-04, hertz, specimen captured before sweep — replaces the earlier
"UNEXPLAINED" verdict and retires the disk-exhaustion candidate):** the window STICKS because the
row kills the job ~1s after spawn, the console handoff to Windows Terminal dies mid-flight
(`0x800700e8` = win32 232 ERROR_NO_DATA, "pipe is being closed"), and **WT keeps a failed-launch
window open for a human to read — the window is OWNED by WindowsTerminal, a third process outside
our tree.** That is why tree-kill cleans processes and leaves windows, one per lost race. Measured
21 windows per 40 runs of ONE row (tree-teardown grandchild row, unloaded arm, harness console
0x0); four daemon.rs test rows spawn cmd through the same helper, so the full-suite rate is HIGHER
than 21/40 — never quote 21/40 as the suite rate. Sweep remedy: WM_CLOSE to exact-title-matched
HWNDs only, never kill WT (it hosts live agent tabs).

**Fix scoping caveat (todlando, accepted load-bearing):** `CREATE_NO_WINDOW` without
`DETACHED_PROCESS` = no NEW visible console; a spawner that HAS a console hands its own down and
foreign output interleaves into it. Two spawner arms — console-less (measured 0x0, hertz's arm)
and console-owning (human `cargo test` in a terminal; CI runner state UNMEASURED). A zero on one
arm does not carry to the other; the per-site remedy choice depends on spawner console state, not
just foreign-vs-rung. Doyle-ruled 2026-08-04: per-call fix direction accepted, waits operator
triage on releases#131; gate wants a FULL spawn-site census (creation_flags grep alone misses
sites — servicehost.rs:770 is not in it) and a two-arm differential.

⚠⚠ **`EnumWindows` IS THE RIGHT INSTRUMENT AND STILL RETURNED A CLEAN ZERO TWICE (hertz, 2026-08-04, operator-corrected both times).** Having the right tool is not having a competent probe:
1. **Class-name guess.** I filtered `EnumWindows` output on `ConsoleWindowClass|CASCADIA|PseudoConsole` and got **0 of 85**. The real class IS `CASCADIA_HOSTING_WINDOW_CLASS` — the filter should have hit. It did not, and I nearly reported "no windows" off it. **Never filter a census on a guessed constant; enumerate, GROUP BY owning process, and read what is actually there.** Grouping by process is what surfaced `89 WindowsTerminal` immediately.
2. **String marshalling.** Concatenating `StringBuilder` into a delimited string and re-splitting in PowerShell rendered every title as its FIRST CHARACTER (`Title="p"`, `Cls="C"`). A title-matched sweep run off that reader would have matched nothing — or worse, matched loosely. Fix: return a typed object list from C# (`public class Win {…}`) with `CharSet=CharSet.Unicode` on the P/Invoke, never a delimiter round-trip.
**The operator saw the windows on screen while my probe reported zero, twice.** A screenshot beat the instrument. When a human says "it is obviously there" and your census says zero, the census is the suspect — see [[verdict-from-probe-competence]].

⭐ **SWEEP REMEDY PROVEN, with numbers (2026-08-04):** 85→87 windows accreted under ONE `WindowsTerminal` pid that ALSO hosted two live agent windows (`hertz`, `lia`) — so killing the process kills live agents, including yourself. `PostMessageW(WM_CLOSE=0x0010)` to HWNDs matching the title with **case-sensitive exact equality** (`-ceq`, target `ping  -n 120 127.0.0.1`, note the DOUBLE space) closed 87, then 13 stragglers, to zero. Both agent windows and the WT process survived, asserted after. Zero regrowth over 40s. Guard belongs in the loop body, not just the filter.

## THE INVERSE DIRECTION — the same trap read backwards (hertz, 2026-08-04, source-only at main `6ec5237`)

The fix above SHIPPED as a `windowless` spawn arm, and it creates the mirror-image trap. At
`daemon.rs:1218-1219` the windowless path is `(BASE_FLAGS & !DETACHED_PROCESS) | …` — it masks
`DETACHED_PROCESS` **OFF on purpose** so `CREATE_NO_WINDOW` stops being inert (its own comment
cites releases#131). So:

- **known direction:** the bit is PRESENT and does nothing (`CREATE_NO_WINDOW` under `DETACHED_PROCESS`).
- **inverse direction:** the bit is ABSENT from the effective flags although it is present in the
  named constant — `BASE_FLAGS` still *contains* `DETACHED_PROCESS`, but this call site does not
  *pass* it. **Reading the constant answers the wrong question; only the call site's mask does.**

Consequence, and it is load-bearing for the `a_tree_teardown` row: a windowless child is **NOT
detached**, so it OWNS a console, so a **conhost sits in the same parentage** any ppid-filtered
scan reads. That is the mechanism behind "3 ppid matches where it had always read 1"
(`daemon.rs:2623-2631`, measured 2026-08-04 by others). **Prediction PRE-REGISTERED with doyle
before the capture: under this call site a companion in the parentage is STRUCTURAL, so an N=20
field-4 distribution centred above 1 is the expected shape, not a defect signature. Falsifier:
min=median=1 kills it and the 3 needs another mechanism.** Unresolved at time of writing — do not
quote the prediction as a result.

**Stale prose caught by the same read:** `SelectionProbe` field 3's doc (`daemon.rs:2457-2460`)
asserts "BASE_FLAGS carries DETACHED_PROCESS, so there is no conhost child to confuse the scan" —
true of the constant, FALSE of what the row spawns (`:2608` uses the windowless helper). Two
comments 165 lines apart disagree and the MEASURED one is right. Doyle routed the one-line fix
onto hertz's item-3 package lane, explicit in the PR body, pairing it with this entry: a reader
who trusts field 3 re-derives the 3-count as an anomaly — the wrong-model-from-coherent-prose
class. See [[a-predicate-without-its-tool-is-not-evidence]], [[name-the-file-and-sha-a-condition-came-from]].

⚠ Count windows with `EnumWindows`, never `Get-Process | MainWindowTitle`. The latter did not
undercount 10 as 1 — it reported **ZERO** ping windows: a process exposes ONE MainWindowTitle and
which of its windows wins is arbitrary, so the single WT row returned was an unrelated agent tab.
An instrument giving a confident clean answer about a population it cannot see. See [[verdict-from-probe-competence]],
[[a-predicate-without-its-tool-is-not-evidence]], [[stacked-defects-mask-each-other]].
