# Demo: what the user actually sees

**Status: living demo report, NOT an authoritative or direction-providing document.** This walks
through specific tested scenarios and shows exactly what text lands on the user's screen. Purpose:
let a human review the actual console output, spot anything confusing or alarming, and catch code
paths that report something unexpected. **This doc reflects the LATEST state of the code only --
it is not a history of how things changed.** When a message's wording changes, replace the quote
in place; don't keep the old one around for context. Ongoing investigation notes, dead ends, and
day-to-day refinement-pass checks belong in `docs/agent-scratchlog.md` (internal working notes),
not here. Unresolved design questions belong in `docs/open-questions.md`, not here.

# --- head (trimmed) ---


**House style, stated once here rather than repeated per-scenario:** this doc describes CURRENT
behavior, not a changelog of how it got that way. Avoid narrating this document's own revision
history inside scenario prose (phrasing like "not yet re-confirmed against a fresh capture," "the
earlier scan of this scenario quoted only X," or hedging a quote as "the CURRENT shipped wording"
as if some other wording still mattered) -- that's bookkeeping for whoever edits this file next,
not something a reader trying to understand the bootstrapper needs. Likewise, don't cite this
repo's own internal backlog-item numbers or "Active Backlog item N" bookkeeping as part of the
user-facing description; point to `docs/agent-lessons-learned.md` /
`docs/agent-interconnect.md` / `docs/agent-closed-backlog.md` directly instead if a pointer is
genuinely useful. **A real historical GOTCHA is a different thing and stays welcome** -- a bug that
shaped current behavior, worth knowing so a reader doesn't rediscover it the hard way, is exactly
the kind of content this doc wants (see Scenario 29, Scenario 39 for two kept in full). The
distinction is between explaining what the reader is looking at right now versus narrating how
this document itself was assembled. When a mechanism is genuinely covered by README.md or
`run_setup.bat`'s own source/comments, cite those first -- they're the product-facing docs a reader
of this file is more likely to already be looking at; reach for the internal `docs/agent-*.md`
engineering docs only for detail that has no README/source equivalent (implementation-level
CMD/PowerShell quirks, cross-component interconnects, and similar maintainer-facing reasoning).

**House rule -- minimum context per scenario:** every scenario (each numbered "Scenario N" or
equivalent standalone walkthrough entry) shows at least 5 distinct user-visible-output quotes
(lines drawn from the actual bootstrapper console/log output shown in the fenced blocks below it),
not counting input/output *file* contents (`.py`, `.toml`, `requirements.txt`, etc.) or raw
NDJSON/status-file dumps shown only as supporting evidence. A scenario this thin on real quotes
usually means not enough was captured to give a reader real context -- add more before considering
it done, even if the mechanism itself is fully explained in prose.

**House rule -- no internal-only text in a user-visible quote:** a fenced block presented as "what
the user sees" must never contain something a real end user, double-clicking `run_setup.bat` with
no environment variables set, would not actually see on their own screen -- this includes literal
`[TEST]` tags, `HP_TEST_FORCE_*`/other env-var names, "simulating"/"for this test" phrasing, or any
other CI-test-harness-only artifact. When a real capture's own trigger was a test hook, say so in
the surrounding PROSE (never inside the quoted block itself), and either omit the corresponding
line entirely (if the real mechanism is silent at that point) or replace it with the genuine
real-user-equivalent line, extrapolated from `run_setup.bat`'s own source and clearly labeled
`[Extrapolated Branch]` if it isn't independently confirmed by a different real capture.

**Sourcing convention:** every quoted block carries one explicit provenance label -- REAL CI
CAPTURE (copied verbatim from a real GitHub Actions job log, cited with run ID, job ID, lane, and
test file), a source excerpt (taken directly from `run_setup.bat`'s current source because no CI
run has exercised that exact wording yet), or, for the composite walkthroughs in Part VII,
`[Extrapolated Branch]` (assembled by splicing several independently-real fragments together, each
already cited in its own originating scenario). A source excerpt or a composite splice is never
described as a job log or presented as a single real capture.

**Scope:** grouped by feature area, ordered roughly the way a real user would actually encounter
each area -- the default happy path and its immediate variations first, narrower and more advanced
topics last -- rather than the order each was originally reviewed. Nine Parts total; see the table
of contents for the full current list. Extend with a new Part as new feature areas get reviewed,
rather than growing any existing Part indefinitely.

**Console vs. `~setup.log`:** the bootstrapper writes to two different places that are easy to
conflate:
- The **live console** (what a real user watching the window sees) -- everything `:log` echoes,
  plus a handful of raw tool-output lines that aren't redirected.
- **`~setup.log`** (the internal file record) -- receives everything the console does, PLUS extra
  content deliberately redirected away from the console (e.g. the full raw PyInstaller warn-file
  dump). A CI test's "bootstrap log" capture (`cmd /c "call run_setup.bat > log 2>&1"`) matches the
  CONSOLE view, not `~setup.log`. Quotes below are from the console-equivalent capture unless
  stated otherwise.

## Table of contents

- [Part I: Default double-click happy path (uv-first, fresh machine, zero flags)](#part-i-default-double-click-happy-path-uv-first-fresh-machine-zero-flags)
  - [Scenario 1: No `.py` files at all -- the graceful `no_python_files` exit](#scenario-1-no-py-files-at-all----the-graceful-no_python_files-exit)
  - [Scenario 2: Pre-flight guards, lock acquisition, and entry detection on a clean run](#scenario-2-pre-flight-guards-lock-acquisition-and-entry-detection-on-a-clean-run)
  - [Scenario 3: Provider acquisition and dependency install (uv-first)](#scenario-3-provider-acquisition-and-dependency-install-uv-first)
  - [Scenario 4: Build, verify, and the final status panel](#scenario-4-build-verify-and-the-final-status-panel)
  - [Scenario 5: The two elective prompts a real user faces after every successful run](#scenario-5-the-two-elective-prompts-a-real-user-faces-after-every-successful-run)
- [Part II: Second run, nothing changed (repeat-run fast paths)](#part-ii-second-run-nothing-changed-repeat-run-fast-paths)
  - [Scenario 6: The EXE fast path (nothing changed at all)](#scenario-6-the-exe-fast-path-nothing-changed-at-all)
  - [Scenario 7: Source touched just enough to force a rebuild, but the environment is reused](#scenario-7-source-touched-just-enough-to-force-a-rebuild-but-the-environment-is-reused)
- [Part III: User configuration and CLI overrides](#part-iii-user-configuration-and-cli-overrides)
  - [Scenario 8: `PVW_PYTHON_EXE` / `PVW_UV_EXE` / `PVW_TARGET_PY` / `PVW_WORKSPACE`](#scenario-8-pvw_python_exe--pvw_uv_exe--pvw_target_py--pvw_workspace)
  - [Scenario 9: `PVW_CONDA_EXE` and its interaction with the conda self-heal flow](#scenario-9-pvw_conda_exe-and-its-interaction-with-the-conda-self-heal-flow)
  - [Scenario 10: Drag-and-drop / CLI entry-file override (REQ-011 same-directory rule + REQ-002 priority)](#scenario-10-drag-and-drop--cli-entry-file-override-req-011-same-directory-rule--req-002-priority)
- [Part IV: Adversarial and recovery branches](#part-iv-adversarial-and-recovery-branches)
  - [Scenario 11: Network connectivity check and transient-retry (REQ-013 + REQ-022)](#scenario-11-network-connectivity-check-and-transient-retry-req-013--req-022)
  - [Scenario 12: Corrupted-conda self-heal (detect / decline / accept)](#scenario-12-corrupted-conda-self-heal-detect--decline--accept)
  - [Scenario 13: Miniconda install chain (AllUsers -> JustMe -> both-failed)](#scenario-13-miniconda-install-chain-allusers---justme---both-failed)
  - [Scenario 14: Standalone embed-tier download (REQ-009 Tier 5) -- decline and real success](#scenario-14-standalone-embed-tier-download-req-009-tier-5----decline-and-real-success)
  - [Scenario 15: REQ-009 provider cascade -- one real run showing the FULL chain](#scenario-15-req-009-provider-cascade----one-real-run-showing-the-full-chain)
  - [Scenario 16: `--hidden-import` auto-recovery (success and exhaustion)](#scenario-16---hidden-import-auto-recovery-success-and-exhaustion)
  - [Scenario 17: Warnfix repair loop (success, and the failure that feeds the cascade)](#scenario-17-warnfix-repair-loop-success-and-the-failure-that-feeds-the-cascade)
  - [Scenario 18: Pre-flight guards actually firing](#scenario-18-pre-flight-guards-actually-firing)
  - [Scenario 19: Concurrent-instance lock contention (REQ-024)](#scenario-19-concurrent-instance-lock-contention-req-024)
- [Part V: Remaining branches (dependency source precedence, write-back, and misc)](#part-v-remaining-branches-dependency-source-precedence-write-back-and-misc)
  - [Scenario 20: Git config merge (`.gitignore`/`.gitattributes`, REQ-015)](#scenario-20-git-config-merge-gitignoregitattributes-req-015)
  - [Scenario 21: Python-version precedence (REQ-004) and dependency-source precedence (`pyproject.toml`)](#scenario-21-python-version-precedence-req-004-and-dependency-source-precedence-pyprojecttoml)
  - [Scenario 22: PEP 723 dependency write-back (REQ-005.11) -- the fresh-install trigger](#scenario-22-pep-723-dependency-write-back-req-00511----the-fresh-install-trigger)
  - [Scenario 23: `HP_PVW_KNOWN_IDEMPOTENT` execute-mode discovery (REQ-005.13)](#scenario-23-hp_pvw_known_idempotent-execute-mode-discovery-req-00513)
  - [Scenario 24: NI-VISA detection and install outcome (REQ-008)](#scenario-24-ni-visa-detection-and-install-outcome-req-008)
  - [Scenario 25: pandas/openpyxl heuristic dependency augmentation (REQ-005.8)](#scenario-25-pandasopenpyxl-heuristic-dependency-augmentation-req-0058)
  - [Scenario 26: Conda base periodic update](#scenario-26-conda-base-periodic-update)
  - [Scenario 27: REQ-014 system-Python consent -- ACCEPT](#scenario-27-req-014-system-python-consent----accept)
- [Part VI: Additional branches found in a full-file sweep](#part-vi-additional-branches-found-in-a-full-file-sweep)
  - [Scenario 28: Interactive entry picker -- multiple `.py` files, no clear winner (REQ-002)](#scenario-28-interactive-entry-picker----multiple-py-files-no-clear-winner-req-002)
  - [Scenario 29: Pre-flight syntax-error rejection (REQ-021), and a real bug it exposed](#scenario-29-pre-flight-syntax-error-rejection-req-021-and-a-real-bug-it-exposed)
  - [Scenario 30: REQ-007 system-Python build consent, and the resulting no-EXE interpreter path](#scenario-30-req-007-system-python-build-consent-and-the-resulting-no-exe-interpreter-path)
  - [Scenario 31: EXE smoke-run diagnostic hints (companion to Scenario 16)](#scenario-31-exe-smoke-run-diagnostic-hints-companion-to-scenario-16)
- [Part VII: Full startup-to-shutdown walkthroughs](#part-vii-full-startup-to-shutdown-walkthroughs)
  - [Scenario 32: Full walkthrough -- the ordinary happy path, start to shutdown](#scenario-32-full-walkthrough----the-ordinary-happy-path-start-to-shutdown)
  - [Scenario 33: Full walkthrough -- uv can't resolve a dependency, cascades to conda, which does](#scenario-33-full-walkthrough----uv-cant-resolve-a-dependency-cascades-to-conda-which-does)
  - [Scenario 34: Full walkthrough -- warnfix repair and rebuild, start to finish](#scenario-34-full-walkthrough----warnfix-repair-and-rebuild-start-to-finish)
  - [Scenario 35: Full walkthrough -- `--hidden-import` auto-recovery succeeds on the first rebuild](#scenario-35-full-walkthrough------hidden-import-auto-recovery-succeeds-on-the-first-rebuild)
  - [Scenario 36: Full walkthrough -- `HP_PVW_KNOWN_IDEMPOTENT`, with the actual input and output files](#scenario-36-full-walkthrough----hp_pvw_known_idempotent-with-the-actual-input-and-output-files)
- [Part VIII: AV-Safe Build Path (Nuitka fallback)](#part-viii-av-safe-build-path-nuitka-fallback)
  - [Scenario 37: PyInstaller build fails, Tier A (Nuitka) fallback succeeds](#scenario-37-pyinstaller-build-fails-tier-a-nuitka-fallback-succeeds)
  - [Scenario 38: PyInstaller build fails, Tier A fallback ALSO fails (tier exhaustion)](#scenario-38-pyinstaller-build-fails-tier-a-fallback-also-fails-tier-exhaustion)
    - [38a. `execfail` -- the PyInstaller build command itself fails](#38a-execfail----the-pyinstaller-build-command-itself-fails)
    - [38b. `output_vanish` -- PyInstaller succeeds, then the EXE disappears immediately](#38b-output_vanish----pyinstaller-succeeds-then-the-exe-disappears-immediately)
  - [Scenario 39: Tier A + hidden-import auto-recovery skip guard](#scenario-39-tier-a--hidden-import-auto-recovery-skip-guard)
  - [Scenario 40: Requirement 9 -- elective "want an optimized build too?" offer](#scenario-40-requirement-9----elective-want-an-optimized-build-too-offer)
    - [40a. `accept` -- a real optimized build succeeds and is swapped in](#40a-accept----a-real-optimized-build-succeeds-and-is-swapped-in)
    - [40b. `forcefail` -- accepted, but the build fails; original EXE is left untouched](#40b-forcefail----accepted-but-the-build-fails-original-exe-is-left-untouched)
    - [40c. `swapfail` -- verified build, but the final swap step fails; original EXE is left untouched](#40c-swapfail----verified-build-but-the-final-swap-step-fails-original-exe-is-left-untouched)
    - [40d. `decline` -- default/CI path, prompt shown but nothing built](#40d-decline----defaultci-path-prompt-shown-but-nothing-built)
    - [Reactive-only failure hint (both Tier A and requirement 9's real-build-failure paths)](#reactive-only-failure-hint-both-tier-a-and-requirement-9s-real-build-failure-paths)
- [Part IX: CLI interactivity, argv passthrough & honest messaging](#part-ix-cli-interactivity-argv-passthrough--honest-messaging)
  - [Scenario 41: Interactive verification -- live-tee, activity-aware kill, and the quit-prompt hint](#scenario-41-interactive-verification----live-tee-activity-aware-kill-and-the-quit-prompt-hint)
  - [Scenario 42: Argv passthrough (REQ-026) -- launch arguments through the bootstrapper](#scenario-42-argv-passthrough-req-026----launch-arguments-through-the-bootstrapper)
  - [Scenario 43: Honest ambiguous-exit messaging (REQ-027)](#scenario-43-honest-ambiguous-exit-messaging-req-027)
    - [43a. No-EXE path, interpreter also failed](#43a-no-exe-path-interpreter-also-failed)
    - [43b. Cached-EXE fast path, kept despite a non-zero exit](#43b-cached-exe-fast-path-kept-despite-a-non-zero-exit)
- [Part X: Confirmed layered multi-mechanism recovery chain (real E2E evidence)](#part-x-confirmed-layered-multi-mechanism-recovery-chain-real-e2e-evidence)
  - [Scenario 44: Full walkthrough -- cascade, warnfix, hidden-import recovery, and native-DLL bundling, all in one real run](#scenario-44-full-walkthrough----cascade-warnfix-hidden-import-recovery-and-native-dll-bundling-all-in-one-real-run)

---

## Part I: Default double-click happy path (uv-first, fresh machine, zero flags)

**Scope note:** this Part opens the document with what a completely ordinary run looks like end to
end -- one `.py` file, no test hooks, no prior state, no flags, uv reachable (the REQ-009 default,
`uv -> conda -> embed -> venv -> system`). This is what the large majority of real users actually
see; narrower/advanced feature areas (the AV-Safe Build Path's Nuitka fallback, CLI-interactivity
internals) are covered later, in Part VIII and Part IX. Evidence is pulled from a recent fully-green
run (`30328748330`, commit `5872028`, all lanes
green) rather than any single dedicated "happy path" test -- no such test exists as one file, so
each piece below is sourced from whichever real, non-`HP_CI_SKIP_ENV` sub-bootstrap in that run
exercises it most faithfully (mainly `tests/selfapps_envsmoke.ps1`'s real env-smoke sub-bootstrap
and `tests/selftest.ps1`'s stub-app fast path), cited individually per item.

**A structural caveat that applies to every scenario in this Part, stated once here instead of
repeated per-scenario:** every CI lane sets `HP_CI_LANE` at the job level (`batch-check.yml`),
which silently auto-declines every consent prompt in the file the instant it's reached, with no
wait. A genuine double-click has none of `HP_CI_LANE`/`NOINPUT`/`HP_NONINTERACTIVE` set, so any
prompt this Part encounters would, for a real user, actually pause and wait for a keypress (or,
for the one genuinely timed gate, wait up to its timeout) instead of resolving instantly. Scenario
11 below covers this distinction in detail for the two prompts that fire on this exact happy path;
it applies identically to every other consent gate documented elsewhere in this file.

### Scenario 1: No `.py` files at all -- the graceful `no_python_files` exit

**What's tested:** `self.empty_repo.msg` (`tests/selftest.ps1`, `real` lane, real, passing).

**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane).

Referenced throughout this document (e.g. Scenario 3's note that this repo's own bootstrapper
root, which has no loose `.py` files, exercises this exact path) but never shown directly: when
`PYCOUNT` (a plain `dir /b /a-d *.py` count) is zero, the bootstrapper takes the shortest path in
the entire file -- no provider selection, no dependency install, nothing network-touching at all,
skipping straight to a graceful, successful exit:

```
[INFO] Environment name: _selftest_empty
[INFO] Host OS: Microsoft Windows [Version 10.0.26100.32995]
[INFO] Host PowerShell: 5.1.26100.32995
[INFO] Python file count: 0
Python file count: 0
No Python files detected; skipping environment bootstrap.
[INFO] No Python files detected; skipping environment bootstrap.
```

(the last message genuinely appears twice, back to back, in the real captured log -- once as a
plain `echo` straight to console with no timestamp, once through `:log`'s own timestamped form
written to both console and `~setup.log`; the block above shows both, with the second line's
real timestamp prefix, e.g. `Tue 07/28/2026  4:29:15.97`, omitted here since it carries no
information beyond confirming the two lines are adjacent). `~bootstrap.status.json` reads
`{"state":"no_python_files","exitCode":0,
"pyFiles":0}` -- a real user who double-clicks the bootstrapper in an empty folder, or in the
wrong folder entirely, gets a clear, immediate, non-alarming message rather than the bootstrapper
attempting (and inevitably failing) to build an environment for nothing.

### Scenario 2: Pre-flight guards, lock acquisition, and entry detection on a clean run

**What's tested:** no single dedicated test asserts the CLEAN (non-firing) pass of these checks --
`docs/agent-ndjson.md` only registers rows for the *firing* branches (`self.warn.onedrive`,
`self.warn.longpath`, `self.warn.sysdir`, `self.stub.lock_held_decline`, etc.). The evidence below
is the incidental byproduct of `tests/selfapps_envsmoke.ps1`'s full, real bootstrap, which captures
every byte of console output via `cmd /c .\run_setup.bat > '~envsmoke_bootstrap.log'`.

**Source:** REAL CI CAPTURE, run `30328748330`, `tests/~envsmoke/~envsmoke_bootstrap.log`
(published diagnostics site), identical across all 6 lanes checked (`real`, `uv`, `conda-full`,
`justme-test`, `contract-uv`, `contract-uv-fail`):

```
Tue 07/28/2026  4:29:43.96 [INFO] REQ-015: Appending standard ignores to .gitignore.
Tue 07/28/2026  4:29:43.97 [INFO] REQ-015: Appending standard attributes to .gitattributes.
```

Before that first line, nothing prints -- `HP_APP_ARGS` capture (REQ-026, pure variable
assignment), the workspace-path-exists check, `cd /d`, `HP_SCRIPT_ROOT` construction, and the
top-of-file UNC-path check (`if "%HP_SCRIPT_LAUNCH_DIR:~0,2%"=="\\"`, which prints a much louder
`*** WARNING: UNC/network paths detected...` banner when it genuinely fires) are all silent on an
ordinary, non-UNC path. Both `.gitignore`/`.gitattributes` lines come from the same
`:merge_git_config` call (the first output-producing call after `run_setup.bat` sets up its own
log file) -- on a fresh scratch directory neither file yet has the bootstrapper's signature
comment, so both append branches fire back to back.

Immediately after that, the same real capture shows the environment-name and host-diagnostics
lines every run prints (same run, same underlying artifact -- also quoted in Scenario 6 below,
where the exact values are unchanged since they describe the same CI host, not anything specific
to a fresh vs. repeat run):

```
[INFO] Environment name: _envsmoke
[INFO] Host OS: Microsoft Windows [Version 10.0.26100.32995]
[INFO] Host PowerShell: 5.1.26100.32995
```

**The four REQ-025-family pre-flight guards (path-length, OneDrive, system-directory, disk-space --
also part of that unlabeled prologue) are completely silent unless they fire.** Confirmed by both
the absence of any related text anywhere in the captured log, AND by reading the source: none of
the four has an `else` branch that prints a success/clean message -- each is a bare `if (...) (
echo/log ... )` with nothing on the false path. A real user on an ordinary setup (short path, not
under OneDrive, not under `Windows`/`Program Files`, plenty of free disk) sees zero output from
any of these four checks.

**`:acquire_lock` is equally silent on an uncontended acquire.** The `mkdir "%HP_LOCK_DIR%"` call
succeeds immediately (no prior lock directory), jumps straight to `:lock_acquired`, writes a
transient `~bootstrap.lock\owner.txt` marker (a file, not console output), and returns -- every
`echo`/`:log` call inside `:acquire_lock` lives inside the "another instance is already running"
branch, only reached on a genuine `mkdir` failure. The lock directory (and its `owner.txt`) is
gone again by the time the run completes (`:release_lock`, called from both `:die` and `:success`,
is equally silent).

**Entry detection for the common single-`.py`-file case** (`:determine_entry` ->
`tools/find_entry.py` -> `:record_chosen_entry`) produces exactly one line, sourced from
`tests/~envsmoke/~setup.log` (same run, the real, non-`HP_CI_SKIP_ENV` `:determine_entry` call
site inside `:after_env_bootstrap` -- not the separate `HP_CI_SKIP_ENV`-only `:ci_skip_entry`
implementation that
`tests/selfapps_entry.ps1`/`selfapps_single.ps1` exercise, which looks textually similar but is a
different code path):

```
Chosen entry: app.py
```

This is identical whether the sole `.py` file has a preferred name (`main.py`/`app.py`/`run.py`/
`cli.py`) or an arbitrary one (`tools/find_entry.py`'s `len(files) == 1` branch handles both the
same way, with zero stderr diagnostics either way) -- the interactive picker
(`:pick_entry_interactive`) is only ever reached when more than one `.py` file is ambiguous, which
this scenario deliberately doesn't have. `:determine_entry` actually runs twice in a normal
bootstrap (once early, for PEP 723/autopep723 discovery purposes, well before `:after_env_bootstrap`,
and once again at the real entry-selection call site inside `:after_env_bootstrap` that produces
this console line) -- only the second call's result is what a user sees echoed.

---

### Scenario 3: Provider acquisition and dependency install (uv-first)

**What's tested:** `tests/selfapps_envsmoke.ps1`'s real sub-bootstrap (`self.env.smoke.*` rows),
uv-first lane, against a stub `app.py` that genuinely does `import colorama` -- a real, if small,
dependency, chosen deliberately over a zero-dependency stub so the dependency-discovery/install
machinery actually has something to do.

**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` ("real" lane),
`tests/~envsmoke/~envsmoke_bootstrap.log` (what a real user's terminal shows) and
`tests/~envsmoke/~setup.log` (internal detail file, receives everything the console does PLUS
extra content this repo deliberately never puts on-screen -- see this doc's own "Console vs.
`~setup.log`" note at the top). Both are cited below, labeled.

**IMPORTANT: this exact CI run's job-level "Bootstrap environment (run_setup.bat)" step is NOT
representative evidence for this scenario** -- that step runs `run_setup.bat` against the
bootstrapper repo's OWN root (no loose `.py` files there), so it takes the `no_python_files`
graceful-exit path and produces nothing relevant. The genuinely representative evidence is the
"Self-test: real env smoke (CI-only)" step's own inner sub-bootstrap, which runs `run_setup.bat`
for real against a scratch app directory instead.

**uv acquisition** (console, first-ever run, no cached `~uv_bin` -- every fresh CI scratch dir
starts this way, matching a real user's first-ever double-click):

```
[INFO] uv: UV_PYTHON_PREFERENCE=only-managed (orchestration uses managed Python).
[INFO] uv: downloading to ~uv_bin...
[INFO] Downloading uv from https://github.com/astral-sh/uv/releases/latest/download/uv-x86_64-pc-windows-msvc.zip...
[INFO] uv: acquired at ~uv_bin\uv.exe
[INFO] uv-first: Miniconda download skipped.
```

# --- trimmed ---

[WARN] Verifying the built standalone EXE (fallback build system) now: it is force-stopped after about 30 seconds even if running perfectly, so do not start real work in it yet or any unsaved work will be lost.
[WARN] Verifying the built standalone EXE (PyInstaller) now: it is force-stopped after about 30 seconds even if running perfectly, so do not start real work in it yet or any unsaved work will be lost.
```

Note what's missing compared to the main-run wording above: no mention of output extending the
wait, no guidance toward the program's own quit/exit option -- a real interactive program hitting
this check during hidden-import recovery is still force-stopped at 30 seconds flat, output or not.

### Scenario 42: Argv passthrough (REQ-026) -- launch arguments through the bootstrapper

Extra arguments after the entry file on `run_setup.bat`'s own command line (up to 8) are forwarded
verbatim to the target program at every real launch site -- the cached-EXE fast path, the fresh EXE
verification, the no-EXE interpreter run, and the post-execution checkpoint's elective second run.
This is a documented, opt-in escape hatch (no detection or heuristics involved) for a program that
needs `--flag value`-style launch arguments to run correctly, on top of this bootstrapper's usual
zero-argument double-click flow.

**The postflight guidance a user sees after a successful EXE build** (Scenario 37's full panel
above shows this in context):

```
   Does your program need launch arguments (e.g. --input file.csv)? Run
   this bootstrapper again with them added after the entry file, e.g.
     run_setup.bat "<entry.py>" --input file.csv
   and they will be forwarded to your program during THIS setup run
   (up to 8 extra arguments). This does not change how a plain
   double-click of dist\<env>.exe launches it afterward -- for that,
   make a Windows shortcut to the .exe and add the arguments to its
   Target field, or launch it yourself from a Command Prompt.
```

**The equivalent guidance on the no-EXE path** (direct-interpreter-invocation form, part of
Scenario 43a's panel below):

```
   Need launch arguments? Add them directly after that command, e.g.
     "<python>" "<entry.py>" --input file.csv
```

Both are additive to the launch commands already shown in each panel, not a separate prompt --
matching this bootstrapper's general rule that env-var/CLI flags only ever add an opt-in path or
suppress an optional step, never gate a behavior the Prime Directive needs (see CLAUDE.md's
`[REQ-019]`).

### Scenario 43: Honest ambiguous-exit messaging (REQ-027)

Both panels below fire only when a verification run ends AMBIGUOUSLY -- the program exited with an
error, and no automatic repair (`--hidden-import` auto-recovery, the REQ-009 dependency-resolution
cascade) fixed it. Neither panel claims to know WHY: a bug in the program's own code, something
this bootstrapper missed, or an unresolved dependency are all indistinguishable from here, and both
panels say so plainly rather than guessing. This is messaging only -- `~bootstrap.status.json`
semantics, the process exit code, and consent-gate behavior are all unchanged.

Both are new enough (shipped, then refined once more for wording, entirely within this same
session) that no CI run has yet produced a console capture including the current wording -- both
quotes below are sourced directly from `run_setup.bat`, not a job log.

#### 43a. No-EXE path, interpreter also failed

Fires when BOTH PyInstaller and the Nuitka fallback fail to package the app outright, AND the
interpreter fallback that runs next (the only way left to run the program at all) also exits
non-zero -- the scenario Scenario 38a's own `execfail` sub-case would hit if its trivial stub script
didn't happen to exit cleanly. Source: `:print_no_exe_briefing`'s `:noexe_caveat` branch,
`run_setup.bat`:

```
============================================================
 NO STANDALONE .EXE -- AND WE CAN'T CONFIRM YOUR CODE RAN CLEANLY
============================================================
 We could not package your app into a double-clickable .exe
 (see the ERROR message above for why). We also just ran it
 directly via the prepared Python environment, and it exited
 with an error (see the [STATUS] line above) -- so we can't
 tell whether that's a bug in the Python code we tried to run
 or something this bootstrapper missed. Your environment and
 dependencies ARE still installed correctly; run it yourself
 below to see the full output.

 RUNNING YOUR APP (without an .exe) -- the most direct option
   "<python>" "<entry.py>"
   Need launch arguments? Add them directly after that command, e.g.
     "<python>" "<entry.py>" --input file.csv

 Want to try different arguments through the bootstrapper itself
 instead? Your already-installed environment is reused either
 way; it will just attempt the .exe build again too:
   run_setup.bat "<entry.py>" arg1 arg2

 KEEP these files with your project:
   requirements.txt  -- packages your app depends on
   runtime.txt       -- Python version pin

 SAFE TO DELETE to reclaim disk space:
   .*_env\ folders   -- environment directories
   ~* files          -- tilde-prefix work files (e.g. ~setup.log)
============================================================
```

The direct-run command stays the visually primary option (matches this panel's own established
preference for running the program directly over going back through the bootstrapper); the
bootstrapper-rerun mention is deliberately secondary and uses the real entry filename (`%HP_ENTRY%`
is reliably set by this point in the pipeline -- unlike Scenario 43b below).

When the interpreter run instead exits CLEANLY (the common case, and what Scenario 38a's own capture
shows), this panel's header and opening paragraph read differently -- plain "YOUR CODE RAN -- BUT
NO STANDALONE .EXE WAS PRODUCED", with no claim of an unconfirmed run -- but the rest of the panel
(launch commands, KEEP/SAFE TO DELETE lists) is identical either way.

#### 43b. Cached-EXE fast path, kept despite a non-zero exit

Fires when the fail-fast probe classifies a REUSED `dist\<env>.exe` (the top-of-file fast path,
before any provider/entry-file logic runs) as alive/healthy -- so it's kept, never
discarded-and-rebuilt -- and it later exits non-zero. Before this fix, this exact case had no
postflight signal at all beyond one `[WARN]` log line buried among other console output. Source:
`:print_fastpath_ambiguous_note`, `run_setup.bat`:

```
============================================================
 SETUP COMPLETE -- BUT WE CAN'T CONFIRM YOUR LAST RUN WORKED
============================================================
 Your existing standalone application was reused (dist\<env>.exe),
 and it exited with an error just now (see the [STATUS] line
 above) -- so we can't tell whether that's a bug in the Python
 code we tried to run, or something else. Your environment and
 dependencies ARE still installed correctly.

 RUNNING YOUR APP
   Double-click dist\<env>.exe to run it, or run it from a
   Command Prompt to see the full output.

 WANT TO TRY AGAIN? You do not have to start over from scratch --
   just run this bootstrapper again the same way you did before;
   your already-installed environment and built .exe are reused.

 WANT A FRESH BUILD instead (re-checks all dependencies from scratch)?
   Delete dist\<env>.exe and run this bootstrapper again.
============================================================
```

This panel is a PLAIN INFORMATIONAL PRINT, never a consent gate -- the cached-EXE fast path is
deliberately zero-friction for prompts (see README.md's "Fast path is the user's run
(frictionless)" bullet), and this doesn't violate that since it never asks a question.

**No entry filename appears anywhere in this panel, unlike 7a's rerun mention -- deliberately.**
`HP_ENTRY` is not set yet at the point the top-of-file fast path runs (it fires before
`:determine_entry` ever executes, since the cached EXE is self-contained and doesn't need the
original source filename to relaunch), so naming one here would show blank or stale text. The two
rerun options are worded to distinguish a genuinely different tradeoff instead: rerunning WITHOUT
deleting the EXE reuses it (via the same fast path that got the user here) with no promise about
whether it's actually faster overall, while deleting it first forces a full, slower, from-scratch
dependency check.

---

## Part X: Confirmed layered multi-mechanism recovery chain (real E2E evidence)

### Scenario 44: Full walkthrough -- cascade, warnfix, hidden-import recovery, and native-DLL bundling, all in one real run

**What's tested:** `self.layered_e2e.chain` (`tests/selfapps_layered_e2e.ps1`, `cache` lane,
non-gating). A single real app whose three declared dependencies (`pygrib`, `xlrd`, `colorama`)
happen to trigger four separate, independently-documented repair mechanisms (Scenarios 15, 17, 16,
and the native-DLL bundling loop covered in `docs/agent-interconnect.md`) back to back in one
continuous run, with no test-only failure injection anywhere in the chain -- every failure below is
a genuine consequence of what these three packages actually need on Windows.

**Source:** REAL CI CAPTURE, run `31264219121`, job `93119869344` (`cache` lane).

The run starts under uv (the default first provider), installs cleanly detected dependencies, then
hits its first real gap -- `pygrib` has no Windows wheel on PyPI:

```
[BOOT] REQ-009: Selected Python provider: UV.
[INFO] runtime.txt written: python-3.14.7
[INFO] pipreqs 0.4.13 installed successfully; using it for dependency discovery.
[WARN] uv pip install -r requirements.txt failed; some packages may be missing.
[INFO] Building standalone executable -- this may take a minute or two...
[INFO] PyInstaller produced dist\_selftest_layered_e2e.exe
[INFO] warnfix: some modules could not be automatically bundled (full list in ~warnfile.txt / ~setup.log); modules such as posix, fcntl, grp, pwd, resource, _scproxy, _posixsubprocess, collections.abc, and _frozen_importlib_external are expected on Windows and are filtered out automatically; cStringIO and StringIO (Python-2-only compatibility shims some packages still reference) are filtered out automatically too.
[REPAIR] missing modules detected; installing and rebuilding.
[INFO] Attempting to install: xlrd
[INFO] Installed: xlrd
[INFO] Attempting to install: pygrib
[WARN] Repair failed: pygrib
[WARN] One or more repair attempts failed
[INFO] Rebuilding standalone executable after warnfix -- this may take a minute or two...
[REPAIR] rebuild complete after warnfix.
```

Warnfix (Scenario 17) genuinely fixed `xlrd` in the same round it genuinely failed on `pygrib` --
both outcomes are real, not staged. With one dependency still unresolved under uv, the cascade
(Scenario 15) offers, and is accepted -- but the CURRENT (still uv-built) EXE is verified once more
before the cascade actually switches providers, and it fails exactly as expected on the package
warnfix never resolved:

```
[WARN] REQ-009: warnfix left modules unresolved under provider uv.
[INFO] REQ-009: cascade candidate detected.

*** Some dependencies could not be installed under the current Python provider. ***

[INFO] REQ-009: cascade consent: accepted.
[INFO] REQ-009: cascade approved; will re-attempt under the next provider tier.
[INFO] EXE smokerun: testing dist\_selftest_layered_e2e.exe
ModuleNotFoundError: No module named 'pygrib'
[WARN] EXE smokerun: exited 1 (non-zero)
[HINT][HIDDEN_IMPORT] Hidden import likely missing: pygrib
[INFO] Entry smoke exit=1
[STATUS] Run Status: FAILED (Exit Code: 1)
[INFO] REQ-009: cascading provider uv to conda; re-attempting dependencies.
*** [INFO] Trying the next Python provider (conda) to resolve dependencies...
```

That `[HINT][HIDDEN_IMPORT]` line is the always-on diagnostic hint (`:exe_smokerun_hints`), not the
strict `--hidden-import` auto-recovery loop (Scenario 16) actually firing -- `pygrib` was never
successfully installed under uv (the warnfix repair attempt for it genuinely failed, above), and
that loop's own gate requires the target module to be genuinely installed in the build interpreter
before it will act (see "--hidden-import auto-recovery must stay STRICT" in
`docs/agent-lessons-learned.md`). It correctly declines here and lets the cascade handle it instead.

Conda-forge genuinely has a real Windows build of `pygrib` uv/PyPI don't -- the second dependency
install succeeds cleanly this time, and the bootstrapper rebuilds. But `pygrib`'s conda build
depends on a native library (`eccodes.dll`) that PyInstaller's own static analysis bundles the
compiled Python extension for, without ever discovering the separate DLL it links against -- the
native-DLL bundling loop reacts to PyInstaller's own build-time warning about this, catching it
BEFORE the rebuilt EXE is ever run for the first time under conda:

```
[BOOT] REQ-009: Selected Python provider: Conda (Portable).
[INFO] runtime.txt written: python-3.14.6
[INFO] pipreqs 0.4.13 installed successfully; using it for dependency discovery.
[INFO] Building standalone executable -- this may take a minute or two...
[INFO] PyInstaller produced dist\_selftest_layered_e2e.exe
[INFO] warnfix: some modules could not be automatically bundled (full list in ~warnfile.txt / ~setup.log); modules such as posix, fcntl, grp, pwd, resource, _scproxy, _posixsubprocess, collections.abc, and _frozen_importlib_external are expected on Windows and are filtered out automatically; cStringIO and StringIO (Python-2-only compatibility shims some packages still reference) are filtered out automatically too.
[REPAIR][DLL_BUNDLE] Bundling native DLL dependency: eccodes.dll (found at C:\Users\Public\Documents\Miniconda3\envs\_selftest_layered_e2e\Library\bin\eccodes.dll); rebuilding EXE (iter 1/3).
[REPAIR][DLL_BUNDLE] Native-DLL bundling complete (1 DLL(s) added); EXE will be re-verified next.
```

Bundling the DLL fixes `pygrib`'s own loading problem, but the FIRST time this rebuilt EXE actually
runs under conda, it exposes a SECOND, deeper gap: `pygrib`'s own compiled extension needs `numpy`,
invisible to PyInstaller's static scan the same way `pygrib` itself was under uv -- `--hidden-import`
auto-recovery (Scenario 16) reacts, and while fixing that, one of `pygrib`'s OWN submodules (pulled
in by the fix's own `--collect-submodules` pairing) turns out to need `pyproj`, which needs a SECOND
native DLL (`proj_9.dll`) never checked before this exact rebuild -- the bootstrapper's repair loops
hand off to each other across three more rounds, each a real, unstaged rebuild:

```
[INFO] EXE smokerun: testing dist\_selftest_layered_e2e.exe
ModuleNotFoundError: No module named 'numpy'
[WARN] EXE smokerun: exited 1 (non-zero)
[REPAIR][HIDDEN_IMPORT] Adding --hidden-import=numpy --collect-submodules=numpy; rebuilding EXE (iter 1/3).
[REPAIR][HIDDEN_IMPORT] Adding --hidden-import=pyproj --collect-submodules=pyproj; rebuilding EXE (iter 2/3).
[REPAIR][DLL_BUNDLE] Bundling native DLL dependency: proj_9.dll (found at C:\Users\Public\Documents\Miniconda3\envs\_selftest_layered_e2e\Library\bin\proj_9.dll); rebuilding EXE (iter 1/3).
[REPAIR][DLL_BUNDLE] Native-DLL bundling complete (1 DLL(s) added); EXE will be re-verified next.
[REPAIR][HIDDEN_IMPORT] Adding --hidden-import=colorama --collect-submodules=colorama; rebuilding EXE (iter 1/3).
[REPAIR][HIDDEN_IMPORT] EXE verified after hidden-import recovery.
```

That last line -- `colorama` -- is the third declared dependency, imported only via
`importlib.import_module` in this app's own source, so it was never visible to PyInstaller's
static analysis until the `pyproj`/DLL fix let the app get far enough to actually hit that import.
With every gap now resolved, the EXE finally verifies clean and the run completes normally:

```
[INFO] EXE smokerun: exited 0 (ok)
[INFO] Entry smoke exit=0
[STATUS] Run Status: SUCCESS (Exit Code: 0)

*** Verification finished -- see the Run Status above. ***
*** You can run your program again now via the interpreter as an extra diagnostic check. (Optional) ***
[INFO] REQ-018: post-execution checkpoint (exe): declined (run footprint stays at one execution).

*** Your app is ready. ***
*** Want to build an optimized version too? It takes a bit longer to build right now, ***
*** but it starts up more reliably on Windows and runs faster once it is built. (Optional, safe to skip) ***
[INFO] Optimized build: declined.

============================================================
 SETUP COMPLETE
============================================================
 Your standalone application is ready:
   dist\_selftest_layered_e2e.exe

 RUNNING YOUR APP
   Double-click dist\_selftest_layered_e2e.exe to run it.
   You can also run it directly via the interpreter at any time:
     "C:\Users\Public\Documents\Miniconda3\envs\_selftest_layered_e2e\python.exe" "app.py"
```

**Why this run matters beyond any single mechanism**: every other scenario in this document proves
ONE repair mechanism works in isolation. This is the only real capture in this repo's CI history
showing all four -- provider cascade, warnfix, `--hidden-import` recovery, and native-DLL
bundling -- genuinely handing off to each other within a single bootstrap run, each one's own fix
exposing the next real gap underneath it, exactly the way a beginner's own unpredictable dependency
tree could in practice. A user hitting this exact chain would see roughly nine total rebuild
attempts across two Python providers over about ten minutes of unattended waiting, then a normal
"SETUP COMPLETE" panel with no indication anything unusual happened -- everything above the final
panel is diagnostic detail this document surfaces for review, not something a real user needs to
read or understand.

---