From 0bf6bb757bb93e0826b2e12b6576aedff9bed7c6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 03:32:55 +0000 Subject: [PATCH 1/7] Release-prep pass: reorg demo doc, fix stale content, small product fixes docs/demo-bootstrapper-output.md: - Complete the owner-requested reorg TODO: move the "no .py files" scenario to the front as the doc's simplest, most foundational case; push the AV-Safe Build Path and CLI-interactivity Parts to the end. All 38 scenarios renumbered, table of contents and cross-references rebuilt to match, done as a flow-only pass with no scenario content changed. - Add a house-style note to the intro: describe current behavior, not the doc's own revision history -- drop hedges like "not yet re-confirmed against a fresh capture" and internal backlog-item bookkeeping from scenario prose; real historical gotchas stay welcome. - Remove a stale "[WARN] UNC paths not supported" console line from three capture blocks (the broken check that produced it was already removed from run_setup.bat) and trim the now-inapplicable explanation. - Update the long-path guard writeup to reflect this session's fix to self.warn.longpath (now correctly reports skip:true instead of an inconclusive pass) and fill in a concrete example path length. - Update PVW_PYTHON_EXE/PVW_WORKSPACE and the embed-tier download-retry scenarios to reflect real CI coverage added this session (previously documented as untested/extrapolated). - Add a new Part with five full startup-to-shutdown walkthrough panels: the ordinary happy path, uv cascading to conda on a dependency-resolve failure, warnfix repair+rebuild, hidden-import auto-recovery, and HP_PVW_KNOWN_IDEMPOTENT with actual input/output file contents. run_setup.bat: - :pick_entry_interactive now prints the same "Tip: to skip this question next time" guidance in the >9-candidate-files branch, not just the normal picker menu -- it's the guidance a user who just hit that limit needs most. tools/exe_hint_rerun.ps1: - Update a stale header comment: the descendant-holds-the-pipe taskkill /T path is now confirmed exercised on real Windows CI (two lanes on PR #410), not just the drain-wait fallback. docs/agent-closed-backlog.md: - Add a note that Part/Scenario citations in historical entries reflect the demo doc's structure at the time each entry was written, since the reorg above renumbered everything. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW --- docs/agent-closed-backlog.md | 7 + docs/demo-bootstrapper-output.md | 3967 ++++++++++++++++-------------- run_setup.bat | 7 +- tools/exe_hint_rerun.ps1 | 9 +- 4 files changed, 2135 insertions(+), 1855 deletions(-) diff --git a/docs/agent-closed-backlog.md b/docs/agent-closed-backlog.md index 901d1d3e..420aab38 100644 --- a/docs/agent-closed-backlog.md +++ b/docs/agent-closed-backlog.md @@ -13,6 +13,13 @@ CLAUDE.md's ~4200 lines (>60%) despite being pure historical record with no forw action attached to any entry. Nothing here needs re-reading by default; it exists so a specific past decision or fix can be looked up when its details actually matter. +**A `docs/demo-bootstrapper-output.md` "Part N, Scenario N" citation below reflects that doc's +structure AT THE TIME the entry was written, not necessarily its current numbering** -- that file +went through a flow-only reorg pass (2026-08-02) that renumbered every Part and Scenario, and will +likely be reorganized again as it grows. Treat a Part/Scenario citation here as "roughly where to +look," not a precise current coordinate; this file is an append-only historical record, so its own +entries are not retroactively renumbered to track that doc's current structure. + **Two sections below.** "Closed Active Backlog Items" holds items that were promoted out of `CLAUDE.md`'s own "Active Backlog" section once fully resolved (each keeps its original item number for cross-reference stability -- other docs cite these by number). "Closed Backlog" is diff --git a/docs/demo-bootstrapper-output.md b/docs/demo-bootstrapper-output.md index 6d2e87e6..b1e42a3a 100644 --- a/docs/demo-bootstrapper-output.md +++ b/docs/demo-bootstrapper-output.md @@ -9,31 +9,31 @@ in place; don't keep the old one around for context. Ongoing investigation notes 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. +**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. + **Sourcing convention:** every quoted block is either copied verbatim from a real GitHub Actions job log (cited with run ID, job ID, lane, and test file) or, where noted, taken directly from `run_setup.bat`'s current source because no CI run has exercised that exact wording yet -- always labeled explicitly which case applies, never presented as a real capture when it isn't. -**Scope:** grouped by feature area, roughly in the order each was reviewed (now eight Parts, not -the original two -- this paragraph covers only the first two below since they were the doc's -starting point; see the table of contents for the full current list). Part I covers the AV-Safe -Build Path work (Tier A Nuitka fallback, its interaction with hidden-import auto-recovery, and the -requirement-9 optimized-build offer). Part II covers the CLI-interactivity plan -(`docs/plan-cli-interactive-verification.md`): live-tee verification, argv passthrough (REQ-026), -and honest ambiguous-exit messaging (REQ-027). Extend with a new Part as new feature areas get -reviewed, rather than growing any existing Part indefinitely. - -**TODO for the next reorg pass (owner request, 2026-08-01, not done yet -- flow only, no content -change):** the current ordering front-loads two fairly narrow/advanced topics (Part I, Part II) -before the reader ever sees the basic happy path. Move Scenario 38 ("No `.py` files at all -- the -graceful `no_python_files` exit," currently the very LAST scenario in the doc, in Part VIII) up to -the front -- it is the simplest, most foundational case (what happens before anything else can -even run) and reads more naturally as an early scenario than a footnote at the end. Push Part I -and Part II further down, or to the end, to make room. This is a pure reordering/flow pass: move -the existing sections, then update the table of contents anchors and any in-doc cross-references -that name a Part by number (e.g. "see Part I" / "see Part III") so they still point at the right -content after the move -- no scenario text itself should change. Sized for its own dedicated pass, -not a drive-by edit alongside unrelated content changes. +**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: @@ -47,2466 +47,2733 @@ conflate: ## Table of contents -- [Part I: AV-Safe Build Path (Nuitka fallback)](#part-i-av-safe-build-path-nuitka-fallback) - - [Scenario 1: PyInstaller build fails, Tier A (Nuitka) fallback succeeds](#scenario-1-pyinstaller-build-fails-tier-a-nuitka-fallback-succeeds) - - [Scenario 2: PyInstaller build fails, Tier A fallback ALSO fails (tier exhaustion)](#scenario-2-pyinstaller-build-fails-tier-a-fallback-also-fails-tier-exhaustion) - - [2a. `execfail`](#2a-execfail----the-pyinstaller-build-command-itself-fails) - - [2b. `output_vanish`](#2b-output_vanish----pyinstaller-succeeds-then-the-exe-disappears-immediately) - - [Scenario 3: Tier A + hidden-import auto-recovery skip guard](#scenario-3-tier-a-hidden-import-auto-recovery-skip-guard) - - [Scenario 4: Requirement 9 -- elective "want an optimized build too?" offer](#scenario-4-requirement-9----elective-want-an-optimized-build-too-offer) - - [4a. `accept`](#4a-accept----a-real-optimized-build-succeeds-and-is-swapped-in) - - [4b. `forcefail`](#4b-forcefail----accepted-but-the-build-fails-original-exe-is-left-untouched) - - [4c. `swapfail`](#4c-swapfail----verified-build-but-the-final-swap-step-fails-original-exe-is-left-untouched) - - [4d. `decline`](#4d-decline----defaultci-path-prompt-shown-but-nothing-built) - - [Reactive-only failure hint](#reactive-only-failure-hint-both-tier-a-and-requirement-9s-real-build-failure-paths) -- [Part II: CLI interactivity, argv passthrough & honest messaging](#part-ii-cli-interactivity-argv-passthrough-honest-messaging) - - [Scenario 5: Interactive verification -- live-tee, activity-aware kill, and the quit-prompt hint](#scenario-5-interactive-verification----live-tee-activity-aware-kill-and-the-quit-prompt-hint) - - [Scenario 6: Argv passthrough (REQ-026) -- launch arguments through the bootstrapper](#scenario-6-argv-passthrough-req-026----launch-arguments-through-the-bootstrapper) - - [Scenario 7: Honest ambiguous-exit messaging (REQ-027)](#scenario-7-honest-ambiguous-exit-messaging-req-027) - - [7a. No-EXE path, interpreter also failed](#7a-no-exe-path-interpreter-also-failed) - - [7b. Cached-EXE fast path, kept despite a non-zero exit](#7b-cached-exe-fast-path-kept-despite-a-non-zero-exit) -- [Part III: Default double-click happy path (uv-first, fresh machine, zero flags)](#part-iii-default-double-click-happy-path-uv-first-fresh-machine-zero-flags) - - [Scenario 8: Pre-flight guards, lock acquisition, and entry detection on a clean run](#scenario-8-pre-flight-guards-lock-acquisition-and-entry-detection-on-a-clean-run) - - [Scenario 9: Provider acquisition and dependency install (uv-first)](#scenario-9-provider-acquisition-and-dependency-install-uv-first) - - [Scenario 10: Build, verify, and the final status panel](#scenario-10-build-verify-and-the-final-status-panel) - - [Scenario 11: The two elective prompts a real user faces after every successful run](#scenario-11-the-two-elective-prompts-a-real-user-faces-after-every-successful-run) -- [Part IV: Second run, nothing changed (repeat-run fast paths)](#part-iv-second-run-nothing-changed-repeat-run-fast-paths) - - [Scenario 12: The EXE fast path (nothing changed at all)](#scenario-12-the-exe-fast-path-nothing-changed-at-all) - - [Scenario 13: Source touched just enough to force a rebuild, but the environment is reused](#scenario-13-source-touched-just-enough-to-force-a-rebuild-but-the-environment-is-reused) -- [Part V: User configuration and CLI overrides](#part-v-user-configuration-and-cli-overrides) - - [Scenario 14: `PVW_PYTHON_EXE` / `PVW_UV_EXE` / `PVW_TARGET_PY` / `PVW_WORKSPACE`](#scenario-14-pvw_python_exe--pvw_uv_exe--pvw_target_py--pvw_workspace) - - [Scenario 15: `PVW_CONDA_EXE` and its interaction with the conda self-heal flow](#scenario-15-pvw_conda_exe-and-its-interaction-with-the-conda-self-heal-flow) - - [Scenario 16: Drag-and-drop / CLI entry-file override (REQ-011 same-directory rule + REQ-002 priority)](#scenario-16-drag-and-drop--cli-entry-file-override-req-011-same-directory-rule--req-002-priority) -- [Part VI: Adversarial and recovery branches](#part-vi-adversarial-and-recovery-branches) - - [Scenario 17: Network connectivity check and transient-retry (REQ-013 + REQ-022)](#scenario-17-network-connectivity-check-and-transient-retry-req-013--req-022) - - [Scenario 18: Corrupted-conda self-heal (detect / decline / accept)](#scenario-18-corrupted-conda-self-heal-detect--decline--accept) - - [Scenario 19: Miniconda install chain (AllUsers -> JustMe -> both-failed)](#scenario-19-miniconda-install-chain-allusers---justme---both-failed) - - [Scenario 20: Standalone embed-tier download (REQ-009 Tier 5) -- decline and real success](#scenario-20-standalone-embed-tier-download-req-009-tier-5----decline-and-real-success) - - [Scenario 21: REQ-009 provider cascade -- one real run showing the FULL chain](#scenario-21-req-009-provider-cascade----one-real-run-showing-the-full-chain) - - [Scenario 22: `--hidden-import` auto-recovery (success and exhaustion)](#scenario-22---hidden-import-auto-recovery-success-and-exhaustion) - - [Scenario 23: Warnfix repair loop (success, and the failure that feeds the cascade)](#scenario-23-warnfix-repair-loop-success-and-the-failure-that-feeds-the-cascade) - - [Scenario 24: Pre-flight guards actually firing](#scenario-24-pre-flight-guards-actually-firing) - - [Scenario 25: Concurrent-instance lock contention (REQ-024)](#scenario-25-concurrent-instance-lock-contention-req-024) -- [Part VII: Remaining branches (dependency source precedence, write-back, and misc)](#part-vii-remaining-branches-dependency-source-precedence-write-back-and-misc) - - [Scenario 26: Git config merge (`.gitignore`/`.gitattributes`, REQ-015)](#scenario-26-git-config-merge-gitignoregitattributes-req-015) - - [Scenario 27: Python-version precedence (REQ-004) and dependency-source precedence (`pyproject.toml`)](#scenario-27-python-version-precedence-req-004-and-dependency-source-precedence-pyprojecttoml) - - [Scenario 28: PEP 723 dependency write-back (REQ-005.11) -- the fresh-install trigger](#scenario-28-pep-723-dependency-write-back-req-00511----the-fresh-install-trigger) - - [Scenario 29: `HP_PVW_KNOWN_IDEMPOTENT` execute-mode discovery (REQ-005.13)](#scenario-29-hp_pvw_known_idempotent-execute-mode-discovery-req-00513) - - [Scenario 30: NI-VISA detection and install outcome (REQ-008)](#scenario-30-ni-visa-detection-and-install-outcome-req-008) - - [Scenario 31: pandas/openpyxl heuristic dependency augmentation (REQ-005.8)](#scenario-31-pandasopenpyxl-heuristic-dependency-augmentation-req-0058) - - [Scenario 32: Conda base periodic update](#scenario-32-conda-base-periodic-update) - - [Scenario 33: REQ-014 system-Python consent -- ACCEPT](#scenario-33-req-014-system-python-consent----accept) -- [Part VIII: Additional branches found in a full-file sweep](#part-viii-additional-branches-found-in-a-full-file-sweep) - - [Scenario 34: Interactive entry picker -- multiple `.py` files, no clear winner (REQ-002)](#scenario-34-interactive-entry-picker----multiple-py-files-no-clear-winner-req-002) - - [Scenario 35: Pre-flight syntax-error rejection (REQ-021), and a real bug it exposed](#scenario-35-pre-flight-syntax-error-rejection-req-021-and-a-real-bug-it-exposed) - - [Scenario 36: REQ-007 system-Python build consent, and the resulting no-EXE interpreter path](#scenario-36-req-007-system-python-build-consent-and-the-resulting-no-exe-interpreter-path) - - [Scenario 37: EXE smoke-run diagnostic hints (companion to Scenario 22)](#scenario-37-exe-smoke-run-diagnostic-hints-companion-to-scenario-22) - - [Scenario 38: No `.py` files at all -- the graceful `no_python_files` exit](#scenario-38-no-py-files-at-all----the-graceful-no_python_files-exit) +- [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 I: AV-Safe Build Path (Nuitka fallback) +## Part I: Default double-click happy path (uv-first, fresh machine, zero flags) -### Scenario 1: PyInstaller build fails, Tier A (Nuitka) fallback succeeds +**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. -**What's tested:** `self.exe.build.tiera` (`tests/selfapps_nuitka_tiera.ps1`, uv lane, -non-gating). `HP_TEST_FORCE_PYINSTALLER_FAIL=1` forces the primary build to fail deterministically; -the Nuitka fallback (`:try_nuitka_tier_a`) then runs for real -- a genuine compile, not simulated. +**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. -**What appears on screen**, from the moment PyInstaller's build is attempted through to the final -summary. Sourced from real CI (run `29788624195`, job `88506013149`), with the "(fallback build -system)" verification line and the drive-message reassurance line as originally captured, but the -"Verifying the built standalone EXE" line and the "Does your program need launch arguments" -paragraph both updated in place to the CURRENT shipped wording (`docs/plan-cli-interactive- -verification.md` requirement 3's activity-aware kill, and REQ-026's argv passthrough -- both -landed after this specific run) -- **not yet re-confirmed against a fresh CI capture that includes -either addition**: +### 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] Building standalone executable -- this may take a minute or two... -[INFO] (A stray one-line Windows message about a missing drive may appear next -- that is a known side effect from an unrelated background process, unrelated to your app; safe to ignore.) -The system cannot find the drive specified. -The system cannot find the drive specified. -[TEST] HP_TEST_FORCE_PYINSTALLER_FAIL: simulating PyInstaller build failure. -[INFO] Standard build did not complete; attempting a fallback build (this may take a minute or two). -[INFO] Fallback build succeeded: dist\.exe was produced using the fallback build system. -[DEBUG] warnfix: warn file not found -[INFO] PyInstaller build artifacts cleaned up. -[INFO] EXE smokerun: testing dist\.exe -[INFO] Running entry script smoke test via packaged EXE. -[WARN] Verifying the built standalone EXE (fallback build system) now: if it stays completely silent for about 30 seconds it will be force-stopped, but any output (including a prompt waiting on your input) keeps it running as long as needed. If your program is interactive, try answering its prompts through to its own quit/exit option now so we can confirm it exits cleanly. Either way, do not start real work in it yet or any unsaved work will be lost. -[INFO] EXE smokerun: exited 0 (ok) -[INFO] Entry smoke exit=0 -[STATUS] Run Status: SUCCESS (Exit Code: 0) +[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. +``` -*** Verification finished -- see the Run Status above. *** -*** You can run your program again now via the interpreter as an extra diagnostic check. *** -[INFO] REQ-018: post-execution checkpoint (exe): declined (run footprint stays at one execution). +(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. -============================================================ - SETUP COMPLETE -============================================================ - Your standalone application is ready: - dist\.exe +### Scenario 2: Pre-flight guards, lock acquisition, and entry detection on a clean run - RUNNING YOUR APP - Double-click dist\.exe to run it. +**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'`. - STARTUP MAY BE SLOW: a one-file .exe unpacks itself each time it - starts, so allow 10-15 seconds (longer for big libraries like - numpy/scipy/matplotlib, or when extra packages were bundled to fix - missing imports) before assuming it has hung. +**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`): - If the window flashes and closes instantly: that's normal if - your program finished quickly or hit an error before printing - anything. To see what happened, open Command Prompt, cd to - this folder, and run: - dist\.exe - This keeps the window open so you can read any messages. +``` +Tue 07/28/2026 4:29:43.96 [INFO] REQ-015: Appending standard ignores to .gitignore. +``` - A progress indicator that updates in place may appear all at - once instead of live when run as the .exe -- that is a stdout - buffering difference between the .exe and the script, not an error. +Before that 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 -- see `docs/agent-lessons-learned.md` +for the C-runtime backslash-before-quote parsing hazard a check like this has to get right) are all +silent on an ordinary, non-UNC path. - 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 "" --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\.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 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. - KEEP these files with your project: - requirements.txt -- packages your app depends on - runtime.txt -- Python version pin +**`: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): - SAFE TO DELETE to reclaim disk space: - .*_env\ folders -- environment directories - ~* files -- tilde-prefix work files (e.g. ~setup.log) - build\ -- PyInstaller build cache -============================================================ +``` +Chosen entry: app.py ``` -The "Verifying the built standalone EXE" line now correctly says "(fallback build system)" -instead of a hardcoded "(PyInstaller)" when the EXE being verified was actually Nuitka-built -(`:warn_user_code_launch` branches on `HP_NUITKA_FALLBACK_USED`). The postflight briefing's -"PyInstaller build cache" line is left as-is -- Nuitka never creates a `build\\` folder of -its own (its `--remove-output` flag cleans up its own intermediates), so that line stays literally -true regardless of which tool actually built the current EXE: if a `build\` folder exists, it's -PyInstaller's. See Scenario 6 for the argv-passthrough paragraph's own dedicated writeup. +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 2: PyInstaller build fails, Tier A fallback ALSO fails (tier exhaustion) +### Scenario 3: Provider acquisition and dependency install (uv-first) -**What's tested:** `self.exe.build.xfail` (`tests/selfapps_pyinstaller_fail.ps1`, real/conda-full -lanes, gating). Three sub-scenarios share one NDJSON row id: `execfail` (the PyInstaller build -command itself fails), `output_vanish` (PyInstaller succeeds, then the output EXE vanishes -immediately -- simulating AV-style post-creation removal), and `execfail_runtimefail` (packaging -fails AND the interpreter fallback that runs next ALSO exits non-zero -- see Scenario 7a for that -one's console text, since it's really a REQ-027 demo). The first two additionally force -`HP_TEST_FORCE_NUITKA_FAIL=1` so the fallback also fails, proving genuine tier exhaustion. +**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. -#### 2a. `execfail` -- the PyInstaller build command itself fails +**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. -Real CI capture, run `29788624195`, job `88506013028` ("real" lane): +**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. -``` -[INFO] Building standalone executable -- this may take a minute or two... -[TEST] HP_TEST_FORCE_PYINSTALLER_FAIL: simulating PyInstaller build failure. -[INFO] Standard build did not complete; attempting a fallback build (this may take a minute or two). -[TEST] HP_TEST_FORCE_NUITKA_FAIL: simulating fallback build failure. -[ERROR] PyInstaller execution failed. -[DEBUG] warnfix: warn file not found -[INFO] PyInstaller build artifacts cleaned up. -[WARN] EXE smokerun: dist\.exe not found; skipping -[INFO] Running entry script smoke test via uv interpreter. -[INFO] Entry smoke exit=0 -[STATUS] Run Status: SUCCESS (Exit Code: 0) +**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] REQ-018: post-execution checkpoint (interpreter): declined (run footprint stays at one execution). +``` +[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. ``` -When BOTH the PyInstaller build and the Nuitka fallback fail outright but the interpreter -fallback's own run exits 0 (this trivial stub script does), the final line is -`[STATUS] Run Status: SUCCESS (Exit Code: 0)` and the postflight panel is the plain -"YOUR CODE RAN -- BUT NO STANDALONE .EXE WAS PRODUCED" variant -- see Scenario 7a for the -DIFFERENT (honest, "we can't confirm") panel this same tier-exhaustion path now shows when the -interpreter run ALSO fails, which is the REQ-027 fix that closed the gap this section used to flag -as an open question. - -#### 2b. `output_vanish` -- PyInstaller succeeds, then the EXE disappears immediately +(`~setup.log`-only, never shown on-screen: curl's own progress-bar text between the "downloading" +and "acquired" lines, plus uv's own managed-CPython fetch triggered by the version-detection call +-- `Downloading cpython-3.14.6-windows-x86_64-none (download) (21.5MiB)` / +`Downloaded cpython-3.14.6-windows-x86_64-none (download)`. On a REPEAT run with `~uv_bin` already +populated, the cached-binary branch just before `:uv_acquire_download` instead logs `[INFO] uv: +cached binary found at ~uv_bin\uv.exe` -- not independently captured here since every CI scratch +dir is fresh; see this doc's repeat-run scenarios, added in a later pass, for the dedicated +treatment.) -Real CI capture, same run/job as 2a: +**uv venv creation** (console, no pre-existing version pin -- this app has neither `runtime.txt` +nor a `pyproject.toml` constraint): ``` -[INFO] Building standalone executable -- this may take a minute or two... -[TEST] HP_TEST_FORCE_OUTPUT_VANISH: deleting freshly-built EXE to simulate post-creation removal. -[INFO] Standard build did not complete; attempting a fallback build (this may take a minute or two). -[TEST] HP_TEST_FORCE_NUITKA_FAIL: simulating fallback build failure. -[ERROR] PyInstaller did not produce dist\.exe -[DEBUG] warnfix: warn file found -[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. -[INFO] PyInstaller build artifacts cleaned up. -[WARN] EXE smokerun: dist\.exe not found; skipping -[INFO] Running entry script smoke test via uv interpreter. -[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. *** -[INFO] REQ-018: post-execution checkpoint (interpreter): declined (run footprint stays at one execution). +[INFO] uv: creating venv at .uv_env... +[INFO] uv: venv created at .uv_env +[INFO] HP_ENV_MODE=uv +[BOOT] REQ-009: Selected Python provider: UV. +[INFO] runtime.txt written: python-3.14.6 ``` ---- - -### Scenario 3: Tier A + hidden-import auto-recovery skip guard +A version-constrained app (e.g. `pyproject.toml` with `requires-python = ">=3.9"`) instead logs +`[INFO] uv: creating venv at .uv_env with Python 3.9 or newer...` -- confirmed via a second real +capture in the same run (`tests/~pyproject_prec/~pyproject_prec_bootstrap.log`), an exact pin like +`python==3.11` templates to `...with Python 3.11...` per the `HP_UV_PY_DISP` derivation just +above `:uv_venv_ready` (not independently captured, but the template is a literal source string, +not inferred). `~setup.log`-only detail: uv's own raw venv output (`Using CPython 3.14.6`, +`Creating virtual environment with seed packages at: .uv_env`, a `Failed to hardlink files; +falling back to full copy` warning, `+ pip==26.1.2`, `Activate with: .uv_env\Scripts\activate`) -- +none of this reaches the console. -**What's tested:** `self.exe.tiera.hidden_skip` (`tests/selfapps_nuitka_tiera_hidden_skip.ps1`, uv -lane, non-gating). Forces Tier A to trigger and succeed for real, then has the stub app fabricate -a `ModuleNotFoundError: No module named 'nuitka'` on stderr and exit 1 -- the exact signature that -used to (before this fix) trigger an incorrect PyInstaller rebuild attempt against a Nuitka-built -EXE. +**Dependency discovery** -- pipreqs and the REQ-005.12 `autopep723 check` Tier 1 merge running +TOGETHER, genuine production behavior (no `HP_SKIP_PIPREQS` test-isolation flag involved, unlike +the dedicated `selfapps_autopep_discovery.ps1`/`selfapps_pvw_idempotent.ps1` tests that deliberately +isolate one mechanism from the other): -**Source:** confirmed in real CI run `29877805447`, uv lane, job `88792048278`: ``` -{"details":{"appStdoutFound":true,"noRepairRebuild":true,"successLogged":true,"skipGuardLogged":true,"exeExists":true,"statusState":"ok","bootstrapExit":0,"smokerunNonzeroLogged":true,"attemptLogged":true,"log":"~nuitka_tiera_hidden_skip_bootstrap.log"},"req":"REQ-AV","pass":true,"desc":"AV-Safe Build Path Tier A: hidden-import auto-recovery correctly skips (never rebuilds via PyInstaller) against a Nuitka-built EXE","id":"self.exe.tiera.hidden_skip","lane":"uv"} +[INFO] pipreqs 0.4.13 installed successfully; using it for dependency discovery. +[INFO] pipreqs (direct) command: pipreqs . --force --mode compat --savepath "...\requirements.auto.txt" --ignore ".git,.github,.venv,venv,env,.uv_env,build,dist,__pycache__,tests" +*** [WARN] Dependencies were auto-detected (pipreqs) +*** [WARN] Auto-detection may be incomplete or incorrect +*** [INFO] Consider adding requirements.txt or PEP 723 metadata for reliability +[INFO] REQ-005.5: dependency source diff computed -- ~pipreqs.diff.txt +[INFO] REQ-005.12: autopep723 discovery merge complete. ``` -The test only dumps a full console log to CI when a scenario fails; since this one passes, the -exact console text below is reconstructed from `run_setup.bat`'s source rather than copied from a -console dump -- the NDJSON row's `skipGuardLogged`/`noRepairRebuild` fields are the test's own -regex-verified confirmation that these exact lines were present/absent in the real captured log: + +**A real quirk worth flagging so it isn't misread**: the bootstrap log around this point also shows +`DEP_FINAL_COUNT=0` even though `colorama` is a genuine, real dependency that gets installed a few +steps later -- this count is taken BEFORE `requirements.auto.txt` is copied into `requirements.txt` +a few lines further down, still inside `:after_pipreqs_run`'s own tail, so `DEP_FINAL_COUNT=0` +here does not mean "pipreqs found nothing"; it's a pre-existing ordering quirk in the log sequence, not a bug in this +run. `~setup.log`-only (pipreqs's raw output is redirected to a SEPARATE file entirely, +`~pipreqs_direct.log`, not even `~setup.log` -- a real user never sees any of the following): ``` -[INFO] Building standalone executable -- this may take a minute or two... -[TEST] HP_TEST_FORCE_PYINSTALLER_FAIL: simulating PyInstaller build failure. -[INFO] Standard build did not complete; attempting a fallback build (this may take a minute or two). -[INFO] Fallback build succeeded: dist\.exe was produced using the fallback build system. -[INFO] EXE smokerun: testing dist\.exe -[WARN] EXE smokerun: exited 1 (non-zero) -[INFO][HIDDEN_IMPORT] Skipping --hidden-import auto-recovery: dist\.exe was built via the fallback build system (Nuitka), which uses a different missing-import mechanism than PyInstaller's --hidden-import flag. +WARNING: Import named "colorama" not found locally. Trying to resolve it at the PyPI server. +WARNING: Import named "colorama" was resolved to "colorama:0.4.6" package (https://pypi.org/project/colorama/). +Please, verify manually the final list of requirements.txt to avoid possible dependency confusions. +INFO: Successfully saved requirements file in ...\requirements.auto.txt ``` -Without this guard, the OLD behavior would have printed -`[REPAIR][HIDDEN_IMPORT] Adding --hidden-import=nuitka; rebuilding EXE (iter 1/3)` here and -attempted a PyInstaller rebuild against a Nuitka-built EXE. - ---- - -### Scenario 4: Requirement 9 -- elective "want an optimized build too?" offer +plus a handful of cosmetic `SyntaxWarning` lines from pipreqs 0.4.13's own bundled `docopt`/`yarg` +dependencies. `~setup.log` also shows the autopep723 merge's own uvx tool-bootstrap noise +(`Installed 1 package in 26ms`) and its own result line, `no-op: all autopep723 dependencies +already present` -- because pipreqs already found `colorama` first, autopep723's own independent +discovery of the same import is redundant here, a real working example of this repo's "augment, +never replace" design for the two mechanisms. -**What's tested:** `self.optbuild.offer` (`tests/selfapps_optimized_build.ps1`, uv lane, -non-gating), four scenarios sharing one row id. +**Dependency install:** -**Source:** confirmed in real CI run `29877805447`, uv lane, job `88792048278`, all four scenarios -passing: ``` -{"lane":"uv","details":{"log":"~optbuild_accept_bootstrap.log","statusState":"ok","scenario":"accept","successLogged":true,"promptShown":true,"tmpExeGone":true,"exeExists":true,"bootstrapExit":0,"acceptedLogged":true,"appStillRuns":true},"desc":"AV-Safe Build Path requirement 9 (accept): a real optimized build succeeds, verifies, and is swapped into place","req":"REQ-AV","id":"self.optbuild.offer","pass":true} -{"req":"REQ-AV","lane":"uv","desc":"AV-Safe Build Path requirement 9 (forcefail): a failed optimized build leaves the original PyInstaller EXE completely untouched","id":"self.optbuild.offer","details":{"originalStillRuns":true,"bootstrapExit":0,"log":"~optbuild_forcefail_bootstrap.log","tmpExeGone":true,"promptShown":true,"exeExists":true,"testHookFired":true,"scenario":"forcefail","statusState":"ok","noSuccessMsg":true},"pass":true} -{"req":"REQ-AV","id":"self.optbuild.offer","lane":"uv","pass":true,"details":{"bootstrapExit":0,"acceptedLogged":true,"originalStillRuns":true,"log":"~optbuild_swapfail_bootstrap.log","promptShown":true,"exeExists":true,"tmpExeGone":true,"scenario":"swapfail","statusState":"ok","noSuccessMsg":true,"swapFailLogged":true},"desc":"AV-Safe Build Path requirement 9 (swapfail): a verified optimized build whose final swap fails leaves the original PyInstaller EXE completely untouched and cleans up the leftover temp file"} -{"desc":"AV-Safe Build Path requirement 9 (decline): default/CI path shows the prompt but never attempts a build","lane":"uv","details":{"statusState":"ok","noBuildAttempt":true,"tmpExeGone":true,"scenario":"decline","log":"~optbuild_decline_bootstrap.log","bootstrapExit":0,"exeExists":true,"declinedLogged":true,"promptShown":true},"req":"REQ-AV","id":"self.optbuild.offer","pass":true} +[INFO] UV_USED=1 +[INFO] DEP_INSTALLED_CAPTURED=1 +[INFO] Environment snapshot written: ~environment.lock.txt +[INFO] REQ-005.11: PEP 723 header write-back succeeded via uv add --script. ``` -#### 4a. `accept` -- a real optimized build succeeds and is swapped in +`~setup.log`-only (`uv pip install --python ... -r requirements.txt`'s own raw output, never on +console): `Using Python 3.14.6 environment at: .uv_env`, `Resolved 1 package in 257ms`, `Prepared +1 package in 43ms`, `Installed 1 package in 15ms`, ` + colorama==0.4.6`. The real +`~environment.lock.txt` (uv mode copies the pip-freeze output here) contains more than just +`colorama` -- it also lists pipreqs's OWN transitive dependencies (`certifi`, `charset-normalizer`, +`docopt`, `idna`, `pipreqs`, `requests`, `urllib3`, `yarg`), because in `HP_ENV_MODE=uv`, pipreqs +is installed via `uv pip install --python "%HP_PY%"` into the SAME venv as the target app (inside +the pipreqs install step, before `:pipreqs_direct_done`), not an isolated tool venv. This is +genuine, expected production behavior +-- a real user's `.uv_env` always ends up with pipreqs's own dependency footprint mixed in +alongside their actual dependencies, not a CI artifact. + +--- -Real, verbatim console dump (`~selftest_optbuild_accept\~optbuild_accept_bootstrap.log`), with the -"Verifying the built standalone EXE" line updated in place to the CURRENT shipped wording -(requirement 3's activity-aware kill plus the quit-prompt hint, both landed after this capture) -- -**not yet re-confirmed against a fresh CI capture**: +### Scenario 4: Build, verify, and the final status panel + +**What's tested:** `self.stub.fastpath` (`tests/selftest.ps1`, real lane), a genuine first-run +(non-cached) build of a trivial one-line stub app (`hello_stub.py` = `print("hello-from-stub")`). +Chosen over Scenario 3's `colorama`-importing app for this piece specifically because it produces +a single, fully self-consistent, real capture spanning build through the final status panel with +no lines needing a source-level touch-up -- unlike the similar block quoted in Scenario 37 +(Part VIII), where two lines had to be patched to current wording. + +**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` ("real" lane), +`tests/~selftest_stub/~stub_bootstrap.log`: ``` -[INFO] Building standalone executable -- this may take a minute or two... +Tue 07/28/2026 4:55:47.55 [INFO] Building standalone executable -- this may take a minute or two... +Tue 07/28/2026 4:55:47.55 [INFO] (A stray one-line Windows message about a missing drive may appear next -- that is a known side effect from an unrelated background process, unrelated to your app; safe to ignore.) The system cannot find the drive specified. The system cannot find the drive specified. -[INFO] PyInstaller produced dist\.exe -[INFO] warnfix: Platform-specific modules in the list above are expected on Windows: posix, fcntl, grp, pwd, resource, _scproxy, _posixsubprocess, collections.abc, _frozen_importlib_external. These will be filtered out automatically. -[INFO] PyInstaller build artifacts cleaned up. -[INFO] EXE smokerun: testing dist\.exe -[INFO] Running entry script smoke test via packaged EXE. -[WARN] Verifying the built standalone EXE (PyInstaller) now: if it stays completely silent for about 30 seconds it will be force-stopped, but any output (including a prompt waiting on your input) keeps it running as long as needed. If your program is interactive, try answering its prompts through to its own quit/exit option now so we can confirm it exits cleanly. Either way, do not start real work in it yet or any unsaved work will be lost. -[INFO] EXE smokerun: exited 0 (ok) -[INFO] Entry smoke exit=0 -[STATUS] Run Status: SUCCESS (Exit Code: 0) +Tue 07/28/2026 4:55:58.01 [INFO] PyInstaller produced dist\_selftest_stub.exe +Tue 07/28/2026 4:55:58.02 [DEBUG] warnfix: warn file found +Tue 07/28/2026 4:55:58.02 [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. +Tue 07/28/2026 4:55:58.39 [INFO] PyInstaller build artifacts cleaned up. +Tue 07/28/2026 4:55:58.40 [INFO] EXE smokerun: testing dist\_selftest_stub.exe +Tue 07/28/2026 4:55:58.41 [INFO] Running entry script smoke test via packaged EXE. +Tue 07/28/2026 4:55:58.43 [WARN] Verifying the built standalone EXE (PyInstaller) now: if it stays completely silent for about 30 seconds it will be force-stopped, but any output (including a prompt waiting on your input) keeps it running as long as needed. If your program is interactive, try answering its prompts through to its own quit/exit option now so we can confirm it exits cleanly. Either way, do not start real work in it yet or any unsaved work will be lost. +[INFO] Process ID 6076. If it seems stuck: Task Manager > Details tab > find this PID > End Task (this window stays open). +hello-from-stub +Tue 07/28/2026 4:55:59.87 [INFO] EXE smokerun: exited 0 (ok) +Tue 07/28/2026 4:55:59.88 [INFO] Entry smoke exit=0 +Tue 07/28/2026 4:55:59.88 [STATUS] Run Status: SUCCESS (Exit Code: 0) +``` + +Build took ~10.5s wall-clock here (this run has effectively zero dependencies, so it's near the +fast end of what's possible). This run's PyInstaller build DID trigger the warnfix path (`warn +file found` -- expected, since even a bare stub still needs the platform-module filter pass) but +needed no repair install, so the informational filter messaging is present but purely +informational -- the "no warnfix repair needed" success case this doc previously had no example +of. `hello-from-stub` is the stub program's OWN real stdout, live-teed via +`tools/exe_smokerun.ps1`'s chunk-based `ReadAsync` reader (see Scenario 41's fuller writeup of that +mechanism) -- landing exactly between the PID line and the "exited 0" line, precisely where a real +user's own program output appears. The `[INFO] Process ID . If it seems stuck: Task Manager > +Details tab...` line is the already-shipped stuck-program recovery aid (`tools/exe_smokerun.ps1`, +see `docs/agent-interconnect.md`'s "Process-ID display for stuck-program recovery" section) -- +confirmed here as a genuine, working console line, not just source text. + +Immediately following (elective prompts, both auto-declined by CI -- see Scenario 5 below for +what a real user experiences here instead) and then the final panel: +``` *** Verification finished -- see the Run Status above. *** *** You can run your program again now via the interpreter as an extra diagnostic check. *** -[INFO] REQ-018: post-execution checkpoint (exe): declined (run footprint stays at one execution). +Tue 07/28/2026 4:55:59.91 [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. *** -[INFO] Optimized build: accepted; building now (this may take a minute or two). -[INFO] Optimized build succeeded and verified: dist\.exe now uses the fallback build system. -``` +Tue 07/28/2026 4:55:59.94 [INFO] Optimized build: declined. -Note the "warnfix: Platform-specific modules..." line above still shows the OLD wording (this -capture predates the messaging fix described in Scenario 2b) -- the NEW wording is the one shown -there. +============================================================ + SETUP COMPLETE +============================================================ + Your standalone application is ready: + dist\_selftest_stub.exe -**The interactive `Build the optimized version now? [Y/N]` prompt line is echoed unconditionally -by design** (same pattern as `:run_postexec_checkpoint`), but does not appear literally in any -CI capture -- CI answers via the `HP_TEST_OPTBUILD_ANSWER` env-var override, not the interactive -`set /p` path, so only the resolution lines (`accepted`/`declined`) show up in these logs. This is -expected (CI is non-interactive by design), not a gap. + RUNNING YOUR APP + Double-click dist\_selftest_stub.exe to run it. -#### 4b. `forcefail` -- accepted, but the build fails; original EXE is left untouched + STARTUP MAY BE SLOW: a one-file .exe unpacks itself each time it + starts, so allow 10-15 seconds (longer for big libraries like + numpy/scipy/matplotlib, or when extra packages were bundled to fix + missing imports) before assuming it has hung. -Real, verbatim console dump (`~selftest_optbuild_forcefail\~optbuild_forcefail_bootstrap.log`): + If the window flashes and closes instantly: that's normal if + your program finished quickly or hit an error before printing + anything. To see what happened, open Command Prompt, cd to + this folder, and run: + dist\_selftest_stub.exe + This keeps the window open so you can read any messages. -``` -*** 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. *** -[INFO] Optimized build: accepted; building now (this may take a minute or two). -[TEST] HP_TEST_FORCE_OPTBUILD_FAIL: simulating optimized-build failure. -[INFO] REQ-016: Post-flight briefing printed. -``` + A progress indicator that updates in place may appear all at + once instead of live when run as the .exe -- that is a stdout + buffering difference between the .exe and the script, not an error. -No further message prints between the forced-fail log line and the (unrelated, always-present) -post-flight briefing -- the subroutine cleans up the temp file and returns silently. This is a -narrower silence than the wording used on a REAL build-failure branch (which explicitly says -"your app is still ready to use as-is" -- see the reactive hint below); a forced-test-hook failure -and a genuine build failure currently give the user different amounts of reassurance for what is, -from their perspective, the same outcome. + 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 "hello_stub.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\_selftest_stub.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. -#### 4c. `swapfail` -- verified build, but the final swap step fails; original EXE is left untouched + KEEP these files with your project: + requirements.txt -- packages your app depends on + runtime.txt -- Python version pin -Regression test for a real bug: the swap-verification check used to test the DESTINATION file -(which already exists before the move, success or failure alike) instead of the SOURCE (which -should be gone only on success) -- a genuinely failed swap would have been silently misreported -as success. Fixed; console text (expected from source, not yet dumped in a CI console capture -since this scenario has passed on every run so far): + SAFE TO DELETE to reclaim disk space: + .*_env\ folders -- environment directories + ~* files -- tilde-prefix work files (e.g. ~setup.log) + build\ -- PyInstaller build cache +============================================================ -``` -*** Your app is ready. *** -*** Want to build an optimized version too? ... *** -[INFO] Optimized build: accepted; building now (this may take a minute or two). -[WARN] Optimized build verified successfully but could not be swapped into place; your app is still ready to use as-is. +Tue 07/28/2026 4:56:00.03 [INFO] REQ-016: Post-flight briefing printed. ``` -#### 4d. `decline` -- default/CI path, prompt shown but nothing built - -Real, verbatim console dump (`~selftest_optbuild_decline\~optbuild_decline_bootstrap.log`): +The companion `tests/~selftest_stub/~bootstrap.status.json` reads: +```json +{"state":"ok","exitCode":0,"pyFiles":1} ``` -*** 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. *** -[INFO] Optimized build: declined. -``` - -#### Reactive-only failure hint (both Tier A and requirement 9's real-build-failure paths) - -Fires only on a GENUINE Nuitka compiler failure (not the `forcefail` test hook, which bypasses it -entirely). No CI run to date has exercised a real Nuitka compiler failure, so this is sourced from -`run_setup.bat` rather than a console capture: -``` -[WARN] Optimized build did not complete; your app is still ready to use as-is. -[WARN] Hint: if you have Visual Studio 2022 (or newer) with the 'Desktop development with C++' workload installed, this should use it automatically -- no extra setup needed. If not, installing the free Visual Studio Build Tools with that workload can help. -``` +`exitCode` here means "did the bootstrapper's own env/build lifecycle succeed" (a hardcoded `0` +written unconditionally at the success label), NOT the user program's own exit code -- that's +separately surfaced via the console's `[STATUS] Run Status: SUCCESS (Exit Code: 0)` line above. +Both happen to be 0 in this clean run, so the distinction isn't visible here, but it's worth +knowing they're two independent things (see CLAUDE.md's "User-code exit-code semantics" Known +Finding). --- -## Part II: CLI interactivity, argv passthrough & honest messaging - -Covers `docs/plan-cli-interactive-verification.md` (P0/P1/P2, all shipped): the live-tee -verification redesign that lets an interactive `input()`-driven program's prompts actually reach -the console, the activity-aware 30-second kill, argv passthrough (REQ-026), and the honest -ambiguous-exit messaging panels (REQ-027). +### Scenario 5: The two elective prompts a real user faces after every successful run -### Scenario 5: Interactive verification -- live-tee, activity-aware kill, and the quit-prompt hint +**What's tested:** the framing text of both prompts is confirmed via real CI capture (they're +unconditionally echoed even when CI auto-declines); the actual `[Y/N]` question lines themselves +are never visible in ANY CI log, by construction -- they live inside the branch of an `if/elif` +chain that CI's `HP_CI_LANE` auto-decline always short-circuits past, even in tests that force an +accept via an `HP_TEST_*_ANSWER` override (the override branch assigns the answer variable +directly, bypassing the real `set /p` prompt entirely). Both are therefore genuinely +**`[Extrapolated Branch]`** for the exact prompt-line wording specifically, cited from source, even +though the surrounding framing text is a real capture. -**What changed, in one sentence:** before this plan, `:run_exe_smokerun`'s verification launch -force-killed at a hard 30 seconds regardless of output, and captured stdout/stderr only to a file -(never live to the console) -- so a program correctly waiting on its first `input()` prompt looked -identical to a genuinely hung one, and the user watching the window saw nothing until the process -either finished or got killed. +**Source:** framing lines are REAL CI CAPTURE (run `30328748330`, job `90179708091`, "real" lane, +same log as Scenario 4 above); the two `set /p` prompt lines themselves are +`[Extrapolated Branch]`, cited from `:run_postexec_checkpoint` and `:offer_optimized_build` +respectively. -**What's tested (the plumbing):** `self.interactive.stdin.roundtrip` -(`tests/selfapps_interactive_stdin.ps1`, uv lane, non-gating) builds a real PyInstaller EXE from a -multi-round `input()`-driven stub app and pipes a scripted answer sequence into `cmd.exe`'s own -stdin, exercising the full `cmd.exe -> :run_exe_smokerun -> ~exe_smokerun.ps1 -> the built EXE` -chain and asserting each answer lands in the right round via ordering checks on the captured log --- it proves the plumbing doesn't drop or reorder stdin/stdout, not a live human's own typing -timing (which can't be automated). +**Relationship to README.md's `[REQ-018]` spec, since the two don't map 1:1 in an obvious way:** +`:run_postexec_checkpoint`'s own log line is explicitly tagged `REQ-018` in source (`[INFO] +REQ-018: post-execution checkpoint (...): accepted/declined...`), so it -- not a separate, +undocumented prompt -- is the shipped implementation of README's "After a build, the real run is +offered, not forced" / "Consent before any extra run" bullets. Two nuances worth flagging +explicitly rather than leaving implicit: (1) the offered rerun uses the INTERPRETER, not a second +launch of the packaged EXE -- README's "offers to launch the app untimed for real" phrasing reads +as if it means the EXE specifically, but the actual accepted-path log line says "running a second +time via the interpreter", and a companion note the same subroutine prints for the EXE call site +spells this out too ("this diagnostic run uses the interpreter, not the packaged EXE, so behavior +can differ"); (2) README's separate REQ-018 bullet about the FIRST (mandatory) verification run +being "force-stopped after a short interval even if running fine" is stale relative to the +activity-aware-kill behavior actually shipped later (see Scenario 41's fuller writeup and CLAUDE.md's +Closed Backlog "Activity-aware EXE-smoke kill" entry) -- the real WARN text quoted in Scenario 4 +above says the opposite: any output at all, not just a clean exit, keeps the run alive indefinitely, +only a genuinely SILENT process gets force-stopped. Neither nuance changes what actually ships; +both are flagged here because a reader cross-checking this scenario against README's prose could +otherwise reasonably conclude something was missed. -**The WARN line a user sees right before the verification launch**, current shipped wording -(source: `run_setup.bat`, `:warn_user_code_launch` -- not a console capture, since CI answers -scripted stdin rather than a human watching the window; Scenario 1 and Scenario 4a above both show -this exact line in situ): +Right after a normal successful verification run (Scenario 4's tail), a real double-click user +sees: ``` -[WARN] Verifying the built standalone EXE (PyInstaller) now: if it stays completely silent for about 30 seconds it will be force-stopped, but any output (including a prompt waiting on your input) keeps it running as long as needed. If your program is interactive, try answering its prompts through to its own quit/exit option now so we can confirm it exits cleanly. Either way, do not start real work in it yet or any unsaved work will be lost. +*** Verification finished -- see the Run Status above. *** +*** You can run your program again now via the interpreter as an extra diagnostic check. *** + Run again via the interpreter now? [Y/N] _ ``` -Three things this one line is doing: -1. **States the actual kill rule truthfully**: the 30-second cap is a classification checkpoint, - not an unconditional deadline -- `Kill()` only fires if the process has stayed COMPLETELY - silent that whole time. Any output (including a bare prompt with no trailing newline, the exact - shape of Python's own `input("...")`) switches to an unbounded wait. -2. **Actively guides the user toward a clean result**: driving an interactive program to its own - quit/exit option during this pass turns an otherwise-ambiguous exit into a genuine, confirmed - `[STATUS] Run Status: SUCCESS` -- this directly reduces how often a real user ever sees either - of Scenario 7's ambiguous-exit panels. -3. **Still warns it's a throwaway pass, not the user's real, saveable session** -- this verification - EXE is never reused; only the file it's already tested is kept for later double-clicks. - -The `hidden_import` recovery loop's own separate, narrower verification check (see -`docs/agent-interconnect.md`'s "Activity-aware EXE-smoke kill" section) deliberately keeps the -OLDER, unconditional 30-second wording -- it's a bounded repair-verification check on an -already-built EXE, not the user's primary run, so it never got the interactive-friendly rewrite. - -### Scenario 6: 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 1's full panel -above shows this in context): +(cursor sits after `[Y/N] `, waiting indefinitely -- `:run_postexec_checkpoint`, an UNBOUNDED +`set /p`, no timeout of any kind). Any answer other than a leading `Y`/`y` -- including just +pressing Enter -- resolves to decline. If accepted, the entry program runs a second time via the +interpreter (not the packaged EXE) as a diagnostic; if declined, the bootstrap immediately +continues to the second prompt: ``` - 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 "" --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\.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. +*** 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. *** + Build the optimized version now? [Y/N] _ ``` -**The equivalent guidance on the no-EXE path** (direct-interpreter-invocation form, part of -Scenario 7a's panel below): - -``` - Need launch arguments? Add them directly after that command, e.g. - "" "" --input file.csv -``` +(same shape -- `:offer_optimized_build`, also an unbounded `set /p`, also defaults to decline on +anything but a leading Y). Both prompts genuinely fire on essentially every +successful default run (the checkpoint is called after every clean verification; the optimized- +build offer only skips if the AV-Safe-Build-Path Tier A Nuitka fallback already ran, or the +verification itself failed) -- **this is not an edge case, it's what most real users see twice in +a row at the very end of an otherwise fully successful first run.** -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]`). +**CI cannot show either question line, ever, structurally -- not just "doesn't currently show +them."** Both prompts follow this repo's own established CI-safe-gate pattern (see +`docs/agent-interconnect.md`'s "CI-safe interactive gates" section): echo the framing +unconditionally, THEN branch on `HP_TEST_*_ANSWER` override / `HP_CI_LANE` auto-decline / real +`set /p`. Because the actual question text lives inside the `set /p` call itself (not a separate +unconditional `echo`), and CI always takes one of the first two branches, no CI log -- gating or +non-gating, auto-decline or forced-accept -- can ever contain the literal `" Run again via the +interpreter now? [Y/N] "` or `" Build the optimized version now? [Y/N] "` text. This is a genuine +blind spot in what CI evidence alone can show about this bootstrapper's real user-facing behavior, +worth keeping in mind when reading any other scenario in this file that involves a `set /p`-based +consent gate. -### Scenario 7: Honest ambiguous-exit messaging (REQ-027) +**For contrast, briefly (full treatment is Pass 4/adversarial-recovery territory, not this +Part):** not every consent gate in this file shares the "blocks forever" shape. The REQ-009 +provider-cascade gate (`:cascade_consent_gate`, only reached if a build succeeds but a dependency +repair genuinely fails) is the one gate that's genuinely TIMED for a real user -- `choice /C YN /N +/T 30 /D N`, defaulting to decline after 30 seconds with no answer, so it structurally cannot hang +forever even for a truly unattended user. The REQ-014 system-Python consent gate +(`:system_python_consent_gate`, only reached as the absolute last-resort Tier 4) is unbounded like +the two documented above, but its full question text (unusually, including the actual `[y/n]` +wording) IS an unconditional `echo` rather than living inside `set /p` -- so, unlike this +scenario's two prompts, CI logs genuinely do show the complete question for that one, e.g. +`Proceed with System Python? (Global pollution risk) [y/n]: y to accept, n to decline.` (REAL CI +CAPTURE, same run, job `90179708091`) -- only its own terse follow-up `"Your choice [y/n]: "` line +is hidden the same way. Neither of these two gates fires on this Part's happy path; both are +documented fully in Pass 4. -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. +## Part II: Second run, nothing changed (repeat-run fast paths) -#### 7a. No-EXE path, interpreter also failed +**Scope note:** this Part documents the success side of running the bootstrapper a SECOND time in +the same folder with nothing changed -- same entry file, same requirements, no test flags. The +FAILURE side of one of these fast paths (a stale cached EXE that's kept and later exits non-zero) +is already documented as Scenario 43b (Part IX); this Part doesn't repeat that. Evidence again +comes from a recent clean green run (`30328748330`, commit `5872028`) rather than one single +dedicated "repeat run" test file -- `tests/selfapps_envsmoke.ps1` re-invokes `run_setup.bat` a +second time in the same scratch directory with nothing changed (the EXE fast path), and +`tests/selftest.ps1`'s stub scenario and `tests/selfapps_depcheck.ps1` go one step further -- +`Run 1` (fresh bootstrap), `Run 2` (identical, EXE fast path), then deliberately touch the source +file and run a THIRD time -- exercising the "source changed just enough to force a rebuild, but +the environment itself doesn't need recreating" fast paths this Part's second scenario covers. -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 2a'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`: +### Scenario 6: The EXE fast path (nothing changed at all) -``` -============================================================ - 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. +**What's tested:** `self.fastpath` (`tests/selfapps_envsmoke.ps1`'s second, back-to-back +invocation of `run_setup.bat` in the same scratch directory, zero CLI arguments, nothing touched). - RUNNING YOUR APP (without an .exe) -- the most direct option - "" "" - Need launch arguments? Add them directly after that command, e.g. - "" "" --input file.csv +**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` ("real" lane), the console +capture of that second invocation (`~envsmoke_fastpath.log`) plus the matching block of +`~setup.log`. - 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 "" arg1 arg2 +**What fires FIRST, before any provider/entry/dependency logic even starts:** `:try_fast_exe` is +called immediately after environment-name derivation and the Python-file count, right at the top +of the file -- before uv acquisition, before Miniconda, before `:determine_entry`, before +anything else. It runs exactly ONE real check: compare `dist\.exe`'s modification time +against the newest non-infrastructure `.py` file's modification time (via the embedded +`HP_FAST_CHECK` helper). If the EXE is newer-or-equal, the whole rest of the bootstrap short- +circuits straight to `:success`. - KEEP these files with your project: - requirements.txt -- packages your app depends on - runtime.txt -- Python version pin +**Non-interactive console text (what CI captures -- this is also exactly what a real user would +see if they ran the bootstrapper non-interactively, e.g. from a script):** - SAFE TO DELETE to reclaim disk space: - .*_env\ folders -- environment directories - ~* files -- tilde-prefix work files (e.g. ~setup.log) -============================================================ +``` +Tue 07/28/2026 4:30:33.81 [INFO] Environment name: _envsmoke +Tue 07/28/2026 4:30:33.83 [INFO] Host OS: Microsoft Windows [Version 10.0.26100.32995] +Tue 07/28/2026 4:30:34.04 [INFO] Host PowerShell: 5.1.26100.32995 +Tue 07/28/2026 4:30:34.10 [INFO] Python file count: 1 +Tue 07/28/2026 4:30:34.98 [INFO] Fast path: reusing dist\_envsmoke.exe +Tue 07/28/2026 4:30:35.96 [INFO] Entry smoke exit=0 +Tue 07/28/2026 4:30:35.97 [STATUS] Run Status: SUCCESS (Exit Code: 0) +Tue 07/28/2026 4:30:35.99 [INFO] Fast path: skipping PyInstaller rebuild for existing dist\_envsmoke.exe ``` -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 7b below). - -When the interpreter run instead exits CLEANLY (the common case, and what Scenario 2a'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. - -#### 7b. Cached-EXE fast path, kept despite a non-zero exit +No "SETUP COMPLETE" postflight banner appears -- confirmed both structurally (that dispatch is +gated on the fast path NOT having fired) and in the raw capture, which ends right after the last +line above. `~setup.log` shows one extra line between "Fast path: reusing" and "Entry smoke +exit=0" that never reaches the console (`Fast path command: "dist\_envsmoke.exe" > "~run.out.txt" +2> "~run.err.txt"`) -- a raw log-file-only write, not part of what a user actually sees. -Fires when the fail-fast probe classifies a REUSED `dist\.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`: +**Interactive console text (what a genuine double-click end user sees -- differs from CI because +`HP_CI_LANE` is unset, so `:try_fast_exe` takes its OTHER branch, `:try_fast_exe_probe`, which +launches the cached EXE through the same never-kills fail-fast probe mechanism Scenario 41 +documents rather than the plain redirect above).** Assembled from real, independently-confirmed +fragments (the header and PID lines are genuine captured text from a different test that forces +this same interactive branch; their pairing into a clean, fast, successful sequence is +`[Extrapolated Branch]`, grounded directly in source rather than guessed): ``` -============================================================ - SETUP COMPLETE -- BUT WE CAN'T CONFIRM YOUR LAST RUN WORKED -============================================================ - Your existing standalone application was reused (dist\.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. +Tue 07/28/2026 4:30:33.81 [INFO] Environment name: _envsmoke +Tue 07/28/2026 4:30:33.83 [INFO] Host OS: Microsoft Windows [Version 10.0.26100.32995] +Tue 07/28/2026 4:30:34.04 [INFO] Host PowerShell: 5.1.26100.32995 +Tue 07/28/2026 4:30:34.10 [INFO] Python file count: 1 +Tue 07/28/2026 4:30:34.98 [INFO] Launching your program now via the cached standalone EXE (PyInstaller build): dist\_envsmoke.exe +[INFO] Process ID 7692. If it seems stuck: Task Manager > Details tab > find this PID > End Task (this window stays open). + <- the app's own live stdout/stderr tees here, if any +Tue 07/28/2026 4:30:35.96 [INFO] Entry smoke exit=0 +Tue 07/28/2026 4:30:35.97 [STATUS] Run Status: SUCCESS (Exit Code: 0) +Tue 07/28/2026 4:30:35.99 [INFO] Fast path: skipping PyInstaller rebuild for existing dist\_envsmoke.exe +Press any key to continue . . . +``` - RUNNING YOUR APP - Double-click dist\.exe to run it, or run it from a - Command Prompt to see the full output. +The "still running after Nms, keep waiting?" WARN line that the fail-fast probe can print is +conditional on the process actually exceeding its short classification window (confirmed absent +here via real CI capture of the same forced-interactive mechanism failing fast in an unrelated +scenario) -- omitted above since "nothing changed, app still runs fine" implies a normal-speed +exit. `Press any key to continue . . .` is `cmd.exe`'s own native output from a `pause` at the +very end of the main line, gated on `HP_CI_LANE` being unset -- real end-user only, never appears +in any CI log. - 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. +**Why it's fast -- everything this run skips entirely, not just runs faster:** once `:try_fast_exe` +succeeds, the bootstrap jumps to `:success` before any of the following ever execute: the +`HP_CI_SKIP_ENV` dispatch, the entire uv acquisition block (no download, no `uv venv`, no +`UV_PYTHON_PREFERENCE` even gets set), `:select_conda_bat` and all Miniconda install/probe logic, +the env-state fast path (moot -- conda was never considered), `:conda_base_update`, the +`HP_PREP_REQUIREMENTS` heuristic dependency augmentation, `:determine_entry` (the cached EXE's +identity is trusted as-is, no REQ-002 re-selection), pipreqs entirely (no `pipreqs.install`/ +`pipreqs.run`, no `requirements.auto.txt` diff), and -- the single biggest reason this is fast -- +`:run_entry_smoke` never runs, meaning no `py_compile` preflight and no PyInstaller build +invocation of any kind, cached or otherwise. The reused EXE genuinely gets EXECUTED, not merely +detected -- confirmed by the real `Entry smoke exit=0`/`[STATUS]` lines above, which come from an +actual process launch. - WANT A FRESH BUILD instead (re-checks all dependencies from scratch)? - Delete dist\.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 `docs/agent-interconnect.md`'s "Fast path = ZERO -friction" design requirement), and this doesn't violate that since it never asks a question. +### Scenario 7: Source touched just enough to force a rebuild, but the environment is reused -**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. +**What's tested:** `tests/selftest.ps1`'s stub scenario and `tests/selfapps_depcheck.ps1`, both of +which do Run 1 (fresh) -> Run 2 (Scenario 6's EXE fast path) -> touch the entry file's content +and modification time -> Run 3, which is the case documented here: the EXE fast-path timestamp +check now fails (source is newer than the cached EXE), so PyInstaller reruns and produces a new +EXE -- but the ENVIRONMENT itself (the uv venv or conda env, and already-satisfied dependencies) +is recognized as still valid and reused rather than recreated from scratch. ---- +**Source:** REAL CI CAPTURE, run `30328748330`, jobs `90179708091` ("real" lane, uv-first) and +`90179708094` ("conda-full" lane). -## Part III: Default double-click happy path (uv-first, fresh machine, zero flags) +**uv-first lane** (`.uv_env\Scripts\python.exe` already exists and its `import pip` canary +succeeds, so venv creation is skipped -- the gate just above `:uv_venv_ready`): -**Scope note:** Parts I and II each document one feature area's edge cases. This Part documents -the OTHER thing this doc has never shown: 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. 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. +``` +[INFO] uv: reusing existing .uv_env +[INFO] HP_ENV_MODE=uv +[BOOT] REQ-009: Selected Python provider: UV. +``` -**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. +**conda-full lane** (`~env.state.json` is valid and the conda env's `python.exe` is present -- +`:env_state_fast_path`; this mechanism is explicitly bypassed in uv mode, since it exists purely +for the conda-specific case): -### Scenario 8: Pre-flight guards, lock acquisition, and entry detection on a clean run +``` +[INFO] Env-state fast path: reusing conda env _selftest_stub. +[BOOT] REQ-009: Selected Python provider: Conda (Portable) [fast path]. +``` -**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'`. +Confirmed firing across every scratch env in that job's log (not a one-off), so this is a broadly +reliable fast path, not a narrow coincidence. `self.stub.state_skip`'s own NDJSON assertion checks +for EITHER phrase, which is why one shared test scenario validates both depending on which lane +it runs under. -**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`): +**Both lanes then converge on the same dependency-install skip, immediately after dependency +discovery** (pipreqs + the Tier 1 autopep723 merge from Scenario 3 still run normally here -- +neither of the two fast paths above touches dependency DISCOVERY, only environment creation): ``` -[WARN] UNC paths not supported -Tue 07/28/2026 4:29:43.96 [INFO] REQ-015: Appending standard ignores to .gitignore. +[INFO] Dep-check: all pipreqs packages satisfied in lock; skipping conda install. ``` -**The very first console line above (`[WARN] UNC paths not supported`) is historical, from the -captured run, and no longer appears -- fixed since this capture, kept here unedited because it is -a real, timestamped log.** It came from a broken, redundant top-of-file `findstr`-based check (in -the file's unlabeled prologue before the first `:label`): `findstr /C:"\\\\"` was intended to -match a UNC-style double-backslash but did not correctly gate on one -- its exact internal parsing -(`findstr`'s own additional backslash-doubling behavior layered on cmd.exe's own C-runtime -backslash-before-quote argument parsing, documented in `docs/agent-lessons-learned.md`) was never -independently verified. What WAS confirmed is that it fired on 6/6 checked lanes against an -entirely ordinary local CI checkout path (`D:\a\Python_vs_Windows\Python_vs_Windows\tests\ -~envsmoke\`) -- not a UNC path. The companion, independent, correctly-targeted UNC-prefix check a -couple of lines below it (`if "%HP_SCRIPT_LAUNCH_DIR:~0,2%"=="\\"`, which prints the much louder -`*** WARNING: UNC/network paths detected...` banner) already covered the real UNC-detection job on -its own, so the broken check was simply removed rather than repaired -- see -`docs/agent-closed-backlog.md`'s Item 8 for the full trace. - -Between that line and `REQ-015`, nothing else prints -- `HP_APP_ARGS` capture (REQ-026, pure -variable assignment), the workspace-path-exists check, `cd /d`, and `HP_SCRIPT_ROOT` construction -(the rest of that same unlabeled prologue) are all silent by design on a clean pass. +This message literally says "skipping conda install" even in uv mode -- confirmed intentional +(a shared log line covering both providers via the same `HP_DEP_SKIP` flag), not a copy-paste +bug, so don't read it as evidence the wrong provider was used. **One nuance worth flagging so it +isn't misread as a second, real install still happening:** in conda mode specifically, an +unconditional "pip gap fill from `requirements.txt`" step still runs immediately after this skip +line, even though nothing was found missing -- it's a fast, harmless no-op safety net (confirmed +completing in well under a second in the real capture), not evidence the skip failed to take +effect. -**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. +**Net effect for the user:** a rebuild triggered by an ordinary source edit is meaningfully faster +than the very first run -- no fresh uv/conda acquisition, no fresh venv/env creation, and (when +nothing about the dependency set changed) no re-running of the actual install step -- while still +producing a genuinely fresh PyInstaller build and a real verification run of the new EXE. -**`: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): +## Part III: User configuration and CLI overrides + +**Scope note:** argv passthrough (extra launch arguments forwarded to the user's program, REQ-026) +is already fully covered as Scenario 42 -- cross-reference it rather than re-documenting it here. +This Part covers the remaining configuration surface: the five `PVW_*` super-user override +environment variables (distinct from `HP_TEST_*`, which are CI-only and out of scope for this +doc), and the CLI-argument/drag-and-drop entry-file override. + +**All five `PVW_*` variables share one generic acknowledgment line**, printed the instant the +variable is defined, before any real detection/acquisition work runs and regardless of whether the +value ever turns out to be usable: ``` -Chosen entry: app.py +[DEBUG] Using super-user override for PVW_: ``` -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. +**A real nuance worth stating up front rather than repeating per-variable:** the source's own +header comment describes all five uniformly as bypassing auto-detection, but that's only literally +true for two of them. `PVW_UV_EXE` and `PVW_WORKSPACE` genuinely skip the corresponding +detection/creation branch outright. `PVW_PYTHON_EXE` and `PVW_TARGET_PY` do NOT skip anything -- +the full normal detection logic still runs to completion (including real network/disk work), and +the override simply overwrites the *result* variable afterward. `PVW_CONDA_EXE` (Scenario 9) is +its own case again, discussed separately since it has a unique interaction with the conda +self-heal flow. ---- +### Scenario 8: `PVW_PYTHON_EXE` / `PVW_UV_EXE` / `PVW_TARGET_PY` / `PVW_WORKSPACE` -### Scenario 9: Provider acquisition and dependency install (uv-first) +**What's tested:** all four now have real, valid-value end-to-end CI coverage; `PVW_PYTHON_EXE` and +`PVW_WORKSPACE` also each have a real, invalid-value CI scenario (`tests/selfapps_pvw_overrides.ps1`, +`uv` lane, non-gating -- confirmed passing in the same fully-green CI run this whole document's +release cycle was verified against: run `30709255610`, job `91393894838`). Neither `PVW_UV_EXE` +nor `PVW_TARGET_PY` has invalid-value coverage of its own; that half of 8b/8c below stays +`[Extrapolated Branch]`, traced from source. -**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. +**8a. `PVW_PYTHON_EXE`** overrides `HP_PY` at the shared convergence point every REQ-009 provider +path (uv, conda, embed, venv, system, and every provider-cascade re-entry) funnels into after +already selecting and setting up a working interpreter -- so it does NOT skip provider +acquisition, it only overwrites the final result: -**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. +``` +[INFO] Python host: using super-user override PVW_PYTHON_EXE. +``` -**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 (matches CLAUDE.md's own documented -finding for Active Backlog item 15/`conda_avail`'s history -- the same distinction applies here). +Real, confirmed via `tests/selfapps_pvw_overrides.ps1`'s `self.pvw.python_exe.valid` scenario +(a two-stage test: stage 1 does an ordinary uv bootstrap to materialize a real interpreter, stage 2 +is a genuinely fresh scratch directory pointing `PVW_PYTHON_EXE` at stage 1's interpreter) -- the +override log line fires and the app runs successfully via the borrowed interpreter. Invalid-value +trace, confirmed by the same file's `self.pvw.python_exe.invalid` scenario (a nonexistent path): no +existence/executability check on the path itself; the first real probe is a non-fatal interpreter +smoke test (`[WARN] Interpreter smoke test failed (continuing).`) that does NOT abort the run -- +every subsequent `pip install` call is similarly wrapped in a WARN-only failure handler, so the +bootstrap proceeds all the way to the PyInstaller build attempt with a broken interpreter before +finally hitting a real failure there (the pre-existing, already-documented `:die`/`state=error` +path). A bad `PVW_PYTHON_EXE` is therefore detected early (one WARN) but not treated as fatal +until several steps downstream, not at the point of misuse. -**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): +**8b. `PVW_UV_EXE`** overrides `HP_UV_EXE` and genuinely skips the entire uv download/acquire +branch (jumps straight past it): ``` -[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. +[INFO] uv: using super-user override PVW_UV_EXE. ``` -(`~setup.log`-only, never shown on-screen: curl's own progress-bar text between the "downloading" -and "acquired" lines, plus uv's own managed-CPython fetch triggered by the version-detection call --- `Downloading cpython-3.14.6-windows-x86_64-none (download) (21.5MiB)` / -`Downloaded cpython-3.14.6-windows-x86_64-none (download)`. On a REPEAT run with `~uv_bin` already -populated, the cached-binary branch just before `:uv_acquire_download` instead logs `[INFO] uv: -cached binary found at ~uv_bin\uv.exe` -- not independently captured here since every CI scratch -dir is fresh; see this doc's repeat-run scenarios, added in a later pass, for the dedicated -treatment.) - -**uv venv creation** (console, no pre-existing version pin -- this app has neither `runtime.txt` -nor a `pyproject.toml` constraint): +REAL CI CAPTURE, run `30328748330`, job `90179708086` (`contract-uv` lane), +`tests/selfapps_contract_uv.ps1`'s dedicated uv-version-forwarding scenarios (which reuse an +already-downloaded `uv.exe` via this override specifically to avoid re-downloading it for each of +several sub-bootstraps): ``` -[INFO] uv: creating venv at .uv_env... -[INFO] uv: venv created at .uv_env -[INFO] HP_ENV_MODE=uv -[BOOT] REQ-009: Selected Python provider: UV. -[INFO] runtime.txt written: python-3.14.6 +[DEBUG] Using super-user override for PVW_UV_EXE: D:\a\...\~envsmoke\~uv_bin\uv.exe +[INFO] uv: using super-user override PVW_UV_EXE. +[INFO] uv: creating venv at .uv_env with Python 3.12... ``` -A version-constrained app (e.g. `pyproject.toml` with `requires-python = ">=3.9"`) instead logs -`[INFO] uv: creating venv at .uv_env with Python 3.9 or newer...` -- confirmed via a second real -capture in the same run (`tests/~pyproject_prec/~pyproject_prec_bootstrap.log`), an exact pin like -`python==3.11` templates to `...with Python 3.11...` per the `HP_UV_PY_DISP` derivation just -above `:uv_venv_ready` (not independently captured, but the template is a literal source string, -not inferred). `~setup.log`-only detail: uv's own raw venv output (`Using CPython 3.14.6`, -`Creating virtual environment with seed packages at: .uv_env`, a `Failed to hardlink files; -falling back to full copy` warning, `+ pip==26.1.2`, `Activate with: .uv_env\Scripts\activate`) -- -none of this reaches the console. +Invalid-value trace (`[Extrapolated Branch]`, no test forces a bad path): a broken/invalid +`PVW_UV_EXE` is fully absorbed by the existing REQ-009 provider-cascade fallback machinery -- the +uv-first Python-detection probe fails gracefully (WARN, falls toward Miniconda), and even if venv +creation is separately attempted with the same bad binary, an independent, exit-code-agnostic +on-disk check (`if not exist "...\Scripts\python.exe" goto :uv_venv_fail`) catches a binary that +misleadingly reports success without doing real work -- no crash, no silent success, clean +fall-through to conda. -**Dependency discovery** -- pipreqs and the REQ-005.12 `autopep723 check` Tier 1 merge running -TOGETHER, genuine production behavior (no `HP_SKIP_PIPREQS` test-isolation flag involved, unlike -the dedicated `selfapps_autopep_discovery.ps1`/`selfapps_pvw_idempotent.ps1` tests that deliberately -isolate one mechanism from the other): +**8c. `PVW_TARGET_PY`** overrides `PYSPEC` at the shared merge point both the uv-first and +conda-base detection paths converge on -- like `PVW_PYTHON_EXE`, detection still runs to +completion first: ``` -[INFO] pipreqs 0.4.13 installed successfully; using it for dependency discovery. -[INFO] pipreqs (direct) command: pipreqs . --force --mode compat --savepath "...\requirements.auto.txt" --ignore ".git,.github,.venv,venv,env,.uv_env,build,dist,__pycache__,tests" -*** [WARN] Dependencies were auto-detected (pipreqs) -*** [WARN] Auto-detection may be incomplete or incorrect -*** [INFO] Consider adding requirements.txt or PEP 723 metadata for reliability -[INFO] REQ-005.5: dependency source diff computed -- ~pipreqs.diff.txt -[INFO] REQ-005.12: autopep723 discovery merge complete. +[INFO] Python version: using super-user override PVW_TARGET_PY. ``` -**A real quirk worth flagging so it isn't misread**: the bootstrap log around this point also shows -`DEP_FINAL_COUNT=0` even though `colorama` is a genuine, real dependency that gets installed a few -steps later -- this count is taken BEFORE `requirements.auto.txt` is copied into `requirements.txt` -a few lines further down, still inside `:after_pipreqs_run`'s own tail, so `DEP_FINAL_COUNT=0` -here does not mean "pipreqs found nothing"; it's a pre-existing ordering quirk in the log sequence, not a bug in this -run. `~setup.log`-only (pipreqs's raw output is redirected to a SEPARATE file entirely, -`~pipreqs_direct.log`, not even `~setup.log` -- a real user never sees any of the following): +REAL CI CAPTURE, run `30328748330`, job `90179708094` (`conda-full` lane), +`tests/selfapps_pipgap.ps1` (sets this to `python=3.12` to pin conda's Python version so a +specific `opencv-python` wheel is guaranteed available for a different test purpose entirely): ``` -WARNING: Import named "colorama" not found locally. Trying to resolve it at the PyPI server. -WARNING: Import named "colorama" was resolved to "colorama:0.4.6" package (https://pypi.org/project/colorama/). -Please, verify manually the final list of requirements.txt to avoid possible dependency confusions. -INFO: Successfully saved requirements file in ...\requirements.auto.txt +[DEBUG] Using super-user override for PVW_TARGET_PY: python=3.12 +[INFO] Python version: using super-user override PVW_TARGET_PY. ``` -plus a handful of cosmetic `SyntaxWarning` lines from pipreqs 0.4.13's own bundled `docopt`/`yarg` -dependencies. `~setup.log` also shows the autopep723 merge's own uvx tool-bootstrap noise -(`Installed 1 package in 26ms`) and its own result line, `no-op: all autopep723 dependencies -already present` -- because pipreqs already found `colorama` first, autopep723's own independent -discovery of the same import is redundant here, a real working example of this repo's "augment, -never replace" design for the two mechanisms. +Invalid-value trace (`[Extrapolated Branch]`): no format validation; a garbage value becomes an +invalid conda package spec or an invalid `uv venv --python` request, surfacing as a real, +correctly-handled provider failure absorbed by the same fallback/cascade machinery as 8b -- +reaching a graceful `:die` (`state=error`) only if every fallback tier is also exhausted, never an +uncontrolled crash. -**Dependency install:** +**8d. `PVW_WORKSPACE`** overrides `HP_UV_ENV_PATH` (the uv venv's path) with a clean, immediate +override -- the default is assigned and instantly replaced before any use, unlike 8a/8c's +"let it run, override the result" pattern. **Scope limitation worth flagging explicitly: this +variable only takes effect in uv mode.** Conda's own environment path has no corresponding check +at all -- a conda-mode run ignores `PVW_WORKSPACE` entirely. + +Uniquely among the four, **there is no dedicated confirmation log line at its actual point of +use** -- only the generic top-of-file `[DEBUG]` line, which fires purely because the variable is +defined, before it's even known whether uv mode (where this variable matters at all) will be +selected. Real, confirmed via `tests/selfapps_pvw_overrides.ps1`'s own `self.pvw.workspace.valid` +scenario: ``` -[INFO] UV_USED=1 -[INFO] DEP_INSTALLED_CAPTURED=1 -[INFO] Environment snapshot written: ~environment.lock.txt -[INFO] REQ-005.11: PEP 723 header write-back succeeded via uv add --script. +[DEBUG] Using super-user override for PVW_WORKSPACE: ``` -`~setup.log`-only (`uv pip install --python ... -r requirements.txt`'s own raw output, never on -console): `Using Python 3.14.6 environment at: .uv_env`, `Resolved 1 package in 257ms`, `Prepared -1 package in 43ms`, `Installed 1 package in 15ms`, ` + colorama==0.4.6`. The real -`~environment.lock.txt` (uv mode copies the pip-freeze output here) contains more than just -`colorama` -- it also lists pipreqs's OWN transitive dependencies (`certifi`, `charset-normalizer`, -`docopt`, `idna`, `pipreqs`, `requests`, `urllib3`, `yarg`), because in `HP_ENV_MODE=uv`, pipreqs -is installed via `uv pip install --python "%HP_PY%"` into the SAME venv as the target app (inside -the pipreqs install step, before `:pipreqs_direct_done`), not an isolated tool venv. This is -genuine, expected production behavior --- a real user's `.uv_env` always ends up with pipreqs's own dependency footprint mixed in -alongside their actual dependencies, not a CI artifact. +with `Scripts\python.exe` genuinely present at the custom path afterward (and the default +`.uv_env` never created). Invalid-value trace, confirmed by the same test file's +`self.pvw.workspace.invalid` scenario (a path already occupied by a plain file, so uv cannot +create a venv "inside" it): no path validation up front, but the failure lands on exactly the +already-established `:uv_venv_fail` fallback chain 8b/8c also use, reaching the log line `falling +back to conda create` -- no crash, no silent success. --- -### Scenario 10: Build, verify, and the final status panel +### Scenario 9: `PVW_CONDA_EXE` and its interaction with the conda self-heal flow -**What's tested:** `self.stub.fastpath` (`tests/selftest.ps1`, real lane), a genuine first-run -(non-cached) build of a trivial one-line stub app (`hello_stub.py` = `print("hello-from-stub")`). -Chosen over Scenario 9's `colorama`-importing app for this piece specifically because it produces -a single, fully self-consistent, real capture spanning build through the final status panel with -zero staleness caveats -- strictly better evidence than the similar block quoted in Scenario 1 -(Part I), which has two lines marked "not yet re-confirmed against a fresh capture." +**What's tested:** `self.corrupt.conda.override_exit` (`tests/selftest.ps1`), self-contained by +construction (no ordering dependency on Miniconda already being installed elsewhere in the job, +unlike its sibling corrupt-conda scenarios). -**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` ("real" lane), -`tests/~selftest_stub/~stub_bootstrap.log`: +**Source:** REAL CI CAPTURE, run `30328748330`, both gating lanes (`real` job `90179708091` and +`conda-full` job `90179708094`), both `pass: true`, `exitCode: 2`. + +`PVW_CONDA_EXE` overrides the resolved conda batch-file path unconditionally, the instant it's +defined -- and because this happens BEFORE the "install Miniconda if missing" block, setting it +also skips the Miniconda download/install entirely: ``` -Tue 07/28/2026 4:55:47.55 [INFO] Building standalone executable -- this may take a minute or two... -Tue 07/28/2026 4:55:47.55 [INFO] (A stray one-line Windows message about a missing drive may appear next -- that is a known side effect from an unrelated background process, unrelated to your app; safe to ignore.) -The system cannot find the drive specified. -The system cannot find the drive specified. -Tue 07/28/2026 4:55:58.01 [INFO] PyInstaller produced dist\_selftest_stub.exe -Tue 07/28/2026 4:55:58.02 [DEBUG] warnfix: warn file found -Tue 07/28/2026 4:55:58.02 [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. -Tue 07/28/2026 4:55:58.39 [INFO] PyInstaller build artifacts cleaned up. -Tue 07/28/2026 4:55:58.40 [INFO] EXE smokerun: testing dist\_selftest_stub.exe -Tue 07/28/2026 4:55:58.41 [INFO] Running entry script smoke test via packaged EXE. -Tue 07/28/2026 4:55:58.43 [WARN] Verifying the built standalone EXE (PyInstaller) now: if it stays completely silent for about 30 seconds it will be force-stopped, but any output (including a prompt waiting on your input) keeps it running as long as needed. If your program is interactive, try answering its prompts through to its own quit/exit option now so we can confirm it exits cleanly. Either way, do not start real work in it yet or any unsaved work will be lost. -[INFO] Process ID 6076. If it seems stuck: Task Manager > Details tab > find this PID > End Task (this window stays open). -hello-from-stub -Tue 07/28/2026 4:55:59.87 [INFO] EXE smokerun: exited 0 (ok) -Tue 07/28/2026 4:55:59.88 [INFO] Entry smoke exit=0 -Tue 07/28/2026 4:55:59.88 [STATUS] Run Status: SUCCESS (Exit Code: 0) +[DEBUG] Using super-user override for PVW_CONDA_EXE: ``` -Build took ~10.5s wall-clock here (this run has effectively zero dependencies, so it's near the -fast end of what's possible). This run's PyInstaller build DID trigger the warnfix path (`warn -file found` -- expected, since even a bare stub still needs the platform-module filter pass) but -needed no repair install, so the informational filter messaging is present but purely -informational -- the "no warnfix repair needed" success case this doc previously had no example -of. `hello-from-stub` is the stub program's OWN real stdout, live-teed via -`tools/exe_smokerun.ps1`'s chunk-based `ReadAsync` reader (see Scenario 5's fuller writeup of that -mechanism) -- landing exactly between the PID line and the "exited 0" line, precisely where a real -user's own program output appears. The `[INFO] Process ID . If it seems stuck: Task Manager > -Details tab...` line is the already-shipped stuck-program recovery aid (`tools/exe_smokerun.ps1`, -see `docs/agent-interconnect.md`'s "Process-ID display for stuck-program recovery" section) -- -confirmed here as a genuine, working console line, not just source text. - -Immediately following (elective prompts, both auto-declined by CI -- see Scenario 11 below for -what a real user experiences here instead) and then the final panel: +**The special interaction, and the whole reason this variable gets its own scenario:** normally, +when a health check on the resolved conda binary fails, the bootstrapper offers an interactive +Y/N self-heal prompt that (on accept) deletes and rebuilds the entire Miniconda root. When +`PVW_CONDA_EXE` is set, this self-heal path is skipped outright -- the very FIRST check in the +corruption-handling subroutine, ahead of every other check including the CI auto-decline logic -- +because the bootstrapper will never auto-delete a path it doesn't own: ``` -*** Verification finished -- see the Run Status above. *** -*** You can run your program again now via the interpreter as an extra diagnostic check. *** -Tue 07/28/2026 4:55:59.91 [INFO] REQ-018: post-execution checkpoint (exe): declined (run footprint stays at one execution). +================================================================ + CORRUPTED PYTHON ENVIRONMENT DETECTED +================================================================ -*** 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. *** -Tue 07/28/2026 4:55:59.94 [INFO] Optimized build: declined. + The local conda installation appears to be broken. + This can happen after a Windows update or OS migration + (example: DLL load error 0xc000007b). -============================================================ - SETUP COMPLETE -============================================================ - Your standalone application is ready: - dist\_selftest_stub.exe + Affected path: - RUNNING YOUR APP - Double-click dist\_selftest_stub.exe to run it. + This binary was specified via PVW_CONDA_EXE: + - STARTUP MAY BE SLOW: a one-file .exe unpacks itself each time it - starts, so allow 10-15 seconds (longer for big libraries like - numpy/scipy/matplotlib, or when extra packages were bundled to fix - missing imports) before assuming it has hung. + Automatic self-healing is not available for user-managed conda. + Please fix or replace the binary at the path above, then re-run. +``` - If the window flashes and closes instantly: that's normal if - your program finished quickly or hit an error before printing - anything. To see what happened, open Command Prompt, cd to - this folder, and run: - dist\_selftest_stub.exe - This keeps the window open so you can read any messages. +followed by the exact error/exit sequence: - A progress indicator that updates in place may appear all at - once instead of live when run as the .exe -- that is a stdout - buffering difference between the .exe and the script, not an error. +``` +[ERROR] Corrupt user-managed conda (PVW_CONDA_EXE); fix manually. +``` - 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 "hello_stub.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\_selftest_stub.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. +and the process exits with code **2** -- notably, there is no Y/N prompt at all in this path, even +for a genuinely interactive real user; the override check runs before the interactivity dispatch +even has a chance to matter. - 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) - build\ -- PyInstaller build cache -============================================================ +### Scenario 10: Drag-and-drop / CLI entry-file override (REQ-011 same-directory rule + REQ-002 priority) -Tue 07/28/2026 4:56:00.03 [INFO] REQ-016: Post-flight briefing printed. +**What's tested:** three real, currently-passing NDJSON rows across two test files -- +`self.entry.req011.crossdir` and `self.entry.req011.sameDir` (`tests/selfapps_isolation.ps1`), and +`self.entry.override` (`tests/selfapps_ux_hardening.ps1`, which specifically proves the override +wins over auto-detection, not merely that dragging works at all). + +**Source:** REAL CI CAPTURE, run `30328748330`, both gating lanes (`real` job `90179708091` and +`conda-full` job `90179708094`), all three rows `pass: true` in both. + +A user can either type a `.py` filename as the first CLI argument to `run_setup.bat`, or literally +drag a `.py` file onto the `.bat` file's icon in Windows Explorer (Windows translates the drop +into the identical `%1` argument). **REQ-011's rule: the file must be in the SAME directory as +`run_setup.bat` itself**, checked twice for defense-in-depth (once as an early pre-flight check, +for instant feedback before any environment work begins, and once again inside the entry-selection +subroutine itself). A cross-directory attempt genuinely terminates the whole process (`exit /b 1`, +not merely a `call`-frame return) with: + +``` +[ERROR] REQ-011: Dragged files must reside in the bootstrapper root folder for environment cleanliness. ``` -The companion `tests/~selftest_stub/~bootstrap.status.json` reads: +(This is the raw, untimestamped console line; the separately-written log-file copy is a shorter +variant without the "for environment cleanliness" clause -- a real, source-confirmed difference, +not a typo, confirmed via real CI capture: `[ERROR] REQ-011: Dragged files must reside in the +bootstrapper root folder.`) -```json -{"state":"ok","exitCode":0,"pyFiles":1} +A same-directory file succeeds and prints the filename (a historical bug that once printed this +line with an EMPTY filename -- see `docs/agent-lessons-learned.md`'s "Provider-cascade dispatch is +goto-based on purpose" entry -- is long fixed; the current, correct text is shown below): + +``` +*** Using drag-and-drop file: ``` -`exitCode` here means "did the bootstrapper's own env/build lifecycle succeed" (a hardcoded `0` -written unconditionally at the success label), NOT the user program's own exit code -- that's -separately surfaced via the console's `[STATUS] Run Status: SUCCESS (Exit Code: 0)` line above. -Both happen to be 0 in this clean run, so the distinction isn't visible here, but it's worth -knowing they're two independent things (see CLAUDE.md's "User-code exit-code semantics" Known -Finding). +**Interaction with the REQ-002 interactive entry picker: fully and structurally skipped.** +Providing a valid same-directory file makes entry selection return immediately, before the +auto-detection block (and therefore the picker, which is only ever invoked from inside that same +block) is even reached -- this is REQ-002's documented "priority 0": a co-located override always +wins over auto-detected names, and can never trigger the ambiguous-case timed picker. Confirmed +positively (not just "dragging works," but that override genuinely beats auto-detection) by +`self.entry.override`'s real capture: a scratch directory staged with BOTH `main.py` (which would +win plain auto-detection by name-priority) and `zzz_override.py`, with `zzz_override.py` passed as +the override -- the real run confirms the drag message names the override file, the entry-selected +log line names `zzz_override.py` (not `main.py`), and the override file's own distinguishing output +is what actually ran. --- -### Scenario 11: The two elective prompts a real user faces after every successful run +## Part IV: Adversarial and recovery branches -**What's tested:** the framing text of both prompts is confirmed via real CI capture (they're -unconditionally echoed even when CI auto-declines); the actual `[Y/N]` question lines themselves -are never visible in ANY CI log, by construction -- they live inside the branch of an `if/elif` -chain that CI's `HP_CI_LANE` auto-decline always short-circuits past, even in tests that force an -accept via an `HP_TEST_*_ANSWER` override (the override branch assigns the answer variable -directly, bypassing the real `set /p` prompt entirely). Both are therefore genuinely -**`[Extrapolated Branch]`** for the exact prompt-line wording specifically, cited from source, even -though the surrounding framing text is a real capture. +**Scope note:** excludes `HP_TEST_*` CI-only flags as the documented subject -- they're the +mechanism a test uses to force a scenario deterministically, but the resulting console text below +is exactly what a real user hits when the same underlying condition occurs for real (a genuine +flaky connection, a genuinely corrupted conda install, a genuinely missing bundled module, and so +on). Evidence for this entire Part is pulled from run `30328748330` (commit `5872028`, all lanes +green): `real` (job `90179708091`), `conda-full` (job `90179708094`), `uv` (job `90179708109`), +`justme-test` (job `90179708103`). -**Source:** framing lines are REAL CI CAPTURE (run `30328748330`, job `90179708091`, "real" lane, -same log as Scenario 10 above); the two `set /p` prompt lines themselves are -`[Extrapolated Branch]`, cited from `:run_postexec_checkpoint` and `:offer_optimized_build` -respectively. +### Scenario 11: Network connectivity check and transient-retry (REQ-013 + REQ-022) -**Relationship to README.md's `[REQ-018]` spec, since the two don't map 1:1 in an obvious way:** -`:run_postexec_checkpoint`'s own log line is explicitly tagged `REQ-018` in source (`[INFO] -REQ-018: post-execution checkpoint (...): accepted/declined...`), so it -- not a separate, -undocumented prompt -- is the shipped implementation of README's "After a build, the real run is -offered, not forced" / "Consent before any extra run" bullets. Two nuances worth flagging -explicitly rather than leaving implicit: (1) the offered rerun uses the INTERPRETER, not a second -launch of the packaged EXE -- README's "offers to launch the app untimed for real" phrasing reads -as if it means the EXE specifically, but the actual accepted-path log line says "running a second -time via the interpreter", and a companion note the same subroutine prints for the EXE call site -spells this out too ("this diagnostic run uses the interpreter, not the packaged EXE, so behavior -can differ"); (2) README's separate REQ-018 bullet about the FIRST (mandatory) verification run -being "force-stopped after a short interval even if running fine" is stale relative to the -activity-aware-kill behavior actually shipped later (see Scenario 5's fuller writeup and CLAUDE.md's -Closed Backlog "Activity-aware EXE-smoke kill" entry) -- the real WARN text quoted in Scenario 10 -above says the opposite: any output at all, not just a clean exit, keeps the run alive indefinitely, -only a genuinely SILENT process gets force-stopped. Neither nuance changes what actually ships; -both are flagged here because a reader cross-checking this scenario against README's prose could -otherwise reasonably conclude something was missed. +**What's tested:** `self.ux.connectivity.*` rows (`tests/selfapps_ux_hardening.ps1`, real lane); +`self.stub.conda_retry`/`self.stub.conda_create_retry`/`self.stub.conda_perpkg` +(`tests/selftest.ps1`, conda-full lane). All real, all passing. + +When a download genuinely fails, `:check_net_after_dl_fail` (REQ-013) pings `8.8.8.8` (2 attempts) +then, if ICMP is blocked, tries an HTTPS reachability check against `conda.anaconda.org` (2 +attempts) before concluding the user is actually offline -- this doubled-attempt design exists +because a single dropped ICMP echo or a momentarily-contended connect on a busy machine is enough +to misclassify a genuinely-online host as offline (this was root-caused from a REAL CI flake, not +a hypothetical). If both checks fail, a real user sees an unbounded prompt: + +``` +[WARN] REQ-013: Connectivity check: no internet detected (ICMP and HTTPS check failed). +WARNING: No internet connection detected. Remote providers may fail. Retry? (Fix connection then press Y) or proceed offline (N): +``` + +Pressing Enter with no answer defaults to offline; `Y` re-tries (2 more ping attempts, then 2 more +HTTPS attempts) before re-prompting on continued failure; `N` proceeds offline (`HP_OFFLINE_MODE=1` +for the rest of the run). + +**Transient-retry for `conda create` and conda's bulk package install (REQ-022)** -- both use the +identical detect-and-retry-once pattern: scan the failure output for `CondaHTTPError`/`Failed to +fetch`/`timed out`/`ConnectionError`, wait 15 seconds, retry exactly once. If the retry ALSO fails, +it falls straight through to the normal (non-transient) failure/fallback chain -- this is not an +infinite-retry loop. -Right after a normal successful verification run (Scenario 10's tail), a real double-click user -sees: +`conda create` retry, REAL CI CAPTURE (`tests/selftest.ps1`'s conda-full sub-bootstrap): ``` -*** Verification finished -- see the Run Status above. *** -*** You can run your program again now via the interpreter as an extra diagnostic check. *** - Run again via the interpreter now? [Y/N] _ +Creating Python environment '_selftest_conda_create_retry' -- this may take several minutes... +Conda environment creation failed -- possible network or repository issue. Retrying once... +[INFO] conda create: transient failure detected; retrying after 15s. +Retrying environment creation... +[INFO] runtime.txt written: python-3.14.6 +[BOOT] REQ-009: Selected Python provider: Conda (Portable). ``` -(cursor sits after `[Y/N] `, waiting indefinitely -- `:run_postexec_checkpoint`, an UNBOUNDED -`set /p`, no timeout of any kind). Any answer other than a leading `Y`/`y` -- including just -pressing Enter -- resolves to decline. If accepted, the entry program runs a second time via the -interpreter (not the packaged EXE) as a diagnostic; if declined, the bootstrap immediately -continues to the second prompt: +Bulk package install retry, REAL CI CAPTURE (same test file, a different scratch env): ``` -*** 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. *** - Build the optimized version now? [Y/N] _ +Conda install failed -- possible network or repository issue. Retrying once... +[INSTALL] conda bulk: transient failure detected; retrying after 15s. +Retrying package installation... ``` -(same shape -- `:offer_optimized_build`, also an unbounded `set /p`, also defaults to decline on -anything but a leading Y). Both prompts genuinely fire on essentially every -successful default run (the checkpoint is called after every clean verification; the optimized- -build offer only skips if the AV-Safe-Build-Path Tier A Nuitka fallback already ran, or the -verification itself failed) -- **this is not an edge case, it's what most real users see twice in -a row at the very end of an otherwise fully successful first run.** +A NON-transient bulk failure (any error text that doesn't match the transient patterns above) +skips the retry entirely and instead falls back to installing packages one at a time +(`self.stub.conda_perpkg`, REAL CI CAPTURE, `[INSTALL] conda per-pkg fallback`) -- a completely +separate recovery path from the transient-retry one, chosen based on what kind of failure +actually occurred. -**CI cannot show either question line, ever, structurally -- not just "doesn't currently show -them."** Both prompts follow this repo's own established CI-safe-gate pattern (see -`docs/agent-interconnect.md`'s "CI-safe interactive gates" section): echo the framing -unconditionally, THEN branch on `HP_TEST_*_ANSWER` override / `HP_CI_LANE` auto-decline / real -`set /p`. Because the actual question text lives inside the `set /p` call itself (not a separate -unconditional `echo`), and CI always takes one of the first two branches, no CI log -- gating or -non-gating, auto-decline or forced-accept -- can ever contain the literal `" Run again via the -interpreter now? [Y/N] "` or `" Build the optimized version now? [Y/N] "` text. This is a genuine -blind spot in what CI evidence alone can show about this bootstrapper's real user-facing behavior, -worth keeping in mind when reading any other scenario in this file that involves a `set /p`-based -consent gate. +--- -**For contrast, briefly (full treatment is Pass 4/adversarial-recovery territory, not this -Part):** not every consent gate in this file shares the "blocks forever" shape. The REQ-009 -provider-cascade gate (`:cascade_consent_gate`, only reached if a build succeeds but a dependency -repair genuinely fails) is the one gate that's genuinely TIMED for a real user -- `choice /C YN /N -/T 30 /D N`, defaulting to decline after 30 seconds with no answer, so it structurally cannot hang -forever even for a truly unattended user. The REQ-014 system-Python consent gate -(`:system_python_consent_gate`, only reached as the absolute last-resort Tier 4) is unbounded like -the two documented above, but its full question text (unusually, including the actual `[y/n]` -wording) IS an unconditional `echo` rather than living inside `set /p` -- so, unlike this -scenario's two prompts, CI logs genuinely do show the complete question for that one, e.g. -`Proceed with System Python? (Global pollution risk) [y/n]: y to accept, n to decline.` (REAL CI -CAPTURE, same run, job `90179708091`) -- only its own terse follow-up `"Your choice [y/n]: "` line -is hidden the same way. Neither of these two gates fires on this Part's happy path; both are -documented fully in Pass 4. +### Scenario 12: Corrupted-conda self-heal (detect / decline / accept) ---- +**What's tested:** `self.corrupt.conda.detect`/`.heal.decline`/`.heal.accept` +(`tests/selftest.ps1`, conda-full lane, all three real and passing). The `PVW_CONDA_EXE`-override +variant is already covered in Scenario 9 -- this scenario is the DEFAULT case, where the +bootstrapper owns the conda install and can offer to fix it. -## Part IV: Second run, nothing changed (repeat-run fast paths) +**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708094` (`conda-full` lane). -**Scope note:** this Part documents the success side of running the bootstrapper a SECOND time in -the same folder with nothing changed -- same entry file, same requirements, no test flags. The -FAILURE side of one of these fast paths (a stale cached EXE that's kept and later exits non-zero) -is already documented as Scenario 7b (Part II); this Part doesn't repeat that. Evidence again -comes from a recent clean green run (`30328748330`, commit `5872028`) rather than one single -dedicated "repeat run" test file -- `tests/selfapps_envsmoke.ps1` re-invokes `run_setup.bat` a -second time in the same scratch directory with nothing changed (the EXE fast path), and -`tests/selftest.ps1`'s stub scenario and `tests/selfapps_depcheck.ps1` go one step further -- -`Run 1` (fresh bootstrap), `Run 2` (identical, EXE fast path), then deliberately touch the source -file and run a THIRD time -- exercising the "source changed just enough to force a rebuild, but -the environment itself doesn't need recreating" fast paths this Part's second scenario covers. +When a health check on the resolved conda binary fails, the user sees: -### Scenario 12: The EXE fast path (nothing changed at all) +``` +================================================================ + CORRUPTED PYTHON ENVIRONMENT DETECTED +================================================================ -**What's tested:** `self.fastpath` (`tests/selfapps_envsmoke.ps1`'s second, back-to-back -invocation of `run_setup.bat` in the same scratch directory, zero CLI arguments, nothing touched). + The local conda installation appears to be broken. + This can happen after a Windows update or OS migration + (example: DLL load error 0xc000007b). -**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` ("real" lane), the console -capture of that second invocation (`~envsmoke_fastpath.log`) plus the matching block of -`~setup.log`. + Affected path: C:\Users\Public\Documents\Miniconda3 +``` -**What fires FIRST, before any provider/entry/dependency logic even starts:** `:try_fast_exe` is -called immediately after environment-name derivation and the Python-file count, right at the top -of the file -- before uv acquisition, before Miniconda, before `:determine_entry`, before -anything else. It runs exactly ONE real check: compare `dist\.exe`'s modification time -against the newest non-infrastructure `.py` file's modification time (via the embedded -`HP_FAST_CHECK` helper). If the EXE is newer-or-equal, the whole rest of the bootstrap short- -circuits straight to `:success`. +followed by an unbounded `[Y/N]` prompt (` Would you like to delete it and rebuild? [Y/N] `, +`[Extrapolated Branch]` for the exact prompt line, same reasoning as Scenario 5's two prompts -- +it lives inside `set /p`, never visible in a CI log even when a test forces the accept branch via +an answer override). -**Non-interactive console text (what CI captures -- this is also exactly what a real user would -see if they ran the bootstrapper non-interactively, e.g. from a script):** +**Decline** (real capture): ``` -[WARN] UNC paths not supported -Tue 07/28/2026 4:30:33.81 [INFO] Environment name: _envsmoke -Tue 07/28/2026 4:30:33.83 [INFO] Host OS: Microsoft Windows [Version 10.0.26100.32995] -Tue 07/28/2026 4:30:34.04 [INFO] Host PowerShell: 5.1.26100.32995 -Tue 07/28/2026 4:30:34.10 [INFO] Python file count: 1 -Tue 07/28/2026 4:30:34.98 [INFO] Fast path: reusing dist\_envsmoke.exe -Tue 07/28/2026 4:30:35.96 [INFO] Entry smoke exit=0 -Tue 07/28/2026 4:30:35.97 [STATUS] Run Status: SUCCESS (Exit Code: 0) -Tue 07/28/2026 4:30:35.99 [INFO] Fast path: skipping PyInstaller rebuild for existing dist\_envsmoke.exe + Exiting without changes. Delete the folder above manually, + then run this setup again. ``` -No "SETUP COMPLETE" postflight banner appears -- confirmed both structurally (that dispatch is -gated on the fast path NOT having fired) and in the raw capture, which ends right after the last -line above. `~setup.log` shows one extra line between "Fast path: reusing" and "Entry smoke -exit=0" that never reaches the console (`Fast path command: "dist\_envsmoke.exe" > "~run.out.txt" -2> "~run.err.txt"`) -- a raw log-file-only write, not part of what a user actually sees. +logged as `[ERROR] Corrupt conda env; user declined rebuild.`, exit code 2. -**Interactive console text (what a genuine double-click end user sees -- differs from CI because -`HP_CI_LANE` is unset, so `:try_fast_exe` takes its OTHER branch, `:try_fast_exe_probe`, which -launches the cached EXE through the same never-kills fail-fast probe mechanism Scenario 5 -documents rather than the plain redirect above).** Assembled from real, independently-confirmed -fragments (the header and PID lines are genuine captured text from a different test that forces -this same interactive branch; their pairing into a clean, fast, successful sequence is -`[Extrapolated Branch]`, grounded directly in source rather than guessed): +**Accept** (real capture; this specific test run additionally forces the actual re-download step +to be skipped for CI-safety reasons, so the "Downloading fresh copy..." line below is +`[Extrapolated Branch]`, cited from source, while everything else is real): ``` -[WARN] UNC paths not supported -Tue 07/28/2026 4:30:33.81 [INFO] Environment name: _envsmoke -Tue 07/28/2026 4:30:33.83 [INFO] Host OS: Microsoft Windows [Version 10.0.26100.32995] -Tue 07/28/2026 4:30:34.04 [INFO] Host PowerShell: 5.1.26100.32995 -Tue 07/28/2026 4:30:34.10 [INFO] Python file count: 1 -Tue 07/28/2026 4:30:34.98 [INFO] Launching your program now via the cached standalone EXE (PyInstaller build): dist\_envsmoke.exe -[INFO] Process ID 7692. If it seems stuck: Task Manager > Details tab > find this PID > End Task (this window stays open). - <- the app's own live stdout/stderr tees here, if any -Tue 07/28/2026 4:30:35.96 [INFO] Entry smoke exit=0 -Tue 07/28/2026 4:30:35.97 [STATUS] Run Status: SUCCESS (Exit Code: 0) -Tue 07/28/2026 4:30:35.99 [INFO] Fast path: skipping PyInstaller rebuild for existing dist\_envsmoke.exe -Press any key to continue . . . + [INFO] Removing corrupt Miniconda installation... + [INFO] Corrupt installation removed. Downloading fresh copy... ``` -The "still running after Nms, keep waiting?" WARN line that the fail-fast probe can print is -conditional on the process actually exceeding its short classification window (confirmed absent -here via real CI capture of the same forced-interactive mechanism failing fast in an unrelated -scenario) -- omitted above since "nothing changed, app still runs fine" implies a normal-speed -exit. `Press any key to continue . . .` is `cmd.exe`'s own native output from a `pause` at the -very end of the main line, gated on `HP_CI_LANE` being unset -- real end-user only, never appears -in any CI log. +``` +[INFO] Self-healing: corrupt conda evicted from C:\Users\Public\Documents\Miniconda3. +[INFO] Workspace: ...\tests\~selftest_heal_accept +[INFO] Env name: _selftest_heal_accept +[INFO] HP_ENV_MODE=conda +[INFO] Creating Python environment '_selftest_heal_accept' -- this may take several minutes... +[INFO] runtime.txt written: python-3.14.6 +[BOOT] REQ-009: Selected Python provider: Conda (Portable). +``` -**Why it's fast -- everything this run skips entirely, not just runs faster:** once `:try_fast_exe` -succeeds, the bootstrap jumps to `:success` before any of the following ever execute: the -`HP_CI_SKIP_ENV` dispatch, the entire uv acquisition block (no download, no `uv venv`, no -`UV_PYTHON_PREFERENCE` even gets set), `:select_conda_bat` and all Miniconda install/probe logic, -the env-state fast path (moot -- conda was never considered), `:conda_base_update`, the -`HP_PREP_REQUIREMENTS` heuristic dependency augmentation, `:determine_entry` (the cached EXE's -identity is trusted as-is, no REQ-002 re-selection), pipreqs entirely (no `pipreqs.install`/ -`pipreqs.run`, no `requirements.auto.txt` diff), and -- the single biggest reason this is fast -- -`:run_entry_smoke` never runs, meaning no `py_compile` preflight and no PyInstaller build -invocation of any kind, cached or otherwise. The reused EXE genuinely gets EXECUTED, not merely -detected -- confirmed by the real `Entry smoke exit=0`/`[STATUS]` lines above, which come from an -actual process launch. +A real user's accept path re-downloads and reinstalls Miniconda from scratch (the full +acquisition sequence from Scenario 3), then proceeds exactly as a fresh first run would. --- -### Scenario 13: Source touched just enough to force a rebuild, but the environment is reused +### Scenario 13: Miniconda install chain (AllUsers -> JustMe -> both-failed) -**What's tested:** `tests/selftest.ps1`'s stub scenario and `tests/selfapps_depcheck.ps1`, both of -which do Run 1 (fresh) -> Run 2 (Scenario 12's EXE fast path) -> touch the entry file's content -and modification time -> Run 3, which is the case documented here: the EXE fast-path timestamp -check now fails (source is newer than the cached EXE), so PyInstaller reruns and produces a new -EXE -- but the ENVIRONMENT itself (the uv venv or conda env, and already-satisfied dependencies) -is recognized as still valid and reused rather than recreated from scratch. +**What's tested:** `conda.install.justme` (`tests/selftest.ps1`, `justme-test` lane, real, +passing) and `self.conda.bothfail` (`tests/selfapps_conda_bothfail.ps1`, `uv` lane, real, +passing). -**Source:** REAL CI CAPTURE, run `30328748330`, jobs `90179708091` ("real" lane, uv-first) and -`90179708094` ("conda-full" lane). +**Source:** REAL CI CAPTURE, run `30328748330`, jobs `90179708103` (`justme-test`) and +`90179708109` (`uv`). -**uv-first lane** (`.uv_env\Scripts\python.exe` already exists and its `import pip` canary -succeeds, so venv creation is skipped -- the gate just above `:uv_venv_ready`): +Miniconda install first attempts an AllUsers (machine-wide) install; if UAC rejects elevation (or +the process simply isn't elevated), it skips straight to a JustMe (per-user) install instead, no +wasted attempt. **Both the "skip, never attempted" path and a genuine post-attempt AllUsers +failure fall through to the same shared `:tci_justme` label** (`run_setup.bat`) -- the label checks +a flag set only right before the real AllUsers install attempt, so the two paths get distinct +wording instead of both unconditionally claiming AllUsers "failed." On the common non-elevated +machine (skip path, `[Extrapolated Branch]`, cited from source): ``` -[INFO] uv: reusing existing .uv_env -[INFO] HP_ENV_MODE=uv -[BOOT] REQ-009: Selected Python provider: UV. +[INFO] Not elevated; skipping AllUsers Miniconda install. +[INFO] Miniconda AllUsers install skipped (not elevated); trying JustMe install instead. +[INFO] Miniconda installed (JustMe fallback). ``` -**conda-full lane** (`~env.state.json` is valid and the conda env's `python.exe` is present -- -`:env_state_fast_path`; this mechanism is explicitly bypassed in uv mode, since it exists purely -for the conda-specific case): +A genuine, post-attempt AllUsers failure still gets the original WARN wording, now also carrying +the installer's own exit code and a reason token (`[Extrapolated Branch]`, cited from source -- +this branch requires a real elevated process whose AllUsers installer genuinely fails, which no +current CI hook forces without also forcing the skip path): ``` -[INFO] Env-state fast path: reusing conda env _selftest_stub. -[BOOT] REQ-009: Selected Python provider: Conda (Portable) [fast path]. +[WARN] Miniconda AllUsers install failed (exitCode=1, reason=installer_failed); retrying with JustMe. ``` -Confirmed firing across every scratch env in that job's log (not a one-off), so this is a broadly -reliable fast path, not a narrow coincidence. `self.stub.state_skip`'s own NDJSON assertion checks -for EITHER phrase, which is why one shared test scenario validates both depending on which lane -it runs under. - -**Both lanes then converge on the same dependency-install skip, immediately after dependency -discovery** (pipreqs + the Tier 1 autopep723 merge from Scenario 9 still run normally here -- -neither of the two fast paths above touches dependency DISCOVERY, only environment creation): +If the installer instead hits `:run_installer_timeout`'s own 60-minute ceiling (see that +subroutine's header comment), the exit code is a hardcoded sentinel, not the installer's real +exit code -- reported as `reason=timeout` with no fabricated `exitCode` field instead +(`[Extrapolated Branch]`, an even rarer sub-case of the one above, never observed in CI): ``` -[INFO] Dep-check: all pipreqs packages satisfied in lock; skipping conda install. +[WARN] Miniconda AllUsers install failed (reason=timeout); retrying with JustMe. ``` -This message literally says "skipping conda install" even in uv mode -- confirmed intentional -(a shared log line covering both providers via the same `HP_DEP_SKIP` flag), not a copy-paste -bug, so don't read it as evidence the wrong provider was used. **One nuance worth flagging so it -isn't misread as a second, real install still happening:** in conda mode specifically, an -unconditional "pip gap fill from `requirements.txt`" step still runs immediately after this skip -line, even though nothing was found missing -- it's a fast, harmless no-op safety net (confirmed -completing in well under a second in the real capture), not evidence the skip failed to take -effect. +**If JustMe ALSO fails** (both installation options exhausted; REAL CI CAPTURE for the skip-path +lines, `[Extrapolated Branch]` for the now-corrected wording): -**Net effect for the user:** a rebuild triggered by an ordinary source edit is meaningfully faster -than the very first run -- no fresh uv/conda acquisition, no fresh venv/env creation, and (when -nothing about the dependency set changed) no re-running of the actual install step -- while still -producing a genuinely fresh PyInstaller build and a real verification run of the new EXE. +``` +[INFO] Not elevated; skipping AllUsers Miniconda install. +[INFO] Miniconda AllUsers install skipped (not elevated); trying JustMe install instead. +[ERROR] Miniconda install failed (AllUsers skipped -- not elevated; JustMe also failed). +``` + +This is a genuine `:die` (`state=error`), not a graceful degrade -- with no Python interpreter +acquirable at all through conda, there's nothing left for this tier to hand back. Both installer +launches are bounded by a generous 60-minute timeout (not unbounded), based on real-world reports +of Miniconda's silent installer hanging indefinitely on some machines. --- -## Part V: User configuration and CLI overrides +### Scenario 14: Standalone embed-tier download (REQ-009 Tier 5) -- decline and real success -**Scope note:** argv passthrough (extra launch arguments forwarded to the user's program, REQ-026) -is already fully covered as Scenario 6 -- cross-reference it rather than re-documenting it here. -This Part covers the remaining configuration surface: the five `PVW_*` super-user override -environment variables (distinct from `HP_TEST_*`, which are CI-only and out of scope for this -doc), and the CLI-argument/drag-and-drop entry-file override. +**What's tested:** `self.embed.fallback.decline`/`self.embed.fallback.real` +(`tests/selfapps_ux_hardening.ps1`, `uv` lane, both real and passing). -**All five `PVW_*` variables share one generic acknowledgment line**, printed the instant the -variable is defined, before any real detection/acquisition work runs and regardless of whether the -value ever turns out to be usable: +**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708109` (`uv` lane). + +When uv and conda have both failed (or been declined/exhausted), the bootstrapper tries +downloading a private, checksummed Python interpreter directly from python.org -- no consent +prompt (unlike system Python), since this is a bootstrapper-controlled, disposable extraction: ``` -[DEBUG] Using super-user override for PVW_: +[WARN] Attempting embedded Python download (REQ-009 Tier 5)... ``` -**A real nuance worth stating up front rather than repeating per-variable:** the source's own -header comment describes all five uniformly as bypassing auto-detection, but that's only literally -true for two of them. `PVW_UV_EXE` and `PVW_WORKSPACE` genuinely skip the corresponding -detection/creation branch outright. `PVW_PYTHON_EXE` and `PVW_TARGET_PY` do NOT skip anything -- -the full normal detection logic still runs to completion (including real network/disk work), and -the override simply overwrites the *result* variable afterward. `PVW_CONDA_EXE` (Scenario 15) is -its own case again, discussed separately since it has a unique interaction with the conda -self-heal flow. +**Real success** (a genuine end-to-end download/verify/extract/patch/pip-bootstrap/canary/build/ +run, REAL CI CAPTURE): + +``` +[INFO] Downloading embedded Python 3.14.6 from https://www.python.org/ftp/python/3.14.6/python-3.14.6-embed-amd64.zip... +[INFO] embed fallback: 3.14.6 extracted and verified. +``` -### Scenario 14: `PVW_PYTHON_EXE` / `PVW_UV_EXE` / `PVW_TARGET_PY` / `PVW_WORKSPACE` +**Tier exhaustion** (this specific tier also fails, e.g. offline -- REAL CI CAPTURE): -**What's tested:** `PVW_UV_EXE` and `PVW_TARGET_PY` each have real, valid-value end-to-end CI -coverage (details below). `PVW_PYTHON_EXE` and `PVW_WORKSPACE` have **zero** test coverage of any -kind -- not even a valid-value smoke test -- confirmed via a repo-wide search across every test -file and every lane of a recent clean run. **No test anywhere exercises an INVALID value for any -of the four** -- all invalid-value behavior below is `[Extrapolated Branch]`, traced from source. +``` +[WARN] embed fallback: offline mode; cannot download embedded Python. +``` -**14a. `PVW_PYTHON_EXE`** overrides `HP_PY` at the shared convergence point every REQ-009 provider -path (uv, conda, embed, venv, system, and every provider-cascade re-entry) funnels into after -already selecting and setting up a working interpreter -- so it does NOT skip provider -acquisition, it only overwrites the final result: +A genuine download failure (not offline, an actual failed transfer) retries the WHOLE +download+verify cycle once before giving up. Real, confirmed via `self.embed.dl.retry` +(`tests/selfapps_ux_hardening.ps1`, `uv` lane, non-gating -- `HP_TEST_FORCE_EMBED_DL_FAIL_ONCE=1` +deterministically fails only the first attempt, no network touched, then a real second attempt +succeeds): ``` -[INFO] Python host: using super-user override PVW_PYTHON_EXE. +[TEST] HP_TEST_FORCE_EMBED_DL_FAIL_ONCE: simulating download failure on attempt 1 (no network touched). +[WARN] embed fallback: download failed; retrying once. ``` -`[Extrapolated Branch]`, genuinely untested. Invalid-value trace: no existence/executability check -on the path itself; the first real probe is a non-fatal interpreter smoke test -(`[WARN] Interpreter smoke test failed (continuing).`) that does NOT abort the run -- every -subsequent `pip install` call is similarly wrapped in a WARN-only failure handler, so the -bootstrap proceeds all the way to the PyInstaller build attempt with a broken interpreter before -finally hitting a real failure there (the pre-existing, already-documented `:die`/`state=error` -path). A bad `PVW_PYTHON_EXE` is therefore detected early (one WARN) but not treated as fatal -until several steps downstream, not at the point of misuse. +followed by the tier succeeding end to end on the second attempt. A checksum mismatch is treated +the same way (redownload, not just re-verify, since a mismatch can mean a truncated download +rather than a bad pin) -- that specific trigger has no dedicated test of its own, `[Extrapolated +Branch]` for that one detail. -**14b. `PVW_UV_EXE`** overrides `HP_UV_EXE` and genuinely skips the entire uv download/acquire -branch (jumps straight past it): +--- + +### Scenario 15: REQ-009 provider cascade -- one real run showing the FULL chain + +**What's tested:** `self.cascade.exec` (`tests/selfapps_cascade.ps1`, `uv` lane, real, passing). +This single test happens to exercise BOTH mid-exhaustion (uv/conda/embed all cascade past) AND +full exhaustion (system Python, the final tier, declines) in one coherent real capture -- an +unusually complete real-world illustration of this mechanism. + +**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708109` (`uv` lane). + +After a build succeeds but a dependency repair genuinely fails (Scenario 17 covers what triggers +this), the user is offered (Scenario 5's timed `:cascade_consent_gate`, up to 30s, default +decline) a chance to try the next Python provider. On acceptance, the REAL capture below shows the +cascade working through every tier in order -- each hop re-attempts the full dependency phase +under the new provider, and each hop that ALSO can't fully resolve dependencies triggers another +verification failure and another cascade offer: ``` -[INFO] uv: using super-user override PVW_UV_EXE. +[INFO] REQ-009: cascading provider uv to conda; re-attempting dependencies. +*** [INFO] Trying the next Python provider (conda) to resolve dependencies... ``` -REAL CI CAPTURE, run `30328748330`, job `90179708086` (`contract-uv` lane), -`tests/selfapps_contract_uv.ps1`'s dedicated uv-version-forwarding scenarios (which reuse an -already-downloaded `uv.exe` via this override specifically to avoid re-downloading it for each of -several sub-bootstraps): +``` +[INFO] REQ-009: cascading provider conda to embed; re-attempting dependencies. +*** [INFO] Trying the next Python provider (embed) to resolve dependencies... +``` ``` -[DEBUG] Using super-user override for PVW_UV_EXE: D:\a\...\~envsmoke\~uv_bin\uv.exe -[INFO] uv: using super-user override PVW_UV_EXE. -[INFO] uv: creating venv at .uv_env with Python 3.12... +[INFO] REQ-009: cascading provider embed to venv; re-attempting dependencies. +*** [INFO] Trying the next Python provider (venv) to resolve dependencies... ``` -Invalid-value trace (`[Extrapolated Branch]`, no test forces a bad path): a broken/invalid -`PVW_UV_EXE` is fully absorbed by the existing REQ-009 provider-cascade fallback machinery -- the -uv-first Python-detection probe fails gracefully (WARN, falls toward Miniconda), and even if venv -creation is separately attempted with the same bad binary, an independent, exit-code-agnostic -on-disk check (`if not exist "...\Scripts\python.exe" goto :uv_venv_fail`) catches a binary that -misleadingly reports success without doing real work -- no crash, no silent success, clean -fall-through to conda. +**Note the log wording says "uv to conda", never "uv -> conda"** -- deliberate: `:log` echoes +unquoted, so a literal `>` would be parsed as shell redirection and silently eat the line. -**14c. `PVW_TARGET_PY`** overrides `PYSPEC` at the shared merge point both the uv-first and -conda-base detection paths converge on -- like `PVW_PYTHON_EXE`, detection still runs to -completion first: +Right before each cascade offer, the elective postexec-checkpoint/optimized-build prompts from +Scenario 5 are SKIPPED (not just auto-declined) once cascade is approved -- confirmed in this +same real capture: ``` -[INFO] Python version: using super-user override PVW_TARGET_PY. +[INFO] Entry smoke exit=1 +[STATUS] Run Status: FAILED (Exit Code: 1) +[INFO] REQ-009: cascade approved; skipping the post-verification offers for this build. ``` -REAL CI CAPTURE, run `30328748330`, job `90179708094` (`conda-full` lane), -`tests/selfapps_pipgap.ps1` (sets this to `python=3.12` to pin conda's Python version so a -specific `opencv-python` wheel is guaranteed available for a different test purpose entirely): +**Full exhaustion**, reached when venv ALSO can't fully resolve dependencies and the cascade +offers the absolute last resort, system Python (REQ-014's consent gate, Scenario 5's other +cross-referenced gate) -- declined here (CI auto-decline; a real interactive user sees the full +timed/untimed prompt sequence documented in Scenario 5): ``` -[DEBUG] Using super-user override for PVW_TARGET_PY: python=3.12 -[INFO] Python version: using super-user override PVW_TARGET_PY. +[INFO] REQ-009: cascading provider venv to system; re-attempting dependencies. +[WARN] Attempting system Python fallback (degraded)... + +*** WARNING: System Python Execution *** +*** Using global system Python may pollute shared packages. *** + +Proceed with System Python? (Global pollution risk) [y/n]: y to accept, n to decline. +[INFO] REQ-014: System Python consent: user declined. +[INFO] REQ-014: System Python fallback aborted: consent not granted. +[WARN] REQ-009: cascade target system Python unavailable; keeping current build. ``` -Invalid-value trace (`[Extrapolated Branch]`): no format validation; a garbage value becomes an -invalid conda package spec or an invalid `uv venv --python` request, surfacing as a real, -correctly-handled provider failure absorbed by the same fallback/cascade machinery as 14b -- -reaching a graceful `:die` (`state=error`) only if every fallback tier is also exhausted, never an -uncontrolled crash. +With every tier now exhausted, the bootstrapper keeps whatever build it had (venv, in this +capture) and prints the honest caveat panel (Scenario 43's REQ-027 messaging) instead of "SETUP +COMPLETE": -**14d. `PVW_WORKSPACE`** overrides `HP_UV_ENV_PATH` (the uv venv's path) with a clean, immediate -override -- the default is assigned and instantly replaced before any use, unlike 14a/14c's -"let it run, override the result" pattern. **Scope limitation worth flagging explicitly: this -variable only takes effect in uv mode.** Conda's own environment path has no corresponding check -at all -- a conda-mode run ignores `PVW_WORKSPACE` entirely. +``` +============================================================ + SETUP COMPLETE -- WITH A CAVEAT +============================================================ + We packaged your app, but couldn't fully verify it runs as a + standalone program. Your environment and dependencies ARE + installed correctly -- you can always run your app directly: + "" "app.py" + + RUNNING YOUR APP + Double-click dist\_selftest_cascade_exec.exe to run it. +``` -`[Extrapolated Branch]`, genuinely untested, and uniquely among the four, **there is no dedicated -confirmation log line at its actual point of use** -- only the generic top-of-file `[DEBUG]` line, -which fires purely because the variable is defined, before it's even known whether uv mode (where -this variable matters at all) will be selected. Invalid-value trace: no path validation; if a -`Scripts\python.exe` already happens to exist at the given path, a real functional canary -(`import pip`) guards whether it's actually reused; a creation failure at a bad path is absorbed -by the same `:uv_venv_fail` fallback chain as 14b/14c. +**Each tier is tried at most once as a cascade source** (`HP_CASCADE_TRIED_` guards), +`HP_ENV_MODE` only ever advances (`uv -> conda -> embed -> venv -> system`), so the cascade +structurally cannot loop -- it either lands on a working tier or exhausts and stops, exactly as +shown here. --- -### Scenario 15: `PVW_CONDA_EXE` and its interaction with the conda self-heal flow +### Scenario 16: `--hidden-import` auto-recovery (success and exhaustion) -**What's tested:** `self.corrupt.conda.override_exit` (`tests/selftest.ps1`), self-contained by -construction (no ordering dependency on Miniconda already being installed elsewhere in the job, -unlike its sibling corrupt-conda scenarios). +**What's tested:** `self.exe.hidden_import` (success, `tests/selfapps_hidden_import.ps1`) and +`self.exe.hidden_import.exhaust` (`tests/selfapps_hidden_import_exhaust.ps1`), both real/conda-full +lanes, real, passing. -**Source:** REAL CI CAPTURE, run `30328748330`, both gating lanes (`real` job `90179708091` and -`conda-full` job `90179708094`), both `pass: true`, `exitCode: 2`. +**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane). -`PVW_CONDA_EXE` overrides the resolved conda batch-file path unconditionally, the instant it's -defined -- and because this happens BEFORE the "install Miniconda if missing" block, setting it -also skips the Miniconda download/install entirely: +When a frozen EXE fails at runtime with `ModuleNotFoundError` for a module that IS installed in +the build interpreter, the bootstrapper rebuilds with `--hidden-import=` added, bounded to +3 attempts. This is strict by design -- an uninstalled module (a real missing dependency) or a +plain `ImportError` (not a missing module at all) never triggers a rebuild, since the fix is not +mechanically derivable. + +**Exhaustion** (three DIFFERENT modules missing across three rebuilds, still never fully +resolving -- REAL CI CAPTURE): ``` -[DEBUG] Using super-user override for PVW_CONDA_EXE: +[WARN][HIDDEN_IMPORT] Auto-recovery exhausted after 3 attempts; module(s) still missing. ``` -**The special interaction, and the whole reason this variable gets its own scenario:** normally, -when a health check on the resolved conda binary fails, the bootstrapper offers an interactive -Y/N self-heal prompt that (on accept) deletes and rebuilds the entire Miniconda root. When -`PVW_CONDA_EXE` is set, this self-heal path is skipped outright -- the very FIRST check in the -corruption-handling subroutine, ahead of every other check including the CI auto-decline logic -- -because the bootstrapper will never auto-delete a path it doesn't own: +`~bootstrap.status.json` still reads `state: ok` in this case -- the user's own program repeatedly +failing to import something is not a bootstrapper failure (the environment and build lifecycle +both succeeded); see CLAUDE.md's "User-code exit-code semantics" Known Finding. -``` -================================================================ - CORRUPTED PYTHON ENVIRONMENT DETECTED -================================================================ +--- - The local conda installation appears to be broken. - This can happen after a Windows update or OS migration - (example: DLL load error 0xc000007b). +### Scenario 17: Warnfix repair loop (success, and the failure that feeds the cascade) - Affected path: +**What's tested:** `self.exe.warnfix.install`/`.pass` (success) and `.xfail` (a module install +genuinely fails), `tests/selfapps_warnfix.ps1`, real/conda-full lanes, all real, all passing. - This binary was specified via PVW_CONDA_EXE: - +**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane). - Automatic self-healing is not available for user-managed conda. - Please fix or replace the binary at the path above, then re-run. +Unlike Scenario 16 (runs AFTER a launch fails), warnfix runs BEFORE the EXE is ever launched -- +PyInstaller's own warn file lists modules it couldn't bundle statically, and the bootstrapper +tries to install and rebuild: + +**Success** (real capture, a clean repair with no failures): + +``` +[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. +[REPAIR] missing modules detected; installing and rebuilding. +[REPAIR] rebuild complete after warnfix. ``` -followed by the exact error/exit sequence: +**A module that genuinely can't be installed** (real capture -- `StringIO`, a Python-2-only +module some Python-3 code still conditionally imports, which no Python-3 environment can satisfy): ``` -[ERROR] Corrupt user-managed conda (PVW_CONDA_EXE); fix manually. +[REPAIR] missing modules detected; installing and rebuilding. +[WARN] Repair failed: StringIO +[WARN] One or more repair attempts failed +[REPAIR] rebuild complete after warnfix. ``` -and the process exits with code **2** -- notably, there is no Y/N prompt at all in this path, even -for a genuinely interactive real user; the override check runs before the interactivity dispatch -even has a chance to matter. +The rebuild still happens (bundling whatever WAS successfully installed), but this specific +unresolved-after-rebuild situation is exactly what feeds `HP_CASCADE_CANDIDATE` -- setting up the +provider-cascade offer documented fully in Scenario 15. --- -### Scenario 16: Drag-and-drop / CLI entry-file override (REQ-011 same-directory rule + REQ-002 priority) +### Scenario 18: Pre-flight guards actually firing -**What's tested:** three real, currently-passing NDJSON rows across two test files -- -`self.entry.req011.crossdir` and `self.entry.req011.sameDir` (`tests/selfapps_isolation.ps1`), and -`self.entry.override` (`tests/selfapps_ux_hardening.ps1`, which specifically proves the override -wins over auto-detection, not merely that dragging works at all). +**What's tested:** `self.warn.onedrive`, `self.warn.sysdir`, `self.stub.low_disk_warn` +(`tests/selftest.ps1`, real lane, all real and passing). Contrast with Scenario 2, which showed +these same four guards' CLEAN (silent) pass. -**Source:** REAL CI CAPTURE, run `30328748330`, both gating lanes (`real` job `90179708091` and -`conda-full` job `90179708094`), all three rows `pass: true` in both. +**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane). -A user can either type a `.py` filename as the first CLI argument to `run_setup.bat`, or literally -drag a `.py` file onto the `.bat` file's icon in Windows Explorer (Windows translates the drop -into the identical `%1` argument). **REQ-011's rule: the file must be in the SAME directory as -`run_setup.bat` itself**, checked twice for defense-in-depth (once as an early pre-flight check, -for instant feedback before any environment work begins, and once again inside the entry-selection -subroutine itself). A cross-directory attempt genuinely terminates the whole process (`exit /b 1`, -not merely a `call`-frame return) with: +**OneDrive** (real capture): ``` -[ERROR] REQ-011: Dragged files must reside in the bootstrapper root folder for environment cleanliness. +*** WARNING: Script appears to be in a OneDrive folder. File locking may cause failures. +[WARN] OneDrive path detected; file locking may cause failures. ``` -(This is the raw, untimestamped console line; the separately-written log-file copy is a shorter -variant without the "for environment cleanliness" clause -- a real, source-confirmed difference, -not a typo, confirmed via real CI capture: `[ERROR] REQ-011: Dragged files must reside in the -bootstrapper root folder.`) - -A same-directory file succeeds and prints the filename (a historical bug that once printed this -line with an EMPTY filename -- see `docs/agent-lessons-learned.md`'s "Provider-cascade dispatch is -goto-based on purpose" entry -- is long fixed; the current, correct text is shown below): +**Disk space, REQ-025** (real capture -- warn-only, never a hard block, per REQ-001's rule that a +flag-detectable condition must never gate the Prime Directive). The block emits three raw `echo` +lines before the `[WARN]` line the underlying test asserts on; all four are console-visible in the +same run, so a real user sees the full four-line block below, not just the final `[WARN]`: ``` -*** Using drag-and-drop file: +*** WARNING: Only ~0 GB free disk space detected on this drive. +*** Downloading Python/Miniconda and building your app can need several GB. +*** If setup fails partway through, freeing up disk space is a likely fix. +[WARN] REQ-025: low disk space detected (~0 GB free); continuing (warn-only). ``` -**Interaction with the REQ-002 interactive entry picker: fully and structurally skipped.** -Providing a valid same-directory file makes entry selection return immediately, before the -auto-detection block (and therefore the picker, which is only ever invoked from inside that same -block) is even reached -- this is REQ-002's documented "priority 0": a co-located override always -wins over auto-detected names, and can never trigger the ambiguous-case timed picker. Confirmed -positively (not just "dragging works," but that override genuinely beats auto-detection) by -`self.entry.override`'s real capture: a scratch directory staged with BOTH `main.py` (which would -win plain auto-detection by name-priority) and `zzz_override.py`, with `zzz_override.py` passed as -the override -- the real run confirms the drag message names the override file, the entry-selected -log line names `zzz_override.py` (not `main.py`), and the override file's own distinguishing output -is what actually ran. - ---- - -## Part VI: Adversarial and recovery branches - -**Scope note:** excludes `HP_TEST_*` CI-only flags as the documented subject -- they're the -mechanism a test uses to force a scenario deterministically, but the resulting console text below -is exactly what a real user hits when the same underlying condition occurs for real (a genuine -flaky connection, a genuinely corrupted conda install, a genuinely missing bundled module, and so -on). Evidence for this entire Part is pulled from run `30328748330` (commit `5872028`, all lanes -green): `real` (job `90179708091`), `conda-full` (job `90179708094`), `uv` (job `90179708109`), -`justme-test` (job `90179708103`). - -### Scenario 17: Network connectivity check and transient-retry (REQ-013 + REQ-022) +**System directory guard** (the one guard that's a hard ABORT, not a warning -- exit code 1, +confirmed via a real, passing NDJSON row, though the specific console dump wasn't captured +verbatim in this run's log excerpt, so the exact banner text below is `[Extrapolated Branch]`, +cited directly from source): -**What's tested:** `self.ux.connectivity.*` rows (`tests/selfapps_ux_hardening.ps1`, real lane); -`self.stub.conda_retry`/`self.stub.conda_create_retry`/`self.stub.conda_perpkg` -(`tests/selftest.ps1`, conda-full lane). All real, all passing. +``` +*** ERROR: This script is located inside a Windows system folder. +*** Placing it here does not "install" it. Windows restricts writes to this location +*** without administrator rights, and this bootstrapper needs to create files right +*** next to itself to work. +*** Please move this script (and your .py files) to a normal folder -- your Desktop +*** or Documents folder both work well -- then run it again from there. +``` -When a download genuinely fails, `:check_net_after_dl_fail` (REQ-013) pings `8.8.8.8` (2 attempts) -then, if ICMP is blocked, tries an HTTPS reachability check against `conda.anaconda.org` (2 -attempts) before concluding the user is actually offline -- this doubled-attempt design exists -because a single dropped ICMP echo or a momentarily-contended connect on a busy machine is enough -to misclassify a genuinely-online host as offline (this was root-caused from a REAL CI flake, not -a hypothetical). If both checks fail, a real user sees an unbounded prompt: +**Path-length guard**: this one is genuinely hard to exercise in CI, for a structural reason worth +being upfront about -- GitHub-hosted Windows runners don't have `LongPathsEnabled` by default, so +PowerShell's own `Push-Location` can fail to navigate into a scratch directory built long enough to +trigger `run_setup.bat`'s own 200-char check before the sub-bootstrap ever gets a chance to run at +all. The test's own real NDJSON row makes this concrete: `pathLen: 312` (the directory really was +built long enough), `ranBootstrap: false` (the runner itself couldn't get there), `skip: true, +reason: 'runner-cannot-navigate-long-path'` -- an honest inconclusive result, not a false pass. The +guard's own text is confirmed from source only for this doc (`[Extrapolated Branch]`): ``` -[WARN] REQ-013: Connectivity check: no internet detected (ICMP and HTTPS check failed). -WARNING: No internet connection detected. Remote providers may fail. Retry? (Fix connection then press Y) or proceed offline (N): +*** WARNING: Script path is 312 chars. Paths near 260 chars may cause cmd.exe failures. ``` -Pressing Enter with no answer defaults to offline; `Y` re-tries (2 more ping attempts, then 2 more -HTTPS attempts) before re-prompting on continued failure; `N` proceeds offline (`HP_OFFLINE_MODE=1` -for the rest of the run). +--- -**Transient-retry for `conda create` and conda's bulk package install (REQ-022)** -- both use the -identical detect-and-retry-once pattern: scan the failure output for `CondaHTTPError`/`Failed to -fetch`/`timed out`/`ConnectionError`, wait 15 seconds, retry exactly once. If the retry ALSO fails, -it falls straight through to the normal (non-transient) failure/fallback chain -- this is not an -infinite-retry loop. +### Scenario 19: Concurrent-instance lock contention (REQ-024) -`conda create` retry, REAL CI CAPTURE (`tests/selftest.ps1`'s conda-full sub-bootstrap): +**What's tested:** `self.stub.lock_held_decline`/`self.stub.lock_stale_evict` +(`tests/selftest.ps1`, real lane, both real and passing). + +**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane). + +A user double-clicking the bootstrapper twice in quick succession (or genuinely running two +instances) hits an NTFS-atomic `mkdir`-based lock. If a second instance finds the lock genuinely +held by a live first instance: ``` -Creating Python environment '_selftest_conda_create_retry' -- this may take several minutes... -Conda environment creation failed -- possible network or repository issue. Retrying once... -[INFO] conda create: transient failure detected; retrying after 15s. -Retrying environment creation... -[INFO] runtime.txt written: python-3.14.6 -[BOOT] REQ-009: Selected Python provider: Conda (Portable). +*** +*** Another instance of this setup appears to be running in this folder already. +*** If you are sure that is NOT the case (for example, a previous run crashed), +*** delete the "~bootstrap.lock" folder next to this script and run it again. +*** +pid= +started= +[WARN] REQ-024: setup already running (lock held, not stale); this instance is exiting. ``` -Bulk package install retry, REAL CI CAPTURE (same test file, a different scratch env): +exit code 1 -- the losing instance never touches the lock it doesn't own. If the lock directory +is instead STALE (left over from a crashed/killed prior run, older than the ~2 hour staleness +threshold), it's evicted automatically and the run proceeds normally with no user action needed: ``` -Conda install failed -- possible network or repository issue. Retrying once... -[INSTALL] conda bulk: transient failure detected; retrying after 15s. -Retrying package installation... +[INFO] REQ-024: stale lock evicted (older than the staleness threshold); proceeding. ``` -A NON-transient bulk failure (any error text that doesn't match the transient patterns above) -skips the retry entirely and instead falls back to installing packages one at a time -(`self.stub.conda_perpkg`, REAL CI CAPTURE, `[INSTALL] conda per-pkg fallback`) -- a completely -separate recovery path from the transient-retry one, chosen based on what kind of failure -actually occurred. +Staleness is deliberately age-based, not PID-liveness-based -- a dead process's PID can be +recycled by an unrelated program, so trusting PID liveness for automated eviction would be unsafe. --- -### Scenario 18: Corrupted-conda self-heal (detect / decline / accept) - -**What's tested:** `self.corrupt.conda.detect`/`.heal.decline`/`.heal.accept` -(`tests/selftest.ps1`, conda-full lane, all three real and passing). The `PVW_CONDA_EXE`-override -variant is already covered in Scenario 15 -- this scenario is the DEFAULT case, where the -bootstrapper owns the conda install and can offer to fix it. - -**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708094` (`conda-full` lane). +## Part V: Remaining branches (dependency source precedence, write-back, and misc) -When a health check on the resolved conda binary fails, the user sees: +**Scope note:** this part collects everything from the plan's 5-pass checklist that didn't fit +naturally into Parts I-IV: dependency and Python-version source precedence, the two write-back +mechanisms (`runtime.txt`, PEP 723 headers), the optional execute-mode discovery flag, NI-VISA/ +pandas per-package special-casing, the periodic conda maintenance timer, and the one REQ-014 +branch (consent ACCEPT) that Part IV's Scenario 15 didn't cover (it only showed decline). -``` -================================================================ - CORRUPTED PYTHON ENVIRONMENT DETECTED -================================================================ +--- - The local conda installation appears to be broken. - This can happen after a Windows update or OS migration - (example: DLL load error 0xc000007b). +### Scenario 20: Git config merge (`.gitignore`/`.gitattributes`, REQ-015) - Affected path: C:\Users\Public\Documents\Miniconda3 -``` +**What's tested:** `self.ux.gitignore.merge`/`.preserve`/`.idem`, `self.ux.gitattributes.merge` +(`tests/selfapps_ux_hardening.ps1`, `real` lane, all real and passing). -followed by an unbounded `[Y/N]` prompt (` Would you like to delete it and rebuild? [Y/N] `, -`[Extrapolated Branch]` for the exact prompt line, same reasoning as Scenario 11's two prompts -- -it lives inside `set /p`, never visible in a CI log even when a test forces the accept branch via -an answer override). +**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane). -**Decline** (real capture): +On a fresh checkout with no `.gitignore`/`.gitattributes` at all, `:merge_git_config` (`run_setup.bat`) +idempotently appends a standard block to each, guarded by an `findstr` signature check +(`# Automated Python Bootstrapper Standard Ignores` / `...Attributes`) so a second run is a clean +no-op rather than a duplicate append: ``` - Exiting without changes. Delete the folder above manually, - then run this setup again. +[INFO] REQ-015: Appending standard ignores to .gitignore. +[INFO] REQ-015: Appending standard attributes to .gitattributes. ``` -logged as `[ERROR] Corrupt conda env; user declined rebuild.`, exit code 2. - -**Accept** (real capture; this specific test run additionally forces the actual re-download step -to be skipped for CI-safety reasons, so the "Downloading fresh copy..." line below is -`[Extrapolated Branch]`, cited from source, while everything else is real): +The appended `.gitignore` block (verbatim from source): ``` - [INFO] Removing corrupt Miniconda installation... - [INFO] Corrupt installation removed. Downloading fresh copy... +# Automated Python Bootstrapper Standard Ignores +.*_env/ +.venv/ +.uv/ +.cache/ +.conda/ +dist/ +build/ +*~ +~* ``` +and `.gitattributes`: + ``` -[INFO] Self-healing: corrupt conda evicted from C:\Users\Public\Documents\Miniconda3. -[INFO] Workspace: ...\tests\~selftest_heal_accept -[INFO] Env name: _selftest_heal_accept -[INFO] HP_ENV_MODE=conda -[INFO] Creating Python environment '_selftest_heal_accept' -- this may take several minutes... -[INFO] runtime.txt written: python-3.14.6 -[BOOT] REQ-009: Selected Python provider: Conda (Portable). +# Automated Python Bootstrapper Attributes +*.bat eol=crlf +*.cmd eol=crlf +*.exe binary ``` -A real user's accept path re-downloads and reinstalls Miniconda from scratch (the full -acquisition sequence from Scenario 9), then proceeds exactly as a fresh first run would. +Real evidence confirms all three properties the tests assert: the signature is appended +(`self.ux.gitignore.merge`), any PRE-EXISTING content in the file (e.g. a user's own `node_modules/` +line) survives the merge untouched (`self.ux.gitignore.preserve`, `nodeModulesFound:true`), and +running the bootstrapper a second time does not duplicate the signature (`self.ux.gitignore.idem`, +`sigCount:1`). This runs unconditionally on every bootstrap invocation, independent of provider or +entry-file state -- it is one of the first things `run_setup.bat` does after the pre-flight guards. --- -### Scenario 19: Miniconda install chain (AllUsers -> JustMe -> both-failed) +### Scenario 21: Python-version precedence (REQ-004) and dependency-source precedence (`pyproject.toml`) -**What's tested:** `conda.install.justme` (`tests/selftest.ps1`, `justme-test` lane, real, -passing) and `self.conda.bothfail` (`tests/selfapps_conda_bothfail.ps1`, `uv` lane, real, -passing). +**What's tested:** `pyproject.precedence.detect`/`.writeback` (`tests/selfapps_pyproject_precedence.ps1`, +both real and passing) and `pyproject.dep.detect`/`.noproj` (same file). `pyproject.precedence.detect` +ran in the `conda-full` lane in this capture (the `real` lane's own copy of the row emits +`skip=true, reason=conda-not-installed-uv-first` since it happens to call `Get-CondaBatPath`, per +`docs/agent-interconnect.md`'s skip-pattern convention for this test file); `pyproject.dep.*` and +`.writeback` ran in `real`. -**Source:** REAL CI CAPTURE, run `30328748330`, jobs `90179708103` (`justme-test`) and -`90179708109` (`uv`). +**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708094` (`conda-full`) for the version +tiers, job `90179708091` (`real`) for the dependency-source precedence. -Miniconda install first attempts an AllUsers (machine-wide) install; if UAC rejects elevation (or -the process simply isn't elevated), it skips straight to a JustMe (per-user) install instead, no -wasted attempt. **Both the "skip, never attempted" path and a genuine post-attempt AllUsers -failure fall through to the same shared `:tci_justme` label** (`run_setup.bat`), but (fixed -2026-07-31, Closed Active Backlog item 16, renumbered from 11 -- see `docs/agent-closed-backlog.md`) the label -now checks a flag set only right before the real AllUsers install attempt, so the two paths get -distinct wording instead of both unconditionally claiming AllUsers "failed." On the common -non-elevated machine (skip path, `[Extrapolated Branch]` for the new wording -- not yet -re-confirmed against a fresh CI capture): +These are two genuinely SEPARATE precedence systems that happen to both read `pyproject.toml` and +are easy to conflate -- worth documenting distinctly. -``` -[INFO] Not elevated; skipping AllUsers Miniconda install. -[INFO] Miniconda AllUsers install skipped (not elevated); trying JustMe install instead. -[INFO] Miniconda installed (JustMe fallback). -``` +**REQ-004 (Python VERSION precedence, three tiers)**: Tier 1 `runtime.txt` beats Tier 2 +`pyproject.toml`'s `[project].requires-python` beats Tier 3 "let the selected provider pick latest, +then write `runtime.txt` back." These two NDJSON rows deliberately test two DIFFERENT things in two +DIFFERENT scratch directories with two DIFFERENT constraints, not one continuous flow -- worth +being precise about, since the test file's own comments explain why: `.detect` calls +`~detect_python.py` directly (no bootstrap, no environment ever created) against a tight +`requires-python = ">=3.10,<3.11"` to check Tier 2's parse/forward precision in isolation; `.writeback` +runs the FULL bootstrapper against a deliberately loose `requires-python = ">=3.9"` in a separate +directory ("so conda picks a cached Python version and avoids a slow resolver round-trip for +Python 3.10 packages," per the test's own comment) to check Tier 3's write-back end to end. -A genuine, post-attempt AllUsers failure still gets the original WARN wording, now also carrying -the installer's own exit code and a reason token (`[Extrapolated Branch]`, cited from source -- -this branch requires a real elevated process whose AllUsers installer genuinely fails, which no -current CI hook forces without also forcing the skip path): +`.detect`'s real NDJSON output confirms Tier 2's parse/forward is exact: `output":"python>=3.10,<3.11"`. +`.writeback`'s real capture shows Tier 3 firing (since `runtime.txt` didn't pre-exist there either): ``` -[WARN] Miniconda AllUsers install failed (exitCode=1, reason=installer_failed); retrying with JustMe. +[INFO] runtime.txt written: python-3.14.6 ``` -If the installer instead hits `:run_installer_timeout`'s own 60-minute ceiling (see that -subroutine's header comment), the exit code is a hardcoded sentinel, not the installer's real -exit code -- reported as `reason=timeout` with no fabricated `exitCode` field instead -(`[Extrapolated Branch]`, an even rarer sub-case of the one above, never observed in CI): +(real capture; `pyproject.precedence.writeback`'s own NDJSON row confirms `runtimeVersion: +python-3.14.6` and `versionSatisfied:true` against ITS OWN, looser `>=3.9` constraint -- 3.14.6 +satisfies `>=3.9` comfortably. It does NOT satisfy the OTHER test's `<3.10,<3.11` constraint, but +that constraint was never used for this environment; the two tests are independent, and this +document originally conflated them into one implied sequence before being corrected.) Malformed +`pyproject.toml` TOML degrades gracefully rather than aborting the whole precedence chain (real +capture, `self.pyproject.malformed`): ``` -[WARN] Miniconda AllUsers install failed (reason=timeout); retrying with JustMe. +*** [WARN] pyproject.toml could not be parsed as valid TOML; falling back to requirements.txt or pipreqs. ``` -**If JustMe ALSO fails** (both installation options exhausted; REAL CI CAPTURE for the skip-path -lines, `[Extrapolated Branch]` for the now-corrected wording): +logged compactly too: `[WARN] pyproject.toml TOML parse error; falling back.` -- the bootstrap then +proceeds via Tier 3 (provider picks latest) exactly as if `pyproject.toml` had never existed. + +**Dependency-SOURCE precedence (a different mechanism, REQ-004/REQ-005.1 rows, unrelated to Python +version)**: when `pyproject.toml` declares a real `[project].dependencies` array, it takes priority +over any `requirements.txt` present -- this is decided independently of the version-tier logic +above and can fire even when `runtime.txt` already exists. Real capture: ``` -[INFO] Not elevated; skipping AllUsers Miniconda install. -[INFO] Miniconda AllUsers install skipped (not elevated); trying JustMe install instead. -[ERROR] Miniconda install failed (AllUsers skipped -- not elevated; JustMe also failed). +*** [INFO] pyproject.toml [project].dependencies found; overrides requirements.txt +[INFO] pyproject.toml [project].dependencies detected +[INFO] DEP_SOURCE=pyproject ``` -This is a genuine `:die` (`state=error`), not a graceful degrade -- with no Python interpreter -acquirable at all through conda, there's nothing left for this tier to hand back. Both installer -launches are bounded by a generous 60-minute timeout (not unbounded), based on real-world reports -of Miniconda's silent installer hanging indefinitely on some machines -- documented in -CLAUDE.md's Active Backlog history. +`~pyproj_deps.py` (`HP_PYPROJ_DEPS`) is the helper that extracts the array; real NDJSON detail from +`pyproject.dep.detect` shows it parsing a real two-line array (`"output":"requests>=2.28\r\ncolorama"`, +`exitCode:0`). When `pyproject.toml` has no `[project]` section at all, the helper exits 1 with no +output rather than a false match (`pyproject.dep.noproj`, `exitCode:1, outExists:false`) -- the +bootstrapper then falls through to `requirements.txt`/pipreqs as usual. --- -### Scenario 20: Standalone embed-tier download (REQ-009 Tier 5) -- decline and real success +### Scenario 22: PEP 723 dependency write-back (REQ-005.11) -- the fresh-install trigger -**What's tested:** `self.embed.fallback.decline`/`self.embed.fallback.real` -(`tests/selfapps_ux_hardening.ps1`, `uv` lane, both real and passing). +**What's tested:** `self.pep723.writeback.fresh`/`.skipflag` (`tests/selfapps_pep723_writeback.ps1`, +`real` lane, both real and passing). -**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708109` (`uv` lane). +**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane). -When uv and conda have both failed (or been declined/exhausted), the bootstrapper tries -downloading a private, checksummed Python interpreter directly from python.org -- no consent -prompt (unlike system Python), since this is a bootstrapper-controlled, disposable extraction: +After a genuinely fresh, fully-successful `HP_ENV_MODE=uv` dependency install (see Part I, +Scenario 3), `:pep723_writeback` promotes the resolved dependency set into the entry file's own +PEP 723 header via `uv add --script`, so the pin travels with the user's source file rather than +staying only in `requirements.txt`/the lock file: ``` -[WARN] Attempting embedded Python download (REQ-009 Tier 5)... +[INFO] REQ-005.11: PEP 723 header write-back succeeded via uv add --script. ``` -**Real success** (a genuine end-to-end download/verify/extract/patch/pip-bootstrap/canary/build/ -run, REAL CI CAPTURE): +When there is nothing to write (a stdlib-only app, no third-party packages resolved), the +subroutine correctly no-ops rather than writing an empty/misleading header -- also a REAL capture: ``` -[INFO] Downloading embedded Python 3.14.6 from https://www.python.org/ftp/python/3.14.6/python-3.14.6-embed-amd64.zip... -[INFO] embed fallback: 3.14.6 extracted and verified. +[INFO] REQ-005.11: PEP 723 write-back skipped (no packages to write). ``` -**Tier exhaustion** (this specific tier also fails, e.g. offline -- REAL CI CAPTURE): +This is `HP_ENV_MODE=uv`-only (v1 scope, see `docs/agent-interconnect.md`) and best-effort/non-gating +-- any failure (malformed existing header not cleanly repairable, a file lock, non-UTF-8 source) +logs a `[WARN]` and the bootstrap continues unaffected; `HP_SKIP_PEP723_WRITEBACK=1` suppresses it +outright per REQ-019 (a genuine opt-OUT flag, not a gate). The warnfix-triggered SECOND write-back +call (after a successful repair round) is functionally identical and not separately captured here +-- same subroutine, same two possible outcomes, triggered from a different call site. + +--- + +### Scenario 23: `HP_PVW_KNOWN_IDEMPOTENT` execute-mode discovery (REQ-005.13) + +**What's tested:** `self.pvw_idempotent.discovery` (`tests/selfapps_pvw_idempotent.ps1`, `uv` lane, +real and passing). + +**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708109` (`uv` lane). + +This is an opt-in super-user flag (not part of the default happy path -- Part I never sets it): +when defined, `run_setup.bat` skips straight to actually RUNNING the entry file live via +`uvx autopep723 ` for dependency discovery, before pipreqs or any static analysis even +starts. Real capture: ``` -[WARN] embed fallback: offline mode; cannot download embedded Python. +[INFO] REQ-005.13: HP_PVW_KNOWN_IDEMPOTENT set; running entry via uvx autopep723 for execute-mode discovery. +[INFO] REQ-005.13: execute-mode discovery run succeeded (RAN:persisted). ``` -A genuine download failure (not offline, an actual failed transfer) retries the WHOLE -download+verify cycle once before giving up (`[WARN] embed fallback: download failed; retrying -once.`, `[Extrapolated Branch]`, cited from `:embed_dl_retry` -- not independently observed in -this run's real capture, since neither scenario above hits a genuine mid-download failure). A -checksum mismatch is treated the same way (redownload, not just re-verify, since a mismatch can -mean a truncated download rather than a bad pin). +The entry script's own stdout is inherited/passed through live during this discovery run (not +captured or suppressed) -- real NDJSON detail confirms `stdoutPassthroughFound:true, appRan:true` +-- and whatever dependency it needed (`requests`, in this real capture: `reqsHasRequests:true`) is +persisted back into the PEP 723 header via `uv add --script`, then re-extracted into +`requirements.txt` so the rest of the pipeline (pipreqs, Tier 1 autopep723 merge, the actual +install) sees it too. Deliberately ADDITIVE, not a replacement for pipreqs -- pipreqs and Tier 1's +own `autopep723 check` merge still run normally afterward to catch anything a single execution +path didn't happen to exercise. --- -### Scenario 21: REQ-009 provider cascade -- one real run showing the FULL chain +### Scenario 24: NI-VISA detection and install outcome (REQ-008) -**What's tested:** `self.cascade.exec` (`tests/selfapps_cascade.ps1`, `uv` lane, real, passing). -This single test happens to exercise BOTH mid-exhaustion (uv/conda/embed all cascade past) AND -full exhaustion (system Python, the final tier, declines) in one coherent real capture -- an -unusually complete real-world illustration of this mechanism. +**What's tested:** `pyvisa.detect`/`.nivisa.branch`/`.nivisa.outcome`/`.nivisa.reason`/`.nivisa.disabled` +(`tests/selfapps_pyvisa.ps1`, `real`/`conda-full` lanes, all real and passing). -**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708109` (`uv` lane). +**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real`) and job `90179708094` +(`conda-full`) -- both lanes captured a genuine NI-VISA install ATTEMPT in this run (not just the +"not required" skip), which is a more complete illustration than what an earlier pass in this +document assumed was CI's only available evidence. -After a build succeeds but a dependency repair genuinely fails (Scenario 23 covers what triggers -this), the user is offered (Scenario 11's timed `:cascade_consent_gate`, up to 30s, default -decline) a chance to try the next Python provider. On acceptance, the REAL capture below shows the -cascade working through every tier in order -- each hop re-attempts the full dependency phase -under the new provider, and each hop that ALSO can't fully resolve dependencies triggers another -verification failure and another cascade offer: +When `pyvisa`/`visa` is detected as an import, the bootstrapper attempts a real NI-VISA driver +install (downloads the online bootstrapper installer via curl, PE-validates it, then launches it +under a bounded timeout): ``` -[INFO] REQ-009: cascading provider uv to conda; re-attempting dependencies. -*** [INFO] Trying the next Python provider (conda) to resolve dependencies... +[INFO] Detected pyvisa/visa import; NI-VISA install may be required. +[VISA] download method: curl +[VISA] installer file size: 6769400 bytes +[VISA] installer PE check: PE_OK +[INFO] Launching NI-VISA installer (timeout ceiling: 5400000 ms). +[VISA] installer exit code: -125202 +[VISA] post-check waiting; retry 1/3 (installer_rc=-125202) +[VISA] post-check waiting; retry 2/3 (installer_rc=-125202) +[VISA] install_failed (post_check_timeout) installer_rc=-125202 ``` -``` -[INFO] REQ-009: cascading provider conda to embed; re-attempting dependencies. -*** [INFO] Trying the next Python provider (embed) to resolve dependencies... -``` +This matches CLAUDE.md's already-documented Known Finding ("NI-VISA real install fails fast in CI") +in shape and mechanism exactly -- a genuine, PE-valid installer download that exits fast and +unattended-incompatible on a CI runner -- though the SPECIFIC installer exit code observed here +(`-125202`) differs from that finding's originally-cited `-125083`. Consistent with the finding's +own framing (an online bootstrapper installer failing an unattended install, not a fixed/stable +error code), not a new discrepancy worth a separate backlog entry. The bootstrap proceeds +gracefully regardless -- a failed NI-VISA install is never treated as a bootstrap failure, only +logged and surfaced; the user's own program still builds and runs. -``` -[INFO] REQ-009: cascading provider embed to venv; re-attempting dependencies. -*** [INFO] Trying the next Python provider (venv) to resolve dependencies... -``` +Real evidence confirms the OTHER outcome branch too, via a dedicated `HP_SKIP_NIVISA=1` scenario in +the same test file: `[VISA] skipped (not_required)`, with `skippedDisabled:true, +noInstallAttempt:true` -- the flag suppresses the install attempt outright even when pyvisa IS +detected, per REQ-019's suppression-only convention. -**Note the log wording says "uv to conda", never "uv -> conda"** -- deliberate: `:log` echoes -unquoted, so a literal `>` would be parsed as shell redirection and silently eat the line. +--- -Right before each cascade offer, the elective postexec-checkpoint/optimized-build prompts from -Scenario 11 are SKIPPED (not just auto-declined) once cascade is approved -- confirmed in this -same real capture: +### Scenario 25: pandas/openpyxl heuristic dependency augmentation (REQ-005.8) + +**What's tested:** `pandas_excel.translate`/`.conda.install`/`.conda.install.req006`/`.runtime`, +`self.pandas.openpyxl.install`/`.import` (`tests/selfapps_pandas_excel.ps1`, `conda-full` lane, all +real and passing) -- plus a genuinely SEPARATE test that happens to exercise the same heuristic in +a different scratch directory, `self.exe.warnfix.real` (`tests/selftest.ps1`'s `real` scenario, +`conda-full` lane, also real and passing, `desc: "Heuristic pre-installed openpyxl via pandas +heuristic; EXE succeeded"`). + +**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708094` (`conda-full` lane). + +`~prep_requirements.py` (`HP_PREP_REQUIREMENTS`) applies a small set of heuristic rules that inject +a commonly-needed-but-undeclared package when its "parent" package is present -- pandas's +`pd.read_excel()`/`to_excel()` need `openpyxl`/`xlsxwriter`, but pipreqs' static analysis has no +way to see that a lazily-imported optional engine is actually required at runtime. Real capture +from `tests/~pandas_excel/`'s own scratch directory: ``` -[INFO] Entry smoke exit=1 -[STATUS] Run Status: FAILED (Exit Code: 1) -[INFO] REQ-009: cascade approved; skipping the post-verification offers for this build. +[HEURISTIC] pandas->xlsxwriter ``` -**Full exhaustion**, reached when venv ALSO can't fully resolve dependencies and the cascade -offers the absolute last resort, system Python (REQ-014's consent gate, Scenario 11's other -cross-referenced gate) -- declined here (CI auto-decline; a real interactive user sees the full -timed/untimed prompt sequence documented in Scenario 11): +(the console line is a compact tag; the two package names themselves are appended to the conda +install spec list, confirmed by the real conda solve plan later in the same `~pandas_excel` log: +`openpyxl conda-forge/win-64::openpyxl-3.1.5-py314hccc76fc_3` and `xlsxwriter +conda-forge/noarch::xlsxwriter-3.2.9-pyhd8ed1ab_0`, and both packages installed into +`~pandas_excel`'s own `requirements.txt`/`~reqs_conda.txt`/`~reqs_pip.txt`). The claim that +`openpyxl` ends up genuinely bundled and importable in a frozen EXE is confirmed by the SIBLING +`self.exe.warnfix.real` test's OWN independent scratch directory (`tests/~selftest_warnfix_real/`, +a different app that also exercises the pandas heuristic, per its own NDJSON `desc` text quoted +above) rather than by `~pandas_excel`'s own PyInstaller warn-file -- its EXE's PyInstaller warn +file shows only expected, harmless optional-dependency lines for the bundled `openpyxl`: `missing +module named PIL - imported by openpyxl.drawing.image (optional)`, not a real gap. This is +`HP_ENV_MODE=conda`-lane-only coverage per this test file's own CI wiring (see +`docs/agent-interconnect.md`'s +"selfapps_pandas_excel.ps1" note) -- the SAME heuristic logic also runs for uv/venv/embed/system +providers via `requirements.txt` write-back (CLAUDE.md's own "Deep research pass" Closed Backlog +entry on this exact fix), just not captured here since this test is conda-only by design. -``` -[INFO] REQ-009: cascading provider venv to system; re-attempting dependencies. -[WARN] Attempting system Python fallback (degraded)... +--- -*** WARNING: System Python Execution *** -*** Using global system Python may pollute shared packages. *** +### Scenario 26: Conda base periodic update -Proceed with System Python? (Global pollution risk) [y/n]: y to accept, n to decline. -[INFO] REQ-014: System Python consent: user declined. -[INFO] REQ-014: System Python fallback aborted: consent not granted. -[WARN] REQ-009: cascade target system Python unavailable; keeping current build. -``` +**What's tested:** `self.conda.base.update` (`tests/selfapps_conda_update.ps1`) -- **NOT currently +wired into any CI lane** (per `docs/agent-ndjson.md`'s own explicit note: the `HP_TEST_CONDA_UPDATE` +injection flag was removed because it upgrades conda to a solver version that cascades failures +across the rest of the `conda-full` job). Only the "skipped" branch below has real CI evidence. -With every tier now exhausted, the bootstrapper keeps whatever build it had (venv, in this -capture) and prints the honest caveat panel (Scenario 7's REQ-027 messaging) instead of "SETUP -COMPLETE": +**Source (skipped branch):** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane, +appears identically across every fresh scratch-dir bootstrap in the run). -``` -============================================================ - SETUP COMPLETE -- WITH A CAVEAT -============================================================ - We packaged your app, but couldn't fully verify it runs as a - standalone program. Your environment and dependencies ARE - installed correctly -- you can always run your app directly: - "" "app.py" +`:conda_base_update` runs `conda update -n base` on a timer (30-day threshold, seeded from +`~conda.lastupdate` on first install) whenever `HP_ENV_MODE=conda`. On a genuinely first-ever +install (the common case in a fresh CI scratch dir, and for most real first-time users), it +correctly skips rather than updating a base that was just installed moments ago: - RUNNING YOUR APP - Double-click dist\_selftest_cascade_exec.exe to run it. +``` +[INFO] Conda base update: skipped (first install). ``` -**Each tier is tried at most once as a cascade source** (`HP_CASCADE_TRIED_` guards), -`HP_ENV_MODE` only ever advances (`uv -> conda -> embed -> venv -> system`), so the cascade -structurally cannot loop -- it either lands on a working tier or exhausts and stops, exactly as -shown here. +**`[Extrapolated Branch]`** -- the actual 30-day-elapsed UPDATE-firing branch (`:cbu_run`, +`run_setup.bat`) is not exercised by any current CI run (the only flag that could force it is +deliberately disabled, per the note above). Traced from source: once the timestamp in +`~conda.lastupdate` is more than 30 days old, the subroutine runs `conda update -n base -y` and +would log something in the shape of `[INFO] Conda base update: running (last updated N days +ago)...` followed by conda's own real update-solve output, then rewrites `~conda.lastupdate` to the +current time on completion. This branch realistically only fires for a long-lived, repeatedly-reused +project folder -- not the fresh-checkout scenarios this document otherwise captures -- and is +correctly out of scope for a dedicated CI test: a forced-update test previously broke conda's own +solver in shared CI runners, an accepted, documented tradeoff (`docs/agent-ndjson.md`'s +"conda-full lane rows" section). --- -### Scenario 22: `--hidden-import` auto-recovery (success and exhaustion) +### Scenario 27: REQ-014 system-Python consent -- ACCEPT -**What's tested:** `self.exe.hidden_import` (success, `tests/selfapps_hidden_import.ps1`) and -`self.exe.hidden_import.exhaust` (`tests/selfapps_hidden_import_exhaust.ps1`), both real/conda-full -lanes, real, passing. +**What's tested:** `self.ux.system.gate.accept` (`tests/selfapps_ux_hardening.ps1`, `real` lane, +real and passing). Part IV's Scenario 15 already showed the DECLINE branch of this same gate (as +the terminal step of a full provider-cascade exhaustion); this scenario completes the pair. **Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane). -When a frozen EXE fails at runtime with `ModuleNotFoundError` for a module that IS installed in -the build interpreter, the bootstrapper rebuilds with `--hidden-import=` added, bounded to -3 attempts. This is strict by design -- an uninstalled module (a real missing dependency) or a -plain `ImportError` (not a missing module at all) never triggers a rebuild, since the fix is not -mechanically derivable. - -**Exhaustion** (three DIFFERENT modules missing across three rebuilds, still never fully -resolving -- REAL CI CAPTURE): +When every other REQ-009 provider tier has failed or been declined, the system-Python tier is +still reached by any default, no-flag run -- it is gated solely by the REQ-014 consent prompt +(Scenario 5 in Part I already documents the prompt's own framing text in full), never by an +env-var the user would need to set. On ACCEPT, the bootstrapper proceeds to use whatever Python is +already on the machine, unmanaged and unisolated: ``` -[WARN][HIDDEN_IMPORT] Auto-recovery exhausted after 3 attempts; module(s) still missing. +[INFO] REQ-014: System Python consent: user accepted. +[INFO] System fallback using C:\hostedtoolcache\windows\Python\3.12.10\x64\python.exe +[BOOT] REQ-009: Selected Python provider: System Python (degraded). ``` -`~bootstrap.status.json` still reads `state: ok` in this case -- the user's own program repeatedly -failing to import something is not a bootstrapper failure (the environment and build lifecycle -both succeeded); see CLAUDE.md's "User-code exit-code semantics" Known Finding. +`~bootstrap.status.json` still reports `state` as the degraded-but-successful `degraded_env` value +(real NDJSON detail: `"state":"degraded_env","exitCode":0`) -- accepting this tier is a genuine, +if suboptimal, path to a working run, not a failure. This is the ONLY REQ-009 tier gated by an +explicit human consent prompt rather than an automatic fallback, precisely because it is the one +tier that touches the user's real, shared Python environment instead of a private/disposable one. --- -### Scenario 23: Warnfix repair loop (success, and the failure that feeds the cascade) +## Part VI: Additional branches found in a full-file sweep -**What's tested:** `self.exe.warnfix.install`/`.pass` (success) and `.xfail` (a module install -genuinely fails), `tests/selfapps_warnfix.ps1`, real/conda-full lanes, all real, all passing. +**Scope note:** after the original 5-pass plan completed, a systematic label-by-label sweep of +every one of `run_setup.bat`'s 164 `:label`s (cross-checked against everything already written in +every other Part of this file) turned up four genuine, user-observable gaps -- three straightforward +missing scenarios, and one previously-undocumented, real bug in the bootstrapper's own error messaging, +found via real CI evidence and confirmed against source before being written up. Everything else +checked in the sweep (roughly 150 of the 164 labels) was either already covered, internal +control-flow plumbing with no independently observable behavior of its own (e.g. `:pfb_runapp`, +`:mgc_gi_done`), or a narrow edge case not worth a dedicated scenario (e.g. `:cascade_consent_no_ +choice_exe`, reached only on a Windows image stripped of `choice.exe`). -**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane). +- [Scenario 28: Interactive entry picker -- multiple `.py` files, no clear winner (REQ-002)](#scenario-34-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-35-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-36-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-37-exe-smoke-run-diagnostic-hints-companion-to-scenario-22) -Unlike Scenario 22 (runs AFTER a launch fails), warnfix runs BEFORE the EXE is ever launched -- -PyInstaller's own warn file lists modules it couldn't bundle statically, and the bootstrapper -tries to install and rebuild: +--- -**Success** (real capture, a clean repair with no failures): +### Scenario 28: Interactive entry picker -- multiple `.py` files, no clear winner (REQ-002) + +**What's tested:** `self.entry.picker` (`tests/selfapps_entry_picker.ps1`, `conda-full` lane, real, +passing). + +**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708094` (`conda-full` lane). + +Part I's happy path covers the common single-`.py`-file case; this covers REQ-002's OTHER real +end-user scenario: a folder with several `.py` files where none is named `main.py`/`app.py`/ +`run.py`/`cli.py` and none has a substantive `if __name__ == "__main__":` block (`:determine_entry`'s +own priority ladder, in `tools/find_entry.py`, exhausts every tier and falls back to +`find_entry.py`'s own `AMBIGUOUS_RC` (3) alphabetical pick). Only THEN does `:pick_entry_interactive` +show a real, timed menu: ``` -[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. -[REPAIR] missing modules detected; installing and rebuilding. -[REPAIR] rebuild complete after warnfix. +Multiple Python files detected -- no clear entry point, so please choose one to run: + [1] a_app.py + [2] b_app.py + + Tip: to skip this question next time, do any one of these: + 1. Drag a .py file onto run_setup.bat -- drop it on the batch file icon to run + that file directly. It must be in this same folder. + 2. Rename your main script to one of: main.py, app.py, run.py, or cli.py. + 3. Give exactly one script an if __name__ == "__main__": block. + If you do nothing, the alphabetically-first file is used: a_app.py + +Type a number 1-2, or wait 30s for the default [1]: ``` -**A module that genuinely can't be installed** (real capture -- `StringIO`, a Python-2-only -module some Python-3 code still conditionally imports, which no Python-3 environment can satisfy): +(the real capture's own timeout is shrunk to 2s via `HP_TEST_FORCE_PICKER`, the same CI-determinism +technique already used for the timed cascade prompt in Scenario 5/21 -- the default, real-user +window is 30 seconds, per `HP_PICK_T` in source, shown above as written). A real, non-interactive +CI run also can't feed `choice.exe` an actual keystroke, so the captured log shows one extra, +CI-only artifact line right after the prompt (`ERROR: The file is either empty or does not contain +the valid choices.`) before falling through to the timeout default -- a real interactive user typing +a number, or simply waiting, never sees that line. Either way, the resolution is logged: ``` -[REPAIR] missing modules detected; installing and rebuilding. -[WARN] Repair failed: StringIO -[WARN] One or more repair attempts failed -[REPAIR] rebuild complete after warnfix. +[INFO] REQ-002: Picker entry selected: a_app.py ``` -The rebuild still happens (bundling whatever WAS successfully installed), but this specific -unresolved-after-rebuild situation is exactly what feeds `HP_CASCADE_CANDIDATE` -- setting up the -provider-cascade offer documented fully in Scenario 21. +If MORE than 9 candidate files exist, the picker's own numbered menu is skipped entirely (no menu +can address more than the `123456789` `choice /C` charset) and the alphabetical pick is kept, logged +as `[INFO] REQ-002: candidates exceed picker limit; keeping (alphabetical).`. The Tip +block (the same three-item list shown above -- drag-and-drop, a preferred filename, or a +`__main__` block) still prints right after that log line, since it's exactly the guidance a user +who just hit a >9-file folder needs most to avoid landing here again -- this used to be silently +skipped along with the rest of the menu, fixed in the same pass that wrote this scenario. +`[Extrapolated Branch]`, cited from `:pick_entry_interactive`, not independently captured in CI +(would need a 10th-plus stub `.py` file staged, which no current test does). --- -### Scenario 24: Pre-flight guards actually firing +### Scenario 29: Pre-flight syntax-error rejection (REQ-021), and a real bug it exposed -**What's tested:** `self.warn.onedrive`, `self.warn.sysdir`, `self.stub.low_disk_warn` -(`tests/selftest.ps1`, real lane, all real and passing). Contrast with Scenario 8, which showed -these same four guards' CLEAN (silent) pass. +**What's tested:** `self.preflight.syntax` (`tests/selfapps_preflight.ps1`, `real` lane, real, +passing) for the ordinary case -- its `$pass` gate enforces the REQ-021 message firing, the real +`SyntaxError` detail appearing, `state: error`, and no PyInstaller-build-crash text; "no EXE was +produced" is separately computed and recorded (`noExe` in the row's own `details`, confirmed +`true` in the real capture below) but is NOT itself part of the pass/fail gate. A SEPARATE, +unrelated real capture (`self.embed.fallback.decline`, `tests/selfapps_ux_hardening.ps1`) +accidentally also reaches this code path under total provider exhaustion, which is what exposed +the bug documented below. **Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane). -**OneDrive** (real capture): +**The ordinary case**: before ever attempting a doomed PyInstaller build, `:preflight_compile` +byte-compiles the entry file with the SAME parser the interpreter itself uses (`py_compile`, +zero false positives), reporting a genuine `SyntaxError` clearly and stopping before the build: + +``` +*** [ERROR] REQ-021: Your Python program has a syntax error and cannot run. *** +*** File: "app.py" *** +``` + +followed by the real Python compiler's own traceback (captured to `~preflight.err.txt`, echoed to +both console and log) and a closing `*** Fix the syntax error shown above, then run this batch +again. ***`. `~bootstrap.status.json` correctly reads `state: error`; the dependency install and +environment creation that ran BEFORE this check are left intact (nothing is torn down), so a fixed +file on the next run reuses them. + +**A real bug, found via this exact sweep, confirmed against a real, unrelated capture (not +fabricated for this scenario), and fixed.** The capture below is historical, from before the fix, +kept unedited as a real, timestamped log. `:preflight_compile` used to invoke `"%HP_PY%" -m +py_compile "%HP_ENTRY%"` with no check that `HP_PY` was actually a valid, non-empty interpreter +path first. On TOTAL REQ-009 provider-tier exhaustion (every tier fails or is declined), +`:after_env_mode_selection`'s own `HP_PY`-resolved guard +(`call :die "[ERROR] Active Python interpreter not resolved."`) does NOT actually halt the +pipeline -- per this repo's own long-documented `:die` semantics, `exit /b` inside `:die` only +returns from `:die`'s own call frame, so execution fell through and continued for another ~15 log +lines with an EMPTY `HP_PY`, all the way to `:preflight_compile`. There, `"" -m py_compile +"app.py"` was not a Python invocation at all -- it was cmd.exe trying to execute a program +literally named `""`, which produces a CMD.EXE ERROR, not a Python traceback. Because +`:preflight_compile` treated ANY nonzero exit as "syntax error," it reported this as if the user's +own code were broken. Real capture, from a test that deliberately force-fails uv (offline), conda, +the embed tier, and venv, and declines the REQ-014 system-Python prompt +(`tests/~selftest_embed_decline/`'s own sub-bootstrap): ``` -*** WARNING: Script appears to be in a OneDrive folder. File locking may cause failures. -[WARN] OneDrive path detected; file locking may cause failures. +[ERROR] Active Python interpreter not resolved. +Interpreter: +[WARN] Interpreter smoke test failed (continuing). ``` -**Disk space, REQ-025** (real capture -- warn-only, never a hard block, per REQ-001's rule that a -flag-detectable condition must never gate the Prime Directive). The block emits three raw `echo` -lines before the `[WARN]` line the test asserts on -- all four are console-visible in the same -run; the earlier scan of this scenario quoted only the last one, which undersold what a real user -actually sees: +(pipreqs, dependency install, and the pyvisa check all run to completion afterward, effectively as +no-ops against the broken interpreter, before the entry is finally selected and preflight runs) ``` -*** WARNING: Only ~0 GB free disk space detected on this drive. -*** Downloading Python/Miniconda and building your app can need several GB. -*** If setup fails partway through, freeing up disk space is a likely fix. -[WARN] REQ-025: low disk space detected (~0 GB free); continuing (warn-only). +*** [ERROR] REQ-021: Your Python program has a syntax error and cannot run. *** +*** File: "app.py" *** +'""' is not recognized as an internal or external command, +operable program or batch file. + +*** Fix the syntax error shown above, then run this batch again. *** ``` -**System directory guard** (the one guard that's a hard ABORT, not a warning -- exit code 1, -confirmed via a real, passing NDJSON row, though the specific console dump wasn't captured -verbatim in this run's log excerpt, so the exact banner text below is `[Extrapolated Branch]`, -cited directly from source): +That last block was genuinely misleading: `app.py` may have had no syntax problem whatsoever -- the +real cause, printed several screens earlier, was that no Python interpreter was ever found. A real +user was realistically reachable here for genuine (not test-only) reasons: README's own REQ-009 +table already notes that falling through three-plus provider tiers in one run is "almost always one +shared root cause" (no internet, a full disk, or a locked-down managed image), and a user hitting +exactly that plus declining the REQ-014 system-Python consent prompt would reach this identical +path. The status FILE was never affected (`state: error` was always written correctly, since +`:die`'s own state-set already happened before the fall-through) -- only the human-readable console +narrative misdirected a user who read just the last error rather than scrolling back. + +**Fixed**: `:after_env_mode_selection`'s guard now also sets `HP_NO_INTERPRETER=1` before calling +`:die` (the call-frame-only-return fall-through itself is left as-is -- a deeper refactor of that +mechanism was judged out of scope for this fix). `:preflight_compile` checks this flag first and, +if set, reports the real cause instead of running `py_compile` against an empty interpreter path: ``` -*** ERROR: This script is located inside a Windows system folder. -*** Placing it here does not "install" it. Windows restricts writes to this location -*** without administrator rights, and this bootstrapper needs to create files right -*** next to itself to work. -*** Please move this script (and your .py files) to a normal folder -- your Desktop -*** or Documents folder both work well -- then run it again from there. +*** [ERROR] No Python interpreter is available; your program was not run or built. *** +*** This is not a syntax error -- the Python interpreter itself could not be used. *** +*** Either every automatic Python-acquisition method -- uv, conda, a fresh download, *** +*** or a local virtual environment -- failed, usually from no internet connection, a *** +*** full disk, or a locked-down managed machine image -- or a PVW_PYTHON_EXE override *** +*** points at a path that does not run. Scroll up in this window for the specific reason. *** ``` -**Path-length guard**: this run's own long-path test scenario shows an interesting CI limitation -worth being honest about rather than papering over -- the test's own NDJSON row reports -`warnFound: false, ranBootstrap: false, pathLen: 312`, meaning the scratch directory this specific -CI run created didn't actually reach a state where the sub-bootstrap could run at all (likely an -OS-level path-length limit on the CI runner itself, hit before `run_setup.bat`'s own 200-char -check ever got a chance to fire) -- yet the test still reports an overall pass, since it's -apparently designed to tolerate this inconclusive outcome. The guard's own text is confirmed from -source only for this doc (`[Extrapolated Branch]`): +(The message text was later reworded during implementation to avoid a literal `(...)` pair split +across two `echo` lines inside the same parenthesized `if` block -- cmd.exe's block parser counts +parens in echo text too, so a `(` on one line and its `)` on the next silently mis-closed the +block and broke every CI lane reaching this branch in the same run. See +`docs/agent-lessons-learned.md`'s batch-syntax-quirks section for the full trace.) -``` -*** WARNING: Script path is chars. Paths near 260 chars may cause cmd.exe failures. -``` +This also skips the doomed PyInstaller build attempt entirely (`:run_entry_smoke`'s existing +`HP_PREFLIGHT_FAILED` check already short-circuits the build, unchanged by this fix), not just the +misleading message. --- -### Scenario 25: Concurrent-instance lock contention (REQ-024) +### Scenario 30: REQ-007 system-Python build consent, and the resulting no-EXE interpreter path -**What's tested:** `self.stub.lock_held_decline`/`self.stub.lock_stale_evict` -(`tests/selftest.ps1`, real lane, both real and passing). +**What's tested:** `self.sysbuild.decline` (`tests/selfapps_sysbuild.ps1`, `real` lane, real, +passing) -- its `$pass` gate enforces that the REQ-007 prompt text appears, the decline is +logged, packaging is skipped with a logged reason, and no EXE exists afterward. The REQ-014 +"use system Python at all" accept step that gets this test INTO system-Python mode in the first +place is the same mechanism Scenario 27's own `self.ux.system.gate.accept` test covers +independently, not re-asserted here. The interpreter-smoke success/status lines quoted below +(`Entry smoke exit=0`, `[STATUS] Run Status: SUCCESS`) are genuinely present in this same real +captured log but are NOT part of this test's own `$pass` gate -- shown here as observed fact from +the real capture, not as something this specific test independently verifies. -**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane). +**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane), +`tests/~selftest_sysbuild/`'s own sub-bootstrap -- ONE coherent, continuous real capture covering +the full journey below. -A user double-clicking the bootstrapper twice in quick succession (or genuinely running two -instances) hits an NTFS-atomic `mkdir`-based lock. If a second instance finds the lock genuinely -held by a live first instance: +Scenario 27 showed REQ-014's "use system Python at all?" consent being accepted. That is not the +only consent gate on this tier: once system Python is actually selected as the provider, a SECOND, +independent consent gate (`:system_build_consent_gate`, REQ-007) asks separately about installing +PyInstaller into that same system Python to build a standalone EXE -- distinct from REQ-014, and +reachable only in `HP_ENV_MODE=system` mode (every other provider tier gets no such prompt, since +none of them touch a shared, uncontrolled Python installation): ``` -*** -*** Another instance of this setup appears to be running in this folder already. -*** If you are sure that is NOT the case (for example, a previous run crashed), -*** delete the "~bootstrap.lock" folder next to this script and run it again. -*** -pid= -started= -[WARN] REQ-024: setup already running (lock held, not stale); this instance is exiting. +*** The standalone EXE build installs PyInstaller into your system Python. *** +*** This is the same PyInstaller build used for every provider -- not a special path -- and *** +*** its footprint is small and self-contained (it does not pin common libraries), so it is *** +*** unlikely to conflict with your existing packages. *** ``` -exit code 1 -- the losing instance never touches the lock it doesn't own. If the lock directory -is instead STALE (left over from a crashed/killed prior run, older than the ~2 hour staleness -threshold), it's evicted automatically and the run proceeds normally with no user action needed: +CI auto-declines (same CI-safe pattern as every other consent gate in this file: `HP_TEST_ +SYSBUILD_ANSWER` override checked first, then `HP_CI_LANE` auto-decline, then a real, unbounded +`set /p` for an interactive user). On decline, dependency install and environment setup are left +completely intact -- only the PyInstaller packaging step is skipped: ``` -[INFO] REQ-024: stale lock evicted (older than the staleness threshold); proceeding. +[INFO] REQ-007: system-Python EXE build consent: declined. +[INFO] REQ-007: system-Python EXE build not consented; skipping PyInstaller packaging. The environment and dependencies are installed; run the app directly via the prepared Python. ``` -Staleness is deliberately age-based, not PID-liveness-based -- a dead process's PID can be -recycled by an unrelated program, so trusting PID liveness for automated eviction would be unsafe. - ---- +With no EXE ever attempted, `:verify_no_exe_interpreter` becomes the sole verification run (this is +also the general no-EXE path reached whenever `dist\.exe` doesn't exist for any reason -- a +declined build here, or PyInstaller being entirely unavailable elsewhere -- not specific to this +consent gate): -## Part VII: Remaining branches (dependency source precedence, write-back, and misc) +``` +[INFO] Running entry script smoke test via system interpreter. +[INFO] Entry smoke exit=0 +[STATUS] Run Status: SUCCESS (Exit Code: 0) +[INFO] REQ-018: post-execution checkpoint (interpreter): declined (run footprint stays at one execution). +``` -**Scope note:** this part collects everything from the plan's 5-pass checklist that didn't fit -naturally into Parts III-VI: dependency and Python-version source precedence, the two write-back -mechanisms (`runtime.txt`, PEP 723 headers), the optional execute-mode discovery flag, NI-VISA/ -pandas per-package special-casing, the periodic conda maintenance timer, and the one REQ-014 -branch (consent ACCEPT) that Part VI's Scenario 21 didn't cover (it only showed decline). +The postflight briefing that follows is `:print_no_exe_briefing` (Part V/CLAUDE.md's REQ-027 P2 +work already documents its honest-messaging design and its `HP_NOEXE_VERIFY_FAILED`-gated caveat +variant) rather than the EXE-focused `:print_postflight_briefing` -- distinct panels for a +genuinely distinct outcome (no packaged deliverable exists, but the app runs fine via the prepared +interpreter). --- -### Scenario 26: Git config merge (`.gitignore`/`.gitattributes`, REQ-015) +### Scenario 31: EXE smoke-run diagnostic hints (companion to Scenario 16) -**What's tested:** `self.ux.gitignore.merge`/`.preserve`/`.idem`, `self.ux.gitattributes.merge` -(`tests/selfapps_ux_hardening.ps1`, `real` lane, all real and passing). +**What's tested:** the `[HINT]` mechanism fires as a byproduct of `selfapps_exedata_fail.ps1` +(`DATA_FILE` hint) and `selfapps_exedyn_fail.ps1`/`selfapps_hidden_import_exhaust.ps1`-family tests +(`HIDDEN_IMPORT` hint), both real/conda-full lanes, real, passing (the hint lines are a bonus +diagnostic these tests emit, not the tests' own primary assertion target). **Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane). -On a fresh checkout with no `.gitignore`/`.gitattributes` at all, `:merge_git_config` (`run_setup.bat`) -idempotently appends a standard block to each, guarded by an `findstr` signature check -(`# Automated Python Bootstrapper Standard Ignores` / `...Attributes`) so a second run is a clean -no-op rather than a duplicate append: - -``` -[INFO] REQ-015: Appending standard ignores to .gitignore. -[INFO] REQ-015: Appending standard attributes to .gitattributes. -``` +Scenario 16 covers `--hidden-import` auto-recovery reaching its 3-attempt cap without resolving. In +that same failure family -- any EXE that still fails at runtime AFTER hidden-import recovery has +been tried (or was never applicable, e.g. a genuinely missing DATA file, not a missing module) -- +`:exe_smokerun_hints` re-runs the EXE briefly (no timeout needed; these failures exit immediately) +purely to pattern-match its stderr and offer a targeted, actionable hint. Two real captures, from +two different xfail scenarios: -The appended `.gitignore` block (verbatim from source): +**A missing bundled data file** (real capture, `FileNotFoundError`): ``` -# Automated Python Bootstrapper Standard Ignores -.*_env/ -.venv/ -.uv/ -.cache/ -.conda/ -dist/ -build/ -*~ -~* +[HINT][DATA_FILE] Missing data file detected: C:\Users\RUNNER~1\AppData\Local\Temp\_MEI41642\mypkg\data\info.txt +[HINT][DATA_FILE] Consider adding: --add-data C:\Users\RUNNER~1\AppData\Local\Temp\_MEI41642\mypkg\data\info.txt;. +[HINT][RUNTIME_MISMATCH] Standalone EXE behavior differs from the Python runtime (possible PyInstaller packaging issue in the EXE, not your environment or dependencies) ``` -and `.gitattributes`: +**A missing module hidden-import recovery couldn't resolve** (real capture, `ModuleNotFoundError`): ``` -# Automated Python Bootstrapper Attributes -*.bat eol=crlf -*.cmd eol=crlf -*.exe binary +[HINT][HIDDEN_IMPORT] Hidden import likely missing: absent_dynmod_xyz +[HINT][HIDDEN_IMPORT] Consider adding: --hidden-import=absent_dynmod_xyz +[HINT][RUNTIME_MISMATCH] Standalone EXE behavior differs from the Python runtime (possible PyInstaller packaging issue in the EXE, not your environment or dependencies) ``` -Real evidence confirms all three properties the tests assert: the signature is appended -(`self.ux.gitignore.merge`), any PRE-EXISTING content in the file (e.g. a user's own `node_modules/` -line) survives the merge untouched (`self.ux.gitignore.preserve`, `nodeModulesFound:true`), and -running the bootstrapper a second time does not duplicate the signature (`self.ux.gitignore.idem`, -`sigCount:1`). This runs unconditionally on every bootstrap invocation, independent of provider or -entry-file state -- it is one of the first things `run_setup.bat` does after the pre-flight guards. +Both hint types are logged via `:log` (console AND `~setup.log`), so a real user hitting either +failure sees them directly -- not buried in a diagnostic-only file. The `RUNTIME_MISMATCH` hint is +a universal closing line, not specific to the `DATA_FILE` branch -- both real captures above show +it: every path through `:exe_smokerun_hints` (data-file match, module-not-found match, or neither) +falls straight through to it with no `goto`/`exit` skipping it in between, so it always fires +alongside whichever more specific hint (if any) matched, as a general reminder that a frozen EXE's +behavior can differ from the interpreter's for packaging reasons unrelated to environment or +dependencies. A separate, optional machine-readable form exists too (`HINT_JSON=1`, an +undocumented-in-README super-user flag that additionally prints each hint as a compact JSON object +via PowerShell) -- not independently captured in this run. --- -### Scenario 27: Python-version precedence (REQ-004) and dependency-source precedence (`pyproject.toml`) - -**What's tested:** `pyproject.precedence.detect`/`.writeback` (`tests/selfapps_pyproject_precedence.ps1`, -both real and passing) and `pyproject.dep.detect`/`.noproj` (same file). `pyproject.precedence.detect` -ran in the `conda-full` lane in this capture (the `real` lane's own copy of the row emits -`skip=true, reason=conda-not-installed-uv-first` since it happens to call `Get-CondaBatPath`, per -`docs/agent-interconnect.md`'s skip-pattern convention for this test file); `pyproject.dep.*` and -`.writeback` ran in `real`. +## Part VII: Full startup-to-shutdown walkthroughs -**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708094` (`conda-full`) for the version -tiers, job `90179708091` (`real`) for the dependency-source precedence. +**Scope note:** every scenario elsewhere in this file is deliberately narrow -- one feature area, +excerpted to the lines that illustrate it. This Part does the opposite: five composite panels that +show a run from its first console line to its last, so a reader can see the actual SHAPE of a +complete bootstrap without jumping between sections. None of these is sourced from one single, +continuous CI capture (no test in this repo is designed to produce that) -- each is assembled from +real fragments already individually cited elsewhere in this file, spliced into one continuous flow +and labeled where the splice itself (not any individual line) is `[Extrapolated Branch]`. Cross- +reference the originating scenario for that fragment's own detailed sourcing rather than re-deriving +it here. -These are two genuinely SEPARATE precedence systems that happen to both read `pyproject.toml` and -are easy to conflate -- worth documenting distinctly. +### Scenario 32: Full walkthrough -- the ordinary happy path, start to shutdown -**REQ-004 (Python VERSION precedence, three tiers)**: Tier 1 `runtime.txt` beats Tier 2 -`pyproject.toml`'s `[project].requires-python` beats Tier 3 "let the selected provider pick latest, -then write `runtime.txt` back." These two NDJSON rows deliberately test two DIFFERENT things in two -DIFFERENT scratch directories with two DIFFERENT constraints, not one continuous flow -- worth -being precise about, since the test file's own comments explain why: `.detect` calls -`~detect_python.py` directly (no bootstrap, no environment ever created) against a tight -`requires-python = ">=3.10,<3.11"` to check Tier 2's parse/forward precision in isolation; `.writeback` -runs the FULL bootstrapper against a deliberately loose `requires-python = ">=3.9"` in a separate -directory ("so conda picks a cached Python version and avoids a slow resolver round-trip for -Python 3.10 packages," per the test's own comment) to check Tier 3's write-back end to end. +Splices Scenario 2's pre-flight/entry-detection evidence and Scenario 3's provider/dependency +evidence (both from the `colorama`-importing stub app) with Scenario 4's own single, fully +self-consistent, zero-staleness real capture (the `hello_stub.py` trivial app, chosen there +specifically because it already spans build through the final status panel with no gaps). All three +are real CI captures from the same run (`30328748330`, job `90179708091`, "real" lane) -- just two +different scratch apps within that run, since no single test in this repo builds one app all the +way from a cold uv download through a `hello_stub`-style trivial verification. The splice point +(where the `colorama` app's pre-build evidence hands off to the `hello_stub` app's build-onward +evidence) is `[Extrapolated Branch]` -- the two are structurally identical at that point (both are +first-ever runs, no cached state, uv-first), but were never the same process. -`.detect`'s real NDJSON output confirms Tier 2's parse/forward is exact: `output":"python>=3.10,<3.11"`. -`.writeback`'s real capture shows Tier 3 firing (since `runtime.txt` didn't pre-exist there either): +``` +[INFO] REQ-015: Appending standard ignores to .gitignore. +[INFO] REQ-015: Appending standard attributes to .gitattributes. +Chosen entry: app.py +[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. +[INFO] uv: creating venv at .uv_env... +[INFO] uv: venv created at .uv_env +[INFO] HP_ENV_MODE=uv +[BOOT] REQ-009: Selected Python provider: UV. +[INFO] runtime.txt written: python-3.14.6 +[INFO] pipreqs 0.4.13 installed successfully; using it for dependency discovery. +*** [WARN] Dependencies were auto-detected (pipreqs) +*** [WARN] Auto-detection may be incomplete or incorrect +*** [INFO] Consider adding requirements.txt or PEP 723 metadata for reliability +[INFO] REQ-005.5: dependency source diff computed -- ~pipreqs.diff.txt +[INFO] REQ-005.12: autopep723 discovery merge complete. +[INFO] UV_USED=1 +[INFO] DEP_INSTALLED_CAPTURED=1 +[INFO] Environment snapshot written: ~environment.lock.txt +[INFO] REQ-005.11: PEP 723 header write-back succeeded via uv add --script. +[INFO] Building standalone executable -- this may take a minute or two... +[INFO] (A stray one-line Windows message about a missing drive may appear next -- that is a known side effect from an unrelated background process, unrelated to your app; safe to ignore.) +The system cannot find the drive specified. +The system cannot find the drive specified. +[INFO] PyInstaller produced dist\.exe +[DEBUG] warnfix: warn file found +[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. +[INFO] PyInstaller build artifacts cleaned up. +[INFO] EXE smokerun: testing dist\.exe +[INFO] Running entry script smoke test via packaged EXE. +[WARN] Verifying the built standalone EXE (PyInstaller) now: if it stays completely silent for about 30 seconds it will be force-stopped, but any output (including a prompt waiting on your input) keeps it running as long as needed. If your program is interactive, try answering its prompts through to its own quit/exit option now so we can confirm it exits cleanly. Either way, do not start real work in it yet or any unsaved work will be lost. +[INFO] Process ID 6076. If it seems stuck: Task Manager > Details tab > find this PID > End Task (this window stays open). +hello-from-stub +[INFO] EXE smokerun: exited 0 (ok) +[INFO] Entry smoke exit=0 +[STATUS] Run Status: SUCCESS (Exit Code: 0) -``` -[INFO] runtime.txt written: python-3.14.6 +*** Verification finished -- see the Run Status above. *** +*** You can run your program again now via the interpreter as an extra diagnostic check. *** + Run again via the interpreter now? [Y/N] _ ``` -(real capture; `pyproject.precedence.writeback`'s own NDJSON row confirms `runtimeVersion: -python-3.14.6` and `versionSatisfied:true` against ITS OWN, looser `>=3.9` constraint -- 3.14.6 -satisfies `>=3.9` comfortably. It does NOT satisfy the OTHER test's `<3.10,<3.11` constraint, but -that constraint was never used for this environment; the two tests are independent, and this -document originally conflated them into one implied sequence before being corrected.) Malformed -`pyproject.toml` TOML degrades gracefully rather than aborting the whole precedence chain (real -capture, `self.pyproject.malformed`): +(the checkpoint prompt line itself is `[Extrapolated Branch]`, same reasoning as Scenario 5 -- it +lives inside an unbounded `set /p`, never visible in a CI log). Declining both elective prompts +(the checkpoint above, then the optimized-build offer) reaches the same "SETUP COMPLETE" panel +already quoted in full in Scenario 4 -- not repeated a third time here; see that scenario for the +exact panel text, or Scenario 5 for what accepting either prompt does instead. -``` -*** [WARN] pyproject.toml could not be parsed as valid TOML; falling back to requirements.txt or pipreqs. -``` +### Scenario 33: Full walkthrough -- uv can't resolve a dependency, cascades to conda, which does -logged compactly too: `[WARN] pyproject.toml TOML parse error; falling back.` -- the bootstrap then -proceeds via Tier 3 (provider picks latest) exactly as if `pyproject.toml` had never existed. +**Honesty note up front:** this exact narrow case -- uv fails specifically at DEPENDENCY +RESOLUTION (not environment creation), cascades exactly once, and conda succeeds -- has no single +real capture in this repo's CI history to point to. Scenario 15's own real capture (the fullest +cascade evidence this repo has) walks uv through every tier to full exhaustion in one run, which is +genuinely a harder case to hit than "cascades once and the next tier just works." The pieces below +are real, individually cited fragments; the SPLICE connecting "uv cascades to conda" into "conda +then succeeds and the run completes" is `[Extrapolated Branch]`, reusing this doc's own +already-verified building blocks (the provider-selection log line and the generic build/verify/ +complete tail, both provider-agnostic in their wording) rather than inventing new text. -**Dependency-SOURCE precedence (a different mechanism, REQ-004/REQ-005.1 rows, unrelated to Python -version)**: when `pyproject.toml` declares a real `[project].dependencies` array, it takes priority -over any `requirements.txt` present -- this is decided independently of the version-tier logic -above and can fire even when `runtime.txt` already exists. Real capture: +Real trigger (Scenario 15, same wording, same reasoning about why it says "uv to conda" not +"uv -> conda"): ``` -*** [INFO] pyproject.toml [project].dependencies found; overrides requirements.txt -[INFO] pyproject.toml [project].dependencies detected -[INFO] DEP_SOURCE=pyproject +[WARN] Repair failed: +[WARN] One or more repair attempts failed +[REPAIR] rebuild complete after warnfix. +[INFO] Entry smoke exit=1 +[STATUS] Run Status: FAILED (Exit Code: 1) +[INFO] REQ-009: cascade approved; skipping the post-verification offers for this build. +[INFO] REQ-009: cascading provider uv to conda; re-attempting dependencies. +*** [INFO] Trying the next Python provider (conda) to resolve dependencies... ``` -`~pyproj_deps.py` (`HP_PYPROJ_DEPS`) is the helper that extracts the array; real NDJSON detail from -`pyproject.dep.detect` shows it parsing a real two-line array (`"output":"requests>=2.28\r\ncolorama"`, -`exitCode:0`). When `pyproject.toml` has no `[project]` section at all, the helper exits 1 with no -output rather than a false match (`pyproject.dep.noproj`, `exitCode:1, outExists:false`) -- the -bootstrapper then falls through to `requirements.txt`/pipreqs as usual. - ---- +`:cascade_acquire_conda` downloads and installs Miniconda on demand at this point if it wasn't +already on disk (uv-first runs skip Miniconda entirely until something actually needs it -- see +`docs/agent-interconnect.md`'s "uv-First Provider Architecture"), then re-enters the same +dependency-install machinery Scenario 3 already documents in full, just under +`HP_ENV_MODE=conda` this time (`[Extrapolated Branch]` for this specific re-entry's own console +text, though every individual line reused below is independently real elsewhere in this file): -### Scenario 28: PEP 723 dependency write-back (REQ-005.11) -- the fresh-install trigger +``` +[BOOT] REQ-009: Selected Python provider: Conda (Portable). +Creating Python environment '' -- this may take several minutes... +[INFO] runtime.txt written: python-3.14.6 +[INFO] Building standalone executable -- this may take a minute or two... +[INFO] PyInstaller produced dist\.exe +[INFO] EXE smokerun: testing dist\.exe +[WARN] Verifying the built standalone EXE (PyInstaller) now: if it stays completely silent for about 30 seconds it will be force-stopped, but any output (including a prompt waiting on your input) keeps it running as long as needed. If your program is interactive, try answering its prompts through to its own quit/exit option now so we can confirm it exits cleanly. Either way, do not start real work in it yet or any unsaved work will be lost. +[INFO] EXE smokerun: exited 0 (ok) +[INFO] Entry smoke exit=0 +[STATUS] Run Status: SUCCESS (Exit Code: 0) +``` -**What's tested:** `self.pep723.writeback.fresh`/`.skipflag` (`tests/selfapps_pep723_writeback.ps1`, -`real` lane, both real and passing). +followed by the ordinary "SETUP COMPLETE" panel (Scenario 4). The mechanism-level reason conda has +a genuine, above-average chance of resolving what uv couldn't -- a real, different package index +with pre-built native-extension wheels, not just a fresh attempt at the same resolution -- is +covered in `docs/agent-interconnect.md`'s "Cascade signal reliability" section; that section is +also why later cascade hops (embed/venv/system) carry comparatively less of this same justification. -**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane). +### Scenario 34: Full walkthrough -- warnfix repair and rebuild, start to finish -After a genuinely fresh, fully-successful `HP_ENV_MODE=uv` dependency install (see Part III, -Scenario 9), `:pep723_writeback` promotes the resolved dependency set into the entry file's own -PEP 723 header via `uv add --script`, so the pin travels with the user's source file rather than -staying only in `requirements.txt`/the lock file: +Splices Scenario 17's real repair-loop fragments with the generic build-start/verify/complete text +already verified real elsewhere in this file (Scenario 4). The connecting tissue (that these two +fragments belong to the same run) is `[Extrapolated Branch]`; every individual line is independently +a real capture cited in its own originating scenario. ``` -[INFO] REQ-005.11: PEP 723 header write-back succeeded via uv add --script. +[INFO] Building standalone executable -- this may take a minute or two... +[INFO] PyInstaller produced dist\.exe +[DEBUG] warnfix: warn file found +[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. +[REPAIR] missing modules detected; installing and rebuilding. +[REPAIR] rebuild complete after warnfix. +[INFO] PyInstaller build artifacts cleaned up. +[INFO] EXE smokerun: testing dist\.exe +[WARN] Verifying the built standalone EXE (PyInstaller) now: if it stays completely silent for about 30 seconds it will be force-stopped, but any output (including a prompt waiting on your input) keeps it running as long as needed. If your program is interactive, try answering its prompts through to its own quit/exit option now so we can confirm it exits cleanly. Either way, do not start real work in it yet or any unsaved work will be lost. +[INFO] EXE smokerun: exited 0 (ok) +[INFO] Entry smoke exit=0 +[STATUS] Run Status: SUCCESS (Exit Code: 0) ``` -When there is nothing to write (a stdlib-only app, no third-party packages resolved), the -subroutine correctly no-ops rather than writing an empty/misleading header -- also a REAL capture: +followed by the ordinary "SETUP COMPLETE" panel. Note what does NOT appear here: no second +"[INFO] Building standalone executable" banner precedes the repair rebuild -- `[REPAIR] rebuild +complete after warnfix.` covers the whole re-invocation, PyInstaller's own build noise from that +second pass is not separately re-echoed. The failure variant of this same loop (a module that +genuinely can't be installed, e.g. `StringIO`) is already documented in Scenario 17 -- that variant +still reaches `[REPAIR] rebuild complete after warnfix.` (bundling whatever DID install) and is what +actually feeds the provider cascade Scenario 33 above walks through. + +### Scenario 35: Full walkthrough -- `--hidden-import` auto-recovery succeeds on the first rebuild + +**Honesty note:** Scenario 16 documents this mechanism's EXHAUSTION path with a real captured line; +the one-shot SUCCESS path's exact rebuild line has never been independently console-dumped in this +file, only confirmed present via `self.exe.hidden_import`'s own passing NDJSON row (the same +"test passes, so no full log was dumped" situation Scenario 39 already documents for a different +mechanism) and via the exact line format quoted from source at Scenario 39 (a different context -- +Tier A's hidden-import SKIP guard -- but quoting the identical `[REPAIR][HIDDEN_IMPORT]` line +PyInstaller's own recovery path would have printed). This whole panel is therefore +`[Extrapolated Branch]`, built from source, not a stitch of independently-real fragments the way +Scenario 32/33/34 above are. ``` -[INFO] REQ-005.11: PEP 723 write-back skipped (no packages to write). +[INFO] Building standalone executable -- this may take a minute or two... +[INFO] PyInstaller produced dist\.exe +[INFO] PyInstaller build artifacts cleaned up. +[INFO] EXE smokerun: testing dist\.exe +[WARN] Verifying the built standalone EXE (PyInstaller) now: if it stays completely silent for about 30 seconds it will be force-stopped, but any output (including a prompt waiting on your input) keeps it running as long as needed. If your program is interactive, try answering its prompts through to its own quit/exit option now so we can confirm it exits cleanly. Either way, do not start real work in it yet or any unsaved work will be lost. +[WARN] EXE smokerun: exited 1 (non-zero) +[REPAIR][HIDDEN_IMPORT] Adding --hidden-import=; rebuilding EXE (iter 1/3) +[INFO] PyInstaller produced dist\.exe +[INFO] EXE smokerun: testing dist\.exe +[INFO] EXE smokerun: exited 0 (ok) +[INFO] Entry smoke exit=0 +[STATUS] Run Status: SUCCESS (Exit Code: 0) ``` -This is `HP_ENV_MODE=uv`-only (v1 scope, see `docs/agent-interconnect.md`) and best-effort/non-gating --- any failure (malformed existing header not cleanly repairable, a file lock, non-UTF-8 source) -logs a `[WARN]` and the bootstrap continues unaffected; `HP_SKIP_PEP723_WRITEBACK=1` suppresses it -outright per REQ-019 (a genuine opt-OUT flag, not a gate). The warnfix-triggered SECOND write-back -call (after a successful repair round) is functionally identical and not separately captured here --- same subroutine, same two possible outcomes, triggered from a different call site. +followed by the ordinary "SETUP COMPLETE" panel. The gate that makes this rebuild worth attempting +at all -- the failure must be a real `ModuleNotFoundError` (not a bare `ImportError`) for a module +that IS installed in the build interpreter, and the EXE must have been built by PyInstaller, not +Tier A's Nuitka fallback -- is covered in full in `docs/agent-lessons-learned.md`'s "--hidden-import +auto-recovery must stay STRICT" entry; Scenario 16 covers the case where three rebuilds still don't +resolve it. ---- +### Scenario 36: Full walkthrough -- `HP_PVW_KNOWN_IDEMPOTENT`, with the actual input and output files -### Scenario 29: `HP_PVW_KNOWN_IDEMPOTENT` execute-mode discovery (REQ-005.13) +Scenario 23 already covers this flag's console output; this walkthrough adds what that scenario +doesn't show: the actual file contents a user would see before and after. Source: the real, +deterministic stub app `tests/selfapps_pvw_idempotent.ps1` stages for `self.pvw_idempotent.discovery` +(`uv` lane, real, passing) -- quoted directly from the test's own source, not a CI console dump, but +these are the literal bytes that test writes and asserts against. -**What's tested:** `self.pvw_idempotent.discovery` (`tests/selfapps_pvw_idempotent.ps1`, `uv` lane, -real and passing). +**Input, `app.py` (the user's only file, before running the bootstrapper at all):** -**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708109` (`uv` lane). +```python +import requests +print('t2-idempotent-ok') +``` -This is an opt-in super-user flag (not part of the default happy path -- Part III never sets it): -when defined, `run_setup.bat` skips straight to actually RUNNING the entry file live via -`uvx autopep723 ` for dependency discovery, before pipreqs or any static analysis even -starts. Real capture: +No `requirements.txt`, no `pyproject.toml`, no PEP 723 header -- genuinely nothing else in the +folder besides `run_setup.bat` and this one file (the test isolates pipreqs via `HP_SKIP_PIPREQS=1` +specifically so this is the ONLY way `requests` can end up discovered -- see Scenario 23's own +production-vs-test-isolation note for why that flag is test-only, never how this feature runs for a +real user). + +**Console output** (real, `self.pvw_idempotent.discovery`'s own capture, already fully quoted in +Scenario 23 -- repeated here only for continuity with the file changes below): ``` [INFO] REQ-005.13: HP_PVW_KNOWN_IDEMPOTENT set; running entry via uvx autopep723 for execute-mode discovery. +t2-idempotent-ok [INFO] REQ-005.13: execute-mode discovery run succeeded (RAN:persisted). ``` -The entry script's own stdout is inherited/passed through live during this discovery run (not -captured or suppressed) -- real NDJSON detail confirms `stdoutPassthroughFound:true, appRan:true` --- and whatever dependency it needed (`requests`, in this real capture: `reqsHasRequests:true`) is -persisted back into the PEP 723 header via `uv add --script`, then re-extracted into -`requirements.txt` so the rest of the pipeline (pipreqs, Tier 1 autopep723 merge, the actual -install) sees it too. Deliberately ADDITIVE, not a replacement for pipreqs -- pipreqs and Tier 1's -own `autopep723 check` merge still run normally afterward to catch anything a single execution -path didn't happen to exercise. - ---- +(`t2-idempotent-ok` is the app's own live stdout, inherited straight through the discovery run -- +real NDJSON detail confirms `stdoutPassthroughFound:true`, the exact design point +`tools/pvw_known_idempotent.py` exists to preserve; see `docs/agent-interconnect.md`.) -### Scenario 30: NI-VISA detection and install outcome (REQ-008) +**Output, `app.py` (same file, now carrying a PEP 723 header `uv add --script` wrote in place):** +this is the STANDARD shape `uv add --script` is documented to produce (see +`docs/agent-lessons-learned.md`'s "`uv add --script` / PEP 723 empirical behavior" section) -- +`[Extrapolated Branch]` for the exact formatting shown, since this specific test doesn't assert +byte-for-byte header content, only that the resulting `requirements.txt` (below) ends up containing +`requests`: -**What's tested:** `pyvisa.detect`/`.nivisa.branch`/`.nivisa.outcome`/`.nivisa.reason`/`.nivisa.disabled` -(`tests/selfapps_pyvisa.ps1`, `real`/`conda-full` lanes, all real and passing). +```python +# /// script +# dependencies = [ +# "requests", +# ] +# /// -**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real`) and job `90179708094` -(`conda-full`) -- both lanes captured a genuine NI-VISA install ATTEMPT in this run (not just the -"not required" skip), which is a more complete illustration than what an earlier pass in this -document assumed was CI's only available evidence. +import requests +print('t2-idempotent-ok') +``` -When `pyvisa`/`visa` is detected as an import, the bootstrapper attempts a real NI-VISA driver -install (downloads the online bootstrapper installer via curl, PE-validates it, then launches it -under a bounded timeout): +**Output, `requirements.txt` (newly created -- did not exist before this run):** re-extracted from +the header above via the same `:extract_pep723_requirements` subroutine the pre-existing-header case +already uses (see `docs/agent-interconnect.md`'s "HP_PVW_KNOWN_IDEMPOTENT execute-mode discovery" +section for why re-extraction, not the header alone, is what feeds the rest of the pipeline). Real, +directly confirmed: the test's own assertion is `$reqsText -match 'requests'` against this exact +file: ``` -[INFO] Detected pyvisa/visa import; NI-VISA install may be required. -[VISA] download method: curl -[VISA] installer file size: 6769400 bytes -[VISA] installer PE check: PE_OK -[INFO] Launching NI-VISA installer (timeout ceiling: 5400000 ms). -[VISA] installer exit code: -125202 -[VISA] post-check waiting; retry 1/3 (installer_rc=-125202) -[VISA] post-check waiting; retry 2/3 (installer_rc=-125202) -[VISA] install_failed (post_check_timeout) installer_rc=-125202 +requests ``` -This matches CLAUDE.md's already-documented Known Finding ("NI-VISA real install fails fast in CI") -in shape and mechanism exactly -- a genuine, PE-valid installer download that exits fast and -unattended-incompatible on a CI runner -- though the SPECIFIC installer exit code observed here -(`-125202`) differs from that finding's originally-cited `-125083`. Consistent with the finding's -own framing (an online bootstrapper installer failing an unattended install, not a fixed/stable -error code), not a new discrepancy worth a separate backlog entry. The bootstrap proceeds -gracefully regardless -- a failed NI-VISA install is never treated as a bootstrap failure, only -logged and surfaced; the user's own program still builds and runs. - -Real evidence confirms the OTHER outcome branch too, via a dedicated `HP_SKIP_NIVISA=1` scenario in -the same test file: `[VISA] skipped (not_required)`, with `skippedDisabled:true, -noInstallAttempt:true` -- the flag suppresses the install attempt outright even when pyvisa IS -detected, per REQ-019's suppression-only convention. - ---- +Production behavior (unlike this test's isolated setup) does NOT stop here -- pipreqs and the +Tier 1 `autopep723 check` merge (Scenario 3) both still run normally afterward, additively layering +on top of whatever this execute-mode pass already found, in case anything conditionally-imported +didn't execute during this one discovery run. -### Scenario 31: pandas/openpyxl heuristic dependency augmentation (REQ-005.8) +## Part VIII: AV-Safe Build Path (Nuitka fallback) -**What's tested:** `pandas_excel.translate`/`.conda.install`/`.conda.install.req006`/`.runtime`, -`self.pandas.openpyxl.install`/`.import` (`tests/selfapps_pandas_excel.ps1`, `conda-full` lane, all -real and passing) -- plus a genuinely SEPARATE test that happens to exercise the same heuristic in -a different scratch directory, `self.exe.warnfix.real` (`tests/selftest.ps1`'s `real` scenario, -`conda-full` lane, also real and passing, `desc: "Heuristic pre-installed openpyxl via pandas -heuristic; EXE succeeded"`). +### Scenario 37: PyInstaller build fails, Tier A (Nuitka) fallback succeeds -**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708094` (`conda-full` lane). +**What's tested:** `self.exe.build.tiera` (`tests/selfapps_nuitka_tiera.ps1`, uv lane, +non-gating). `HP_TEST_FORCE_PYINSTALLER_FAIL=1` forces the primary build to fail deterministically; +the Nuitka fallback (`:try_nuitka_tier_a`) then runs for real -- a genuine compile, not simulated. -`~prep_requirements.py` (`HP_PREP_REQUIREMENTS`) applies a small set of heuristic rules that inject -a commonly-needed-but-undeclared package when its "parent" package is present -- pandas's -`pd.read_excel()`/`to_excel()` need `openpyxl`/`xlsxwriter`, but pipreqs' static analysis has no -way to see that a lazily-imported optional engine is actually required at runtime. Real capture -from `tests/~pandas_excel/`'s own scratch directory: +**What appears on screen**, from the moment PyInstaller's build is attempted through to the final +summary. Sourced from real CI (run `29788624195`, job `88506013149`); the "(fallback build system)" +verification line and the drive-message reassurance line are exactly as captured, while the +"Verifying the built standalone EXE" line and the "Does your program need launch arguments" +paragraph reflect current source (`docs/plan-cli-interactive-verification.md` requirement 3's +activity-aware kill, and REQ-026's argv passthrough, both of which shipped after this specific run): ``` -[HEURISTIC] pandas->xlsxwriter -``` +[INFO] Building standalone executable -- this may take a minute or two... +[INFO] (A stray one-line Windows message about a missing drive may appear next -- that is a known side effect from an unrelated background process, unrelated to your app; safe to ignore.) +The system cannot find the drive specified. +The system cannot find the drive specified. +[TEST] HP_TEST_FORCE_PYINSTALLER_FAIL: simulating PyInstaller build failure. +[INFO] Standard build did not complete; attempting a fallback build (this may take a minute or two). +[INFO] Fallback build succeeded: dist\.exe was produced using the fallback build system. +[DEBUG] warnfix: warn file not found +[INFO] PyInstaller build artifacts cleaned up. +[INFO] EXE smokerun: testing dist\.exe +[INFO] Running entry script smoke test via packaged EXE. +[WARN] Verifying the built standalone EXE (fallback build system) now: if it stays completely silent for about 30 seconds it will be force-stopped, but any output (including a prompt waiting on your input) keeps it running as long as needed. If your program is interactive, try answering its prompts through to its own quit/exit option now so we can confirm it exits cleanly. Either way, do not start real work in it yet or any unsaved work will be lost. +[INFO] EXE smokerun: exited 0 (ok) +[INFO] Entry smoke exit=0 +[STATUS] Run Status: SUCCESS (Exit Code: 0) -(the console line is a compact tag; the two package names themselves are appended to the conda -install spec list, confirmed by the real conda solve plan later in the same `~pandas_excel` log: -`openpyxl conda-forge/win-64::openpyxl-3.1.5-py314hccc76fc_3` and `xlsxwriter -conda-forge/noarch::xlsxwriter-3.2.9-pyhd8ed1ab_0`, and both packages installed into -`~pandas_excel`'s own `requirements.txt`/`~reqs_conda.txt`/`~reqs_pip.txt`). The claim that -`openpyxl` ends up genuinely bundled and importable in a frozen EXE is confirmed by the SIBLING -`self.exe.warnfix.real` test's OWN independent scratch directory (`tests/~selftest_warnfix_real/`, -a different app that also exercises the pandas heuristic, per its own NDJSON `desc` text quoted -above) rather than by `~pandas_excel`'s own PyInstaller warn-file -- its EXE's PyInstaller warn -file shows only expected, harmless optional-dependency lines for the bundled `openpyxl`: `missing -module named PIL - imported by openpyxl.drawing.image (optional)`, not a real gap. This is -`HP_ENV_MODE=conda`-lane-only coverage per this test file's own CI wiring (see -`docs/agent-interconnect.md`'s -"selfapps_pandas_excel.ps1" note) -- the SAME heuristic logic also runs for uv/venv/embed/system -providers via `requirements.txt` write-back (CLAUDE.md's own "Deep research pass" Closed Backlog -entry on this exact fix), just not captured here since this test is conda-only by design. +*** Verification finished -- see the Run Status above. *** +*** You can run your program again now via the interpreter as an extra diagnostic check. *** +[INFO] REQ-018: post-execution checkpoint (exe): declined (run footprint stays at one execution). ---- +============================================================ + SETUP COMPLETE +============================================================ + Your standalone application is ready: + dist\.exe -### Scenario 32: Conda base periodic update + RUNNING YOUR APP + Double-click dist\.exe to run it. -**What's tested:** `self.conda.base.update` (`tests/selfapps_conda_update.ps1`) -- **NOT currently -wired into any CI lane** (per `docs/agent-ndjson.md`'s own explicit note: the `HP_TEST_CONDA_UPDATE` -injection flag was removed because it upgrades conda to a solver version that cascades failures -across the rest of the `conda-full` job). Only the "skipped" branch below has real CI evidence. + STARTUP MAY BE SLOW: a one-file .exe unpacks itself each time it + starts, so allow 10-15 seconds (longer for big libraries like + numpy/scipy/matplotlib, or when extra packages were bundled to fix + missing imports) before assuming it has hung. -**Source (skipped branch):** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane, -appears identically across every fresh scratch-dir bootstrap in the run). + If the window flashes and closes instantly: that's normal if + your program finished quickly or hit an error before printing + anything. To see what happened, open Command Prompt, cd to + this folder, and run: + dist\.exe + This keeps the window open so you can read any messages. -`:conda_base_update` runs `conda update -n base` on a timer (30-day threshold, seeded from -`~conda.lastupdate` on first install) whenever `HP_ENV_MODE=conda`. On a genuinely first-ever -install (the common case in a fresh CI scratch dir, and for most real first-time users), it -correctly skips rather than updating a base that was just installed moments ago: + A progress indicator that updates in place may appear all at + once instead of live when run as the .exe -- that is a stdout + buffering difference between the .exe and the script, not an error. -``` -[INFO] Conda base update: skipped (first install). -``` + 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 "" --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\.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. -**`[Extrapolated Branch]`** -- the actual 30-day-elapsed UPDATE-firing branch (`:cbu_run`, -`run_setup.bat`) is not exercised by any current CI run (the only flag that could force it is -deliberately disabled, per the note above). Traced from source: once the timestamp in -`~conda.lastupdate` is more than 30 days old, the subroutine runs `conda update -n base -y` and -would log something in the shape of `[INFO] Conda base update: running (last updated N days -ago)...` followed by conda's own real update-solve output, then rewrites `~conda.lastupdate` to the -current time on completion. This branch realistically only fires for a long-lived, repeatedly-reused -project folder -- not the fresh-checkout scenarios this document otherwise captures -- and is -correctly out of scope for a dedicated CI test per the reasoning already on record (a forced-update -test previously broke conda's own solver in shared CI runners). No new CLAUDE.md backlog entry -added -- this gap is already fully documented and deliberately accepted in -`docs/agent-ndjson.md`'s "conda-full lane rows" section. + 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) + build\ -- PyInstaller build cache +============================================================ +``` + +The "Verifying the built standalone EXE" line now correctly says "(fallback build system)" +instead of a hardcoded "(PyInstaller)" when the EXE being verified was actually Nuitka-built +(`:warn_user_code_launch` branches on `HP_NUITKA_FALLBACK_USED`). The postflight briefing's +"PyInstaller build cache" line is left as-is -- Nuitka never creates a `build\\` folder of +its own (its `--remove-output` flag cleans up its own intermediates), so that line stays literally +true regardless of which tool actually built the current EXE: if a `build\` folder exists, it's +PyInstaller's. See Scenario 42 for the argv-passthrough paragraph's own dedicated writeup. --- -### Scenario 33: REQ-014 system-Python consent -- ACCEPT +### Scenario 38: PyInstaller build fails, Tier A fallback ALSO fails (tier exhaustion) -**What's tested:** `self.ux.system.gate.accept` (`tests/selfapps_ux_hardening.ps1`, `real` lane, -real and passing). Part VI's Scenario 21 already showed the DECLINE branch of this same gate (as -the terminal step of a full provider-cascade exhaustion); this scenario completes the pair. +**What's tested:** `self.exe.build.xfail` (`tests/selfapps_pyinstaller_fail.ps1`, real/conda-full +lanes, gating). Three sub-scenarios share one NDJSON row id: `execfail` (the PyInstaller build +command itself fails), `output_vanish` (PyInstaller succeeds, then the output EXE vanishes +immediately -- simulating AV-style post-creation removal), and `execfail_runtimefail` (packaging +fails AND the interpreter fallback that runs next ALSO exits non-zero -- see Scenario 43a for that +one's console text, since it's really a REQ-027 demo). The first two additionally force +`HP_TEST_FORCE_NUITKA_FAIL=1` so the fallback also fails, proving genuine tier exhaustion. -**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane). +#### 38a. `execfail` -- the PyInstaller build command itself fails -When every other REQ-009 provider tier has failed or been declined, the system-Python tier is -still reached by any default, no-flag run -- it is gated solely by the REQ-014 consent prompt -(Scenario 11 in Part III already documents the prompt's own framing text in full), never by an -env-var the user would need to set. On ACCEPT, the bootstrapper proceeds to use whatever Python is -already on the machine, unmanaged and unisolated: +Real CI capture, run `29788624195`, job `88506013028` ("real" lane): ``` -[INFO] REQ-014: System Python consent: user accepted. -[INFO] System fallback using C:\hostedtoolcache\windows\Python\3.12.10\x64\python.exe -[BOOT] REQ-009: Selected Python provider: System Python (degraded). +[INFO] Building standalone executable -- this may take a minute or two... +[TEST] HP_TEST_FORCE_PYINSTALLER_FAIL: simulating PyInstaller build failure. +[INFO] Standard build did not complete; attempting a fallback build (this may take a minute or two). +[TEST] HP_TEST_FORCE_NUITKA_FAIL: simulating fallback build failure. +[ERROR] PyInstaller execution failed. +[DEBUG] warnfix: warn file not found +[INFO] PyInstaller build artifacts cleaned up. +[WARN] EXE smokerun: dist\.exe not found; skipping +[INFO] Running entry script smoke test via uv interpreter. +[INFO] Entry smoke exit=0 +[STATUS] Run Status: SUCCESS (Exit Code: 0) + +[INFO] REQ-018: post-execution checkpoint (interpreter): declined (run footprint stays at one execution). ``` -`~bootstrap.status.json` still reports `state` as the degraded-but-successful `degraded_env` value -(real NDJSON detail: `"state":"degraded_env","exitCode":0`) -- accepting this tier is a genuine, -if suboptimal, path to a working run, not a failure. This is the ONLY REQ-009 tier gated by an -explicit human consent prompt rather than an automatic fallback, precisely because it is the one -tier that touches the user's real, shared Python environment instead of a private/disposable one. +When BOTH the PyInstaller build and the Nuitka fallback fail outright but the interpreter +fallback's own run exits 0 (this trivial stub script does), the final line is +`[STATUS] Run Status: SUCCESS (Exit Code: 0)` and the postflight panel is the plain +"YOUR CODE RAN -- BUT NO STANDALONE .EXE WAS PRODUCED" variant -- see Scenario 43a for the +different, honest "we can't confirm" panel this same tier-exhaustion path shows instead when the +interpreter run ALSO fails. ---- +#### 38b. `output_vanish` -- PyInstaller succeeds, then the EXE disappears immediately -## Part VIII: Additional branches found in a full-file sweep +Real CI capture, same run/job as 2a: -**Scope note:** after the original 5-pass plan completed, a systematic label-by-label sweep of -every one of `run_setup.bat`'s 164 `:label`s (cross-checked against everything already written in -Parts I-VII) turned up four genuine, user-observable gaps -- three straightforward missing -scenarios, and one previously-undocumented, real bug in the bootstrapper's own error messaging, -found via real CI evidence and confirmed against source before being written up. Everything else -checked in the sweep (roughly 150 of the 164 labels) was either already covered, internal -control-flow plumbing with no independently observable behavior of its own (e.g. `:pfb_runapp`, -`:mgc_gi_done`), or a narrow edge case not worth a dedicated scenario (e.g. `:cascade_consent_no_ -choice_exe`, reached only on a Windows image stripped of `choice.exe`). +``` +[INFO] Building standalone executable -- this may take a minute or two... +[TEST] HP_TEST_FORCE_OUTPUT_VANISH: deleting freshly-built EXE to simulate post-creation removal. +[INFO] Standard build did not complete; attempting a fallback build (this may take a minute or two). +[TEST] HP_TEST_FORCE_NUITKA_FAIL: simulating fallback build failure. +[ERROR] PyInstaller did not produce dist\.exe +[DEBUG] warnfix: warn file found +[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. +[INFO] PyInstaller build artifacts cleaned up. +[WARN] EXE smokerun: dist\.exe not found; skipping +[INFO] Running entry script smoke test via uv interpreter. +[INFO] Entry smoke exit=0 +[STATUS] Run Status: SUCCESS (Exit Code: 0) -- [Scenario 34: Interactive entry picker -- multiple `.py` files, no clear winner (REQ-002)](#scenario-34-interactive-entry-picker----multiple-py-files-no-clear-winner-req-002) -- [Scenario 35: Pre-flight syntax-error rejection (REQ-021), and a real bug it exposed](#scenario-35-pre-flight-syntax-error-rejection-req-021-and-a-real-bug-it-exposed) -- [Scenario 36: REQ-007 system-Python build consent, and the resulting no-EXE interpreter path](#scenario-36-req-007-system-python-build-consent-and-the-resulting-no-exe-interpreter-path) -- [Scenario 37: EXE smoke-run diagnostic hints (companion to Scenario 22)](#scenario-37-exe-smoke-run-diagnostic-hints-companion-to-scenario-22) +*** Verification finished -- see the Run Status above. *** +*** You can run your program again now via the interpreter as an extra diagnostic check. *** +[INFO] REQ-018: post-execution checkpoint (interpreter): declined (run footprint stays at one execution). +``` --- -### Scenario 34: Interactive entry picker -- multiple `.py` files, no clear winner (REQ-002) - -**What's tested:** `self.entry.picker` (`tests/selfapps_entry_picker.ps1`, `conda-full` lane, real, -passing). +### Scenario 39: Tier A + hidden-import auto-recovery skip guard -**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708094` (`conda-full` lane). +**What's tested:** `self.exe.tiera.hidden_skip` (`tests/selfapps_nuitka_tiera_hidden_skip.ps1`, uv +lane, non-gating). Forces Tier A to trigger and succeed for real, then has the stub app fabricate +a `ModuleNotFoundError: No module named 'nuitka'` on stderr and exit 1 -- the exact signature that +used to (before this fix) trigger an incorrect PyInstaller rebuild attempt against a Nuitka-built +EXE. -Part III's happy path covers the common single-`.py`-file case; this covers REQ-002's OTHER real -end-user scenario: a folder with several `.py` files where none is named `main.py`/`app.py`/ -`run.py`/`cli.py` and none has a substantive `if __name__ == "__main__":` block (`:determine_entry`'s -own priority ladder, in `tools/find_entry.py`, exhausts every tier and falls back to -`find_entry.py`'s own `AMBIGUOUS_RC` (3) alphabetical pick). Only THEN does `:pick_entry_interactive` -show a real, timed menu: +**Source:** confirmed in real CI run `29877805447`, uv lane, job `88792048278`: +``` +{"details":{"appStdoutFound":true,"noRepairRebuild":true,"successLogged":true,"skipGuardLogged":true,"exeExists":true,"statusState":"ok","bootstrapExit":0,"smokerunNonzeroLogged":true,"attemptLogged":true,"log":"~nuitka_tiera_hidden_skip_bootstrap.log"},"req":"REQ-AV","pass":true,"desc":"AV-Safe Build Path Tier A: hidden-import auto-recovery correctly skips (never rebuilds via PyInstaller) against a Nuitka-built EXE","id":"self.exe.tiera.hidden_skip","lane":"uv"} +``` +The test only dumps a full console log to CI when a scenario fails; since this one passes, the +exact console text below is reconstructed from `run_setup.bat`'s source rather than copied from a +console dump -- the NDJSON row's `skipGuardLogged`/`noRepairRebuild` fields are the test's own +regex-verified confirmation that these exact lines were present/absent in the real captured log: ``` -Multiple Python files detected -- no clear entry point, so please choose one to run: - [1] a_app.py - [2] b_app.py +[INFO] Building standalone executable -- this may take a minute or two... +[TEST] HP_TEST_FORCE_PYINSTALLER_FAIL: simulating PyInstaller build failure. +[INFO] Standard build did not complete; attempting a fallback build (this may take a minute or two). +[INFO] Fallback build succeeded: dist\.exe was produced using the fallback build system. +[INFO] EXE smokerun: testing dist\.exe +[WARN] EXE smokerun: exited 1 (non-zero) +[INFO][HIDDEN_IMPORT] Skipping --hidden-import auto-recovery: dist\.exe was built via the fallback build system (Nuitka), which uses a different missing-import mechanism than PyInstaller's --hidden-import flag. +``` - Tip: to skip this question next time, do any one of these: - 1. Drag a .py file onto run_setup.bat -- drop it on the batch file icon to run - that file directly. It must be in this same folder. - 2. Rename your main script to one of: main.py, app.py, run.py, or cli.py. - 3. Give exactly one script an if __name__ == "__main__": block. - If you do nothing, the alphabetically-first file is used: a_app.py +Without this guard, `run_setup.bat` would instead print +`[REPAIR][HIDDEN_IMPORT] Adding --hidden-import=nuitka; rebuilding EXE (iter 1/3)` here and +attempt a PyInstaller rebuild against a Nuitka-built EXE. -Type a number 1-2, or wait 30s for the default [1]: -``` +--- -(the real capture's own timeout is shrunk to 2s via `HP_TEST_FORCE_PICKER`, the same CI-determinism -technique already used for the timed cascade prompt in Scenario 11/21 -- the default, real-user -window is 30 seconds, per `HP_PICK_T` in source, shown above as written). A real, non-interactive -CI run also can't feed `choice.exe` an actual keystroke, so the captured log shows one extra, -CI-only artifact line right after the prompt (`ERROR: The file is either empty or does not contain -the valid choices.`) before falling through to the timeout default -- a real interactive user typing -a number, or simply waiting, never sees that line. Either way, the resolution is logged: +### Scenario 40: Requirement 9 -- elective "want an optimized build too?" offer + +**What's tested:** `self.optbuild.offer` (`tests/selfapps_optimized_build.ps1`, uv lane, +non-gating), four scenarios sharing one row id. +**Source:** confirmed in real CI run `29877805447`, uv lane, job `88792048278`, all four scenarios +passing: ``` -[INFO] REQ-002: Picker entry selected: a_app.py +{"lane":"uv","details":{"log":"~optbuild_accept_bootstrap.log","statusState":"ok","scenario":"accept","successLogged":true,"promptShown":true,"tmpExeGone":true,"exeExists":true,"bootstrapExit":0,"acceptedLogged":true,"appStillRuns":true},"desc":"AV-Safe Build Path requirement 9 (accept): a real optimized build succeeds, verifies, and is swapped into place","req":"REQ-AV","id":"self.optbuild.offer","pass":true} +{"req":"REQ-AV","lane":"uv","desc":"AV-Safe Build Path requirement 9 (forcefail): a failed optimized build leaves the original PyInstaller EXE completely untouched","id":"self.optbuild.offer","details":{"originalStillRuns":true,"bootstrapExit":0,"log":"~optbuild_forcefail_bootstrap.log","tmpExeGone":true,"promptShown":true,"exeExists":true,"testHookFired":true,"scenario":"forcefail","statusState":"ok","noSuccessMsg":true},"pass":true} +{"req":"REQ-AV","id":"self.optbuild.offer","lane":"uv","pass":true,"details":{"bootstrapExit":0,"acceptedLogged":true,"originalStillRuns":true,"log":"~optbuild_swapfail_bootstrap.log","promptShown":true,"exeExists":true,"tmpExeGone":true,"scenario":"swapfail","statusState":"ok","noSuccessMsg":true,"swapFailLogged":true},"desc":"AV-Safe Build Path requirement 9 (swapfail): a verified optimized build whose final swap fails leaves the original PyInstaller EXE completely untouched and cleans up the leftover temp file"} +{"desc":"AV-Safe Build Path requirement 9 (decline): default/CI path shows the prompt but never attempts a build","lane":"uv","details":{"statusState":"ok","noBuildAttempt":true,"tmpExeGone":true,"scenario":"decline","log":"~optbuild_decline_bootstrap.log","bootstrapExit":0,"exeExists":true,"declinedLogged":true,"promptShown":true},"req":"REQ-AV","id":"self.optbuild.offer","pass":true} ``` -If MORE than 9 candidate files exist, the picker is skipped entirely (no menu can address more than -the `123456789` `choice /C` charset) and the alphabetical pick is kept silently, logged as `[INFO] -REQ-002: candidates exceed picker limit; keeping (alphabetical).` -- `[Extrapolated -Branch]`, cited from `:pick_entry_interactive`, not independently captured in this run (would need a -10th-plus stub `.py` file staged, which no current test does). +#### 40a. `accept` -- a real optimized build succeeds and is swapped in ---- +Real, verbatim console dump (`~selftest_optbuild_accept\~optbuild_accept_bootstrap.log`), with the +"Verifying the built standalone EXE" line updated in place to reflect current source (requirement +3's activity-aware kill plus the quit-prompt hint, both of which shipped after this capture): -### Scenario 35: Pre-flight syntax-error rejection (REQ-021), and a real bug it exposed +``` +[INFO] Building standalone executable -- this may take a minute or two... +The system cannot find the drive specified. +The system cannot find the drive specified. +[INFO] PyInstaller produced dist\.exe +[INFO] warnfix: Platform-specific modules in the list above are expected on Windows: posix, fcntl, grp, pwd, resource, _scproxy, _posixsubprocess, collections.abc, _frozen_importlib_external. These will be filtered out automatically. +[INFO] PyInstaller build artifacts cleaned up. +[INFO] EXE smokerun: testing dist\.exe +[INFO] Running entry script smoke test via packaged EXE. +[WARN] Verifying the built standalone EXE (PyInstaller) now: if it stays completely silent for about 30 seconds it will be force-stopped, but any output (including a prompt waiting on your input) keeps it running as long as needed. If your program is interactive, try answering its prompts through to its own quit/exit option now so we can confirm it exits cleanly. Either way, do not start real work in it yet or any unsaved work will be lost. +[INFO] EXE smokerun: exited 0 (ok) +[INFO] Entry smoke exit=0 +[STATUS] Run Status: SUCCESS (Exit Code: 0) -**What's tested:** `self.preflight.syntax` (`tests/selfapps_preflight.ps1`, `real` lane, real, -passing) for the ordinary case -- its `$pass` gate enforces the REQ-021 message firing, the real -`SyntaxError` detail appearing, `state: error`, and no PyInstaller-build-crash text; "no EXE was -produced" is separately computed and recorded (`noExe` in the row's own `details`, confirmed -`true` in the real capture below) but is NOT itself part of the pass/fail gate. A SEPARATE, -unrelated real capture (`self.embed.fallback.decline`, `tests/selfapps_ux_hardening.ps1`) -accidentally also reaches this code path under total provider exhaustion, which is what exposed -the bug documented below. +*** Verification finished -- see the Run Status above. *** +*** You can run your program again now via the interpreter as an extra diagnostic check. *** +[INFO] REQ-018: post-execution checkpoint (exe): declined (run footprint stays at one execution). -**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane). +*** 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. *** +[INFO] Optimized build: accepted; building now (this may take a minute or two). +[INFO] Optimized build succeeded and verified: dist\.exe now uses the fallback build system. +``` -**The ordinary case**: before ever attempting a doomed PyInstaller build, `:preflight_compile` -byte-compiles the entry file with the SAME parser the interpreter itself uses (`py_compile`, -zero false positives), reporting a genuine `SyntaxError` clearly and stopping before the build: +The "warnfix: Platform-specific modules..." line above is this specific verbatim capture's own +pre-existing wording; current wording for that same line is shown in Scenario 34. + +**The interactive `Build the optimized version now? [Y/N]` prompt line is echoed unconditionally +by design** (same pattern as `:run_postexec_checkpoint`), but does not appear literally in any +CI capture -- CI answers via the `HP_TEST_OPTBUILD_ANSWER` env-var override, not the interactive +`set /p` path, so only the resolution lines (`accepted`/`declined`) show up in these logs. This is +expected (CI is non-interactive by design), not a gap. + +#### 40b. `forcefail` -- accepted, but the build fails; original EXE is left untouched + +Real, verbatim console dump (`~selftest_optbuild_forcefail\~optbuild_forcefail_bootstrap.log`): ``` -*** [ERROR] REQ-021: Your Python program has a syntax error and cannot run. *** -*** File: "app.py" *** +*** 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. *** +[INFO] Optimized build: accepted; building now (this may take a minute or two). +[TEST] HP_TEST_FORCE_OPTBUILD_FAIL: simulating optimized-build failure. +[INFO] REQ-016: Post-flight briefing printed. ``` -followed by the real Python compiler's own traceback (captured to `~preflight.err.txt`, echoed to -both console and log) and a closing `*** Fix the syntax error shown above, then run this batch -again. ***`. `~bootstrap.status.json` correctly reads `state: error`; the dependency install and -environment creation that ran BEFORE this check are left intact (nothing is torn down), so a fixed -file on the next run reuses them. +No further message prints between the forced-fail log line and the (unrelated, always-present) +post-flight briefing -- the subroutine cleans up the temp file and returns silently. This is a +narrower silence than the wording used on a REAL build-failure branch (which explicitly says +"your app is still ready to use as-is" -- see the reactive hint below); a forced-test-hook failure +and a genuine build failure currently give the user different amounts of reassurance for what is, +from their perspective, the same outcome. -**A real bug, found via this exact sweep, confirmed against a real, unrelated capture (not -fabricated for this scenario), and FIXED 2026-08-01 (formerly CLAUDE.md Active Backlog item 14, -now in `docs/agent-closed-backlog.md`).** The capture below is historical, from before the fix, -kept unedited as a real, timestamped log. `:preflight_compile` used to invoke `"%HP_PY%" -m -py_compile "%HP_ENTRY%"` with no check that `HP_PY` was actually a valid, non-empty interpreter -path first. On TOTAL REQ-009 provider-tier exhaustion (every tier fails or is declined), -`:after_env_mode_selection`'s own `HP_PY`-resolved guard -(`call :die "[ERROR] Active Python interpreter not resolved."`) does NOT actually halt the -pipeline -- per this repo's own long-documented `:die` semantics, `exit /b` inside `:die` only -returns from `:die`'s own call frame, so execution fell through and continued for another ~15 log -lines with an EMPTY `HP_PY`, all the way to `:preflight_compile`. There, `"" -m py_compile -"app.py"` was not a Python invocation at all -- it was cmd.exe trying to execute a program -literally named `""`, which produces a CMD.EXE ERROR, not a Python traceback. Because -`:preflight_compile` treated ANY nonzero exit as "syntax error," it reported this as if the user's -own code were broken. Real capture, from a test that deliberately force-fails uv (offline), conda, -the embed tier, and venv, and declines the REQ-014 system-Python prompt -(`tests/~selftest_embed_decline/`'s own sub-bootstrap): +#### 40c. `swapfail` -- verified build, but the final swap step fails; original EXE is left untouched + +Regression test for a real bug: the swap-verification check used to test the DESTINATION file +(which already exists before the move, success or failure alike) instead of the SOURCE (which +should be gone only on success) -- a genuinely failed swap would have been silently misreported +as success. Fixed; console text (expected from source, not yet dumped in a CI console capture +since this scenario has passed on every run so far): ``` -[ERROR] Active Python interpreter not resolved. -Interpreter: -[WARN] Interpreter smoke test failed (continuing). +*** Your app is ready. *** +*** Want to build an optimized version too? ... *** +[INFO] Optimized build: accepted; building now (this may take a minute or two). +[WARN] Optimized build verified successfully but could not be swapped into place; your app is still ready to use as-is. ``` -(pipreqs, dependency install, and the pyvisa check all run to completion afterward, effectively as -no-ops against the broken interpreter, before the entry is finally selected and preflight runs) +#### 40d. `decline` -- default/CI path, prompt shown but nothing built -``` -*** [ERROR] REQ-021: Your Python program has a syntax error and cannot run. *** -*** File: "app.py" *** -'""' is not recognized as an internal or external command, -operable program or batch file. +Real, verbatim console dump (`~selftest_optbuild_decline\~optbuild_decline_bootstrap.log`): -*** Fix the syntax error shown above, then run this batch again. *** +``` +*** 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. *** +[INFO] Optimized build: declined. ``` -That last block was genuinely misleading: `app.py` may have had no syntax problem whatsoever -- the -real cause, printed several screens earlier, was that no Python interpreter was ever found. A real -user was realistically reachable here for genuine (not test-only) reasons: README's own REQ-009 -table already notes that falling through three-plus provider tiers in one run is "almost always one -shared root cause" (no internet, a full disk, or a locked-down managed image), and a user hitting -exactly that plus declining the REQ-014 system-Python consent prompt would reach this identical -path. The status FILE was never affected (`state: error` was always written correctly, since -`:die`'s own state-set already happened before the fall-through) -- only the human-readable console -narrative misdirected a user who read just the last error rather than scrolling back. +#### Reactive-only failure hint (both Tier A and requirement 9's real-build-failure paths) -**Fixed**: `:after_env_mode_selection`'s guard now also sets `HP_NO_INTERPRETER=1` before calling -`:die` (the call-frame-only-return fall-through itself is left as-is -- a deeper refactor of that -mechanism was judged out of scope for this fix). `:preflight_compile` checks this flag first and, -if set, reports the real cause instead of running `py_compile` against an empty interpreter path: +Fires only on a GENUINE Nuitka compiler failure (not the `forcefail` test hook, which bypasses it +entirely). No CI run to date has exercised a real Nuitka compiler failure, so this is sourced from +`run_setup.bat` rather than a console capture: ``` -*** [ERROR] No Python interpreter is available; your program was not run or built. *** -*** This is not a syntax error -- the Python interpreter itself could not be used. *** -*** Either every automatic Python-acquisition method -- uv, conda, a fresh download, *** -*** or a local virtual environment -- failed, usually from no internet connection, a *** -*** full disk, or a locked-down managed machine image -- or a PVW_PYTHON_EXE override *** -*** points at a path that does not run. Scroll up in this window for the specific reason. *** +[WARN] Optimized build did not complete; your app is still ready to use as-is. +[WARN] Hint: if you have Visual Studio 2022 (or newer) with the 'Desktop development with C++' workload installed, this should use it automatically -- no extra setup needed. If not, installing the free Visual Studio Build Tools with that workload can help. ``` -(The message text was later reworded during implementation to avoid a literal `(...)` pair split -across two `echo` lines inside the same parenthesized `if` block -- cmd.exe's block parser counts -parens in echo text too, so a `(` on one line and its `)` on the next silently mis-closed the -block and broke every CI lane reaching this branch in the same run. See -`docs/agent-lessons-learned.md`'s batch-syntax-quirks section and -`docs/agent-closed-backlog.md`'s Item 14 entry for the full trace.) +--- -This also skips the doomed PyInstaller build attempt entirely (`:run_entry_smoke`'s existing -`HP_PREFLIGHT_FAILED` check already short-circuits the build, unchanged by this fix), not just the -misleading message. +## Part IX: CLI interactivity, argv passthrough & honest messaging ---- +Covers `docs/plan-cli-interactive-verification.md` (P0/P1/P2, all shipped): the live-tee +verification redesign that lets an interactive `input()`-driven program's prompts actually reach +the console, the activity-aware 30-second kill, argv passthrough (REQ-026), and the honest +ambiguous-exit messaging panels (REQ-027). -### Scenario 36: REQ-007 system-Python build consent, and the resulting no-EXE interpreter path +### Scenario 41: Interactive verification -- live-tee, activity-aware kill, and the quit-prompt hint -**What's tested:** `self.sysbuild.decline` (`tests/selfapps_sysbuild.ps1`, `real` lane, real, -passing) -- its `$pass` gate enforces that the REQ-007 prompt text appears, the decline is -logged, packaging is skipped with a logged reason, and no EXE exists afterward. The REQ-014 -"use system Python at all" accept step that gets this test INTO system-Python mode in the first -place is the same mechanism Scenario 33's own `self.ux.system.gate.accept` test covers -independently, not re-asserted here. The interpreter-smoke success/status lines quoted below -(`Entry smoke exit=0`, `[STATUS] Run Status: SUCCESS`) are genuinely present in this same real -captured log but are NOT part of this test's own `$pass` gate -- shown here as observed fact from -the real capture, not as something this specific test independently verifies. +**What changed, in one sentence:** before this plan, `:run_exe_smokerun`'s verification launch +force-killed at a hard 30 seconds regardless of output, and captured stdout/stderr only to a file +(never live to the console) -- so a program correctly waiting on its first `input()` prompt looked +identical to a genuinely hung one, and the user watching the window saw nothing until the process +either finished or got killed. -**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane), -`tests/~selftest_sysbuild/`'s own sub-bootstrap -- ONE coherent, continuous real capture covering -the full journey below. +**What's tested (the plumbing):** `self.interactive.stdin.roundtrip` +(`tests/selfapps_interactive_stdin.ps1`, uv lane, non-gating) builds a real PyInstaller EXE from a +multi-round `input()`-driven stub app and pipes a scripted answer sequence into `cmd.exe`'s own +stdin, exercising the full `cmd.exe -> :run_exe_smokerun -> ~exe_smokerun.ps1 -> the built EXE` +chain and asserting each answer lands in the right round via ordering checks on the captured log +-- it proves the plumbing doesn't drop or reorder stdin/stdout, not a live human's own typing +timing (which can't be automated). -Scenario 33 showed REQ-014's "use system Python at all?" consent being accepted. That is not the -only consent gate on this tier: once system Python is actually selected as the provider, a SECOND, -independent consent gate (`:system_build_consent_gate`, REQ-007) asks separately about installing -PyInstaller into that same system Python to build a standalone EXE -- distinct from REQ-014, and -reachable only in `HP_ENV_MODE=system` mode (every other provider tier gets no such prompt, since -none of them touch a shared, uncontrolled Python installation): +**The WARN line a user sees right before the verification launch**, current shipped wording +(source: `run_setup.bat`, `:warn_user_code_launch` -- not a console capture, since CI answers +scripted stdin rather than a human watching the window; Scenario 37 and Scenario 40a above both show +this exact line in situ): ``` -*** The standalone EXE build installs PyInstaller into your system Python. *** -*** This is the same PyInstaller build used for every provider -- not a special path -- and *** -*** its footprint is small and self-contained (it does not pin common libraries), so it is *** -*** unlikely to conflict with your existing packages. *** +[WARN] Verifying the built standalone EXE (PyInstaller) now: if it stays completely silent for about 30 seconds it will be force-stopped, but any output (including a prompt waiting on your input) keeps it running as long as needed. If your program is interactive, try answering its prompts through to its own quit/exit option now so we can confirm it exits cleanly. Either way, do not start real work in it yet or any unsaved work will be lost. ``` -CI auto-declines (same CI-safe pattern as every other consent gate in this file: `HP_TEST_ -SYSBUILD_ANSWER` override checked first, then `HP_CI_LANE` auto-decline, then a real, unbounded -`set /p` for an interactive user). On decline, dependency install and environment setup are left -completely intact -- only the PyInstaller packaging step is skipped: +Three things this one line is doing: +1. **States the actual kill rule truthfully**: the 30-second cap is a classification checkpoint, + not an unconditional deadline -- `Kill()` only fires if the process has stayed COMPLETELY + silent that whole time. Any output (including a bare prompt with no trailing newline, the exact + shape of Python's own `input("...")`) switches to an unbounded wait. +2. **Actively guides the user toward a clean result**: driving an interactive program to its own + quit/exit option during this pass turns an otherwise-ambiguous exit into a genuine, confirmed + `[STATUS] Run Status: SUCCESS` -- this directly reduces how often a real user ever sees either + of Scenario 43's ambiguous-exit panels. +3. **Still warns it's a throwaway pass, not the user's real, saveable session** -- this verification + EXE is never reused; only the file it's already tested is kept for later double-clicks. + +The `hidden_import` recovery loop's own separate, narrower verification check (see +`docs/agent-interconnect.md`'s "Activity-aware EXE-smoke kill" section) deliberately keeps the +OLDER, unconditional 30-second wording -- it's a bounded repair-verification check on an +already-built EXE, not the user's primary run, so it never got the interactive-friendly rewrite. + +### 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): ``` -[INFO] REQ-007: system-Python EXE build consent: declined. -[INFO] REQ-007: system-Python EXE build not consented; skipping PyInstaller packaging. The environment and dependencies are installed; run the app directly via the prepared Python. + 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 "" --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\.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. ``` -With no EXE ever attempted, `:verify_no_exe_interpreter` becomes the sole verification run (this is -also the general no-EXE path reached whenever `dist\.exe` doesn't exist for any reason -- a -declined build here, or PyInstaller being entirely unavailable elsewhere -- not specific to this -consent gate): +**The equivalent guidance on the no-EXE path** (direct-interpreter-invocation form, part of +Scenario 43a's panel below): ``` -[INFO] Running entry script smoke test via system interpreter. -[INFO] Entry smoke exit=0 -[STATUS] Run Status: SUCCESS (Exit Code: 0) -[INFO] REQ-018: post-execution checkpoint (interpreter): declined (run footprint stays at one execution). + Need launch arguments? Add them directly after that command, e.g. + "" "" --input file.csv ``` -The postflight briefing that follows is `:print_no_exe_briefing` (Part VII/CLAUDE.md's REQ-027 P2 -work already documents its honest-messaging design and its `HP_NOEXE_VERIFY_FAILED`-gated caveat -variant) rather than the EXE-focused `:print_postflight_briefing` -- distinct panels for a -genuinely distinct outcome (no packaged deliverable exists, but the app runs fine via the prepared -interpreter). - ---- +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 37: EXE smoke-run diagnostic hints (companion to Scenario 22) +### Scenario 43: Honest ambiguous-exit messaging (REQ-027) -**What's tested:** the `[HINT]` mechanism fires as a byproduct of `selfapps_exedata_fail.ps1` -(`DATA_FILE` hint) and `selfapps_exedyn_fail.ps1`/`selfapps_hidden_import_exhaust.ps1`-family tests -(`HIDDEN_IMPORT` hint), both real/conda-full lanes, real, passing (the hint lines are a bonus -diagnostic these tests emit, not the tests' own primary assertion target). +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. -**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane). +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. -Scenario 22 covers `--hidden-import` auto-recovery reaching its 3-attempt cap without resolving. In -that same failure family -- any EXE that still fails at runtime AFTER hidden-import recovery has -been tried (or was never applicable, e.g. a genuinely missing DATA file, not a missing module) -- -`:exe_smokerun_hints` re-runs the EXE briefly (no timeout needed; these failures exit immediately) -purely to pattern-match its stderr and offer a targeted, actionable hint. Two real captures, from -two different xfail scenarios: +#### 43a. No-EXE path, interpreter also failed -**A missing bundled data file** (real capture, `FileNotFoundError`): +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`: ``` -[HINT][DATA_FILE] Missing data file detected: C:\Users\RUNNER~1\AppData\Local\Temp\_MEI41642\mypkg\data\info.txt -[HINT][DATA_FILE] Consider adding: --add-data C:\Users\RUNNER~1\AppData\Local\Temp\_MEI41642\mypkg\data\info.txt;. -[HINT][RUNTIME_MISMATCH] Standalone EXE behavior differs from the Python runtime (possible PyInstaller packaging issue in the EXE, not your environment or dependencies) -``` +============================================================ + 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. -**A missing module hidden-import recovery couldn't resolve** (real capture, `ModuleNotFoundError`): + RUNNING YOUR APP (without an .exe) -- the most direct option + "" "" + Need launch arguments? Add them directly after that command, e.g. + "" "" --input file.csv -``` -[HINT][HIDDEN_IMPORT] Hidden import likely missing: absent_dynmod_xyz -[HINT][HIDDEN_IMPORT] Consider adding: --hidden-import=absent_dynmod_xyz -[HINT][RUNTIME_MISMATCH] Standalone EXE behavior differs from the Python runtime (possible PyInstaller packaging issue in the EXE, not your environment or dependencies) -``` + 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 "" arg1 arg2 -Both hint types are logged via `:log` (console AND `~setup.log`), so a real user hitting either -failure sees them directly -- not buried in a diagnostic-only file. The `RUNTIME_MISMATCH` hint is -a universal closing line, not specific to the `DATA_FILE` branch -- both real captures above show -it: every path through `:exe_smokerun_hints` (data-file match, module-not-found match, or neither) -falls straight through to it with no `goto`/`exit` skipping it in between, so it always fires -alongside whichever more specific hint (if any) matched, as a general reminder that a frozen EXE's -behavior can differ from the interpreter's for packaging reasons unrelated to environment or -dependencies. A separate, optional machine-readable form exists too (`HINT_JSON=1`, an -undocumented-in-README super-user flag that additionally prints each hint as a compact JSON object -via PowerShell) -- not independently captured in this run. + 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) +============================================================ +``` -### Scenario 38: No `.py` files at all -- the graceful `no_python_files` exit +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). -**What's tested:** `self.empty_repo.msg` (`tests/selftest.ps1`, `real` lane, real, passing). +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. -**Source:** REAL CI CAPTURE, run `30328748330`, job `90179708091` (`real` lane). +#### 43b. Cached-EXE fast path, kept despite a non-zero exit -Referenced throughout this document (e.g. Scenario 9'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: +Fires when the fail-fast probe classifies a REUSED `dist\.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`: ``` -[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. +============================================================ + SETUP COMPLETE -- BUT WE CAN'T CONFIRM YOUR LAST RUN WORKED +============================================================ + Your existing standalone application was reused (dist\.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\.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\.exe and run this bootstrapper again. +============================================================ ``` -(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. +This panel is a PLAIN INFORMATIONAL PRINT, never a consent gate -- the cached-EXE fast path is +deliberately zero-friction for prompts (see `docs/agent-interconnect.md`'s "Fast path = ZERO +friction" design requirement), 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. + +--- diff --git a/run_setup.bat b/run_setup.bat index 1dec41f6..9a4da7b2 100644 --- a/run_setup.bat +++ b/run_setup.bat @@ -2642,6 +2642,11 @@ for /f "usebackq delims=" %%F in ("~entry.menu") do set /a HP_PICK_N+=1 if %HP_PICK_N% LSS 2 ( del "~entry.menu" >nul 2>&1 & exit /b 0 ) if %HP_PICK_N% GTR 9 ( call :log "[INFO] REQ-002: %HP_PICK_N% candidates exceed picker limit; keeping %HP_ENTRY% (alphabetical)." + echo Tip: to skip this question next time, do any one of these: + echo 1. Drag a .py file onto run_setup.bat -- drop it on the batch file icon to run + echo that file directly. It must be in this same folder. + echo 2. Rename your main script to one of: main.py, app.py, run.py, or cli.py. + echo 3. Give exactly one script an if __name__ == "__main__": block. del "~entry.menu" >nul 2>&1 exit /b 0 ) @@ -4152,7 +4157,7 @@ set "HP_EMBED_EXTRACT=IyBSRVEtMDA5IFRpZXIgNTogdmVyaWZpZXMgY2hlY2tzdW0sIGV4dHJhY3 set "HP_EMBED_PYVER_CHECK=IyBSRVEtMDA5IFRpZXIgNSwgUHl0aG9uIHN0YWdlOiBydW5zIHVuZGVyIHRoZSAiYWx3YXlzIGxhdGVzdCIgaW50ZXJwcmV0ZXIgfmVtYmVkX2V4dHJhY3QucHMxCiMgKFBvd2VyU2hlbGwgc3RhZ2UpIGFscmVhZHkgZG93bmxvYWRlZC92ZXJpZmllZC9leHRyYWN0ZWQuIFRoaXMgaXMgdGhlIE9OTFkgcGxhY2UgcGVyLXJlcXVlc3QKIyB2ZXJzaW9uIGxvZ2ljIGxpdmVzIC0tIGRlbGliZXJhdGVseSBQeXRob24sIG5vdCBQb3dlclNoZWxsLCByZXVzaW5nIHRoaXMgY29kZWJhc2UncyBwcm92ZW4KIyB2ZXJzaW9uLWRldGVjdGlvbiBwYXR0ZXJuIGluc3RlYWQgb2YgcmUtZGVyaXZpbmcgaXQgaW4gUG93ZXJTaGVsbC4gRnVsbCByYXRpb25hbGU6CiMgZG9jcy9hZ2VudC1pbnRlcmNvbm5lY3QubWQgIlN0YW5kYWxvbmUgUHl0aG9uLWRvd25sb2FkIHRpZXIiLiAiMy4xNCIgZW50cnkgYmVsb3cgTVVTVCBtYXRjaAojIEhQX0VNQkVEX0xBVEVTVF9QQVRDSC9IUF9FTUJFRF9MQVRFU1RfU0hBMjU2IGluIHJ1bl9zZXR1cC5iYXQgLS0gYSBQYXlsb2FkU3luYy1zdHlsZSB1bml0IHRlc3QKIyBhc3NlcnRzIHRoaXMuIExhc3QgcmVmcmVzaGVkOiAyMDI2LTA3LTA5LgppbXBvcnQgaGFzaGxpYgppbXBvcnQgb3MKaW1wb3J0IHJlCmltcG9ydCBzaHV0aWwKaW1wb3J0IHNvY2tldAppbXBvcnQgc3lzCmltcG9ydCB1cmxsaWIucmVxdWVzdAppbXBvcnQgemlwZmlsZQoKIyBkZXJpdmVkIHJlcXVpcmVtZW50OiB1cmxsaWIucmVxdWVzdC51cmxyZXRyaWV2ZSBoYXMgbm8gdGltZW91dD0gcGFyYW1ldGVyICh2ZXJpZmllZCB2aWEKIyBpbnNwZWN0LnNpZ25hdHVyZSAtLSBwYXNzaW5nIG9uZSByYWlzZXMgVHlwZUVycm9yKSwgc28gYSBzdGFsbGVkIChub3QgcmVmdXNlZCkgY29ubmVjdGlvbgojIGR1cmluZyBkb3dubG9hZF9hbmRfdmVyaWZ5KCkgd291bGQgb3RoZXJ3aXNlIGhhbmcgdGhpcyBvbmUtc2hvdCBzY3JpcHQgZm9yZXZlci4gQSBnbG9iYWwKIyBkZWZhdWx0IHRpbWVvdXQgaXMgc2FmZSBoZXJlIHNpbmNlIHRoZSB3aG9sZSBzY3JpcHQgZXhpdHMgaW1tZWRpYXRlbHkgYWZ0ZXIgdXNlIC0tIG5vdGhpbmcKIyBlbHNlIGluIHRoaXMgc2hvcnQtbGl2ZWQgcHJvY2VzcyBpcyBhZmZlY3RlZC4gTWlycm9ycyB0aGUgY3VybCAtLW1heC10aW1lIDEyMCBhbHJlYWR5IHVzZWQKIyBmb3IgdGhlIFBvd2VyU2hlbGwtc3RhZ2UgZG93bmxvYWQgb2YgdGhlIHNhbWUgemlwIGZhbWlseS4Kc29ja2V0LnNldGRlZmF1bHR0aW1lb3V0KDEyMCkKCiMgbWlub3IgLT4gKHBhdGNoLCBzaGEyNTYpCkVNQkVEX1BZVEhPTl9UQUJMRSA9IHsKICAgICIzLjEwIjogKCIzLjEwLjExIiwgIjYwODYxOWY4NjE5MDc1NjI5YzljNjlmMzYxMzUyYTBkYTZlZDdlNjJmODNhMGUxOWM2M2UwZWEzMmViNzYyOWQiKSwKICAgICIzLjExIjogKCIzLjExLjkiLCAiMDA5ZDZiZjdlM2IyZGRjYTNkNzg0ZmEwOWY5MGZlNTQzMzZkNWI2MGYwZTBmMzA1YzM3ZjQwMGJmODNjZmQzYiIpLAogICAgIjMuMTIiOiAoIjMuMTIuMTAiLCAiNGFjYmVkNmRkMWM3NDRiMDM3NmUzYjFjZjU3Y2U5MDZmOWRjOWU5NWU2ODgyNDU4NGM4MDk5YTYzMDI1YTNjMyIpLAogICAgIjMuMTMiOiAoIjMuMTMuMTQiLCAiOTBiNGU1Yjk4OThiNzJkNzQ0NjUwNTI0YmZmOTIzNzdjMzY3ZjQ0YmQ1ZmJkMDllMzE0ODY1NmMwODBhZDkwNyIpLAogICAgIjMuMTQiOiAoIjMuMTQuNiIsICJkZjkwMWU4NGE4OTZmZjFlZTcyMGFkMDMzNzdlMGM4ZDhjMjI0NGZkYTc5ODA4YWVlYWZmNjMxNmRmMWNiNzVjIiksCn0KTEFURVNUX01JTk9SID0gIjMuMTQiCkZMT09SX01JTk9SID0gIjMuMTAiCgpTUEVDX01JTk9SX1JFID0gcmUuY29tcGlsZShyIihbMC05XStcLlswLTldKykiKQoKCmRlZiBfbWlub3Jfa2V5KG1pbm9yKToKICAgIHRyeToKICAgICAgICBtYWpvciwgc3ViID0gbWlub3Iuc3BsaXQoIi4iKQogICAgICAgIHJldHVybiAoaW50KG1ham9yKSwgaW50KHN1YikpCiAgICBleGNlcHQgKFZhbHVlRXJyb3IsIEF0dHJpYnV0ZUVycm9yKToKICAgICAgICByZXR1cm4gKDAsIDApCgoKZGVmIHJlc29sdmVfcmVxdWVzdGVkX21pbm9yKHB5c3BlYyk6CiAgICAjIEV4dHJhY3RzICJYLlkiIGZyb20gYSBQWVNQRUMgc3RyaW5nIChlLmcuICJweXRob24+PTMuMTAsPDQuMCIpOyBOb25lIGlmIGVtcHR5L3VucGFyc2VhYmxlLgogICAgaWYgbm90IHB5c3BlYzoKICAgICAgICByZXR1cm4gTm9uZQogICAgbWF0Y2ggPSBTUEVDX01JTk9SX1JFLnNlYXJjaChweXNwZWMpCiAgICByZXR1cm4gbWF0Y2guZ3JvdXAoMSkgaWYgbWF0Y2ggZWxzZSBOb25lCgoKZGVmIHJlc29sdmVfdGFibGVfZW50cnkocmVxdWVzdGVkX21pbm9yKToKICAgICMgUmV0dXJucyAobWlub3IsIHBhdGNoLCBzaGEyNTYsIGZlbGxfYmFjayk7IG1pcnJvcnMgdGhlIFBvd2VyU2hlbGwgc3RhZ2UncyBvd24gcnVsZXMuCiAgICBpZiByZXF1ZXN0ZWRfbWlub3IgaW4gRU1CRURfUFlUSE9OX1RBQkxFOgogICAgICAgIHBhdGNoLCBzaGEyNTYgPSBFTUJFRF9QWVRIT05fVEFCTEVbcmVxdWVzdGVkX21pbm9yXQogICAgICAgIHJldHVybiByZXF1ZXN0ZWRfbWlub3IsIHBhdGNoLCBzaGEyNTYsIEZhbHNlCiAgICBtaW5vciA9IEZMT09SX01JTk9SIGlmIF9taW5vcl9rZXkocmVxdWVzdGVkX21pbm9yKSA8IF9taW5vcl9rZXkoRkxPT1JfTUlOT1IpIGVsc2UgTEFURVNUX01JTk9SCiAgICBwYXRjaCwgc2hhMjU2ID0gRU1CRURfUFlUSE9OX1RBQkxFW21pbm9yXQogICAgcmV0dXJuIG1pbm9yLCBwYXRjaCwgc2hhMjU2LCBUcnVlCgoKZGVmIGRvd25sb2FkX2FuZF92ZXJpZnkodXJsLCBleHBlY3RlZF9zaGEyNTYsIGRlc3RfemlwKToKICAgIHVybGxpYi5yZXF1ZXN0LnVybHJldHJpZXZlKHVybCwgZGVzdF96aXApCiAgICBkaWdlc3QgPSBoYXNobGliLnNoYTI1NigpCiAgICB3aXRoIG9wZW4oZGVzdF96aXAsICJyYiIpIGFzIGZoOgogICAgICAgIGZvciBjaHVuayBpbiBpdGVyKGxhbWJkYTogZmgucmVhZCgxIDw8IDIwKSwgYiIiKToKICAgICAgICAgICAgZGlnZXN0LnVwZGF0ZShjaHVuaykKICAgIGFjdHVhbCA9IGRpZ2VzdC5oZXhkaWdlc3QoKS5sb3dlcigpCiAgICBpZiBhY3R1YWwgIT0gZXhwZWN0ZWRfc2hhMjU2Lmxvd2VyKCk6CiAgICAgICAgb3MucmVtb3ZlKGRlc3RfemlwKQogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoImNoZWNrc3VtIG1pc21hdGNoOiBleHBlY3RlZCB7fSwgZ290IHt9Ii5mb3JtYXQoZXhwZWN0ZWRfc2hhMjU2LCBhY3R1YWwpKQoKCmRlZiBleHRyYWN0X2FuZF9wYXRjaCh6aXBfcGF0aCwgZGVzdF9kaXIpOgogICAgaWYgb3MucGF0aC5pc2RpcihkZXN0X2Rpcik6CiAgICAgICAgc2h1dGlsLnJtdHJlZShkZXN0X2RpcikKICAgIHdpdGggemlwZmlsZS5aaXBGaWxlKHppcF9wYXRoKSBhcyB6ZjoKICAgICAgICB6Zi5leHRyYWN0YWxsKGRlc3RfZGlyKQogICAgcHRoX2ZpbGVzID0gW2YgZm9yIGYgaW4gb3MubGlzdGRpcihkZXN0X2RpcikgaWYgcmUubWF0Y2gociJecHl0aG9uXGQrXC5fcHRoJCIsIGYpXQogICAgaWYgbm90IHB0aF9maWxlczoKICAgICAgICByYWlzZSBGaWxlTm90Rm91bmRFcnJvcigibm8gcHl0aG9uKi5fcHRoIGZpbGUgZm91bmQgYWZ0ZXIgZXh0cmFjdGlvbiIpCiAgICBwdGhfcGF0aCA9IG9zLnBhdGguam9pbihkZXN0X2RpciwgcHRoX2ZpbGVzWzBdKQogICAgd2l0aCBvcGVuKHB0aF9wYXRoLCAiciIsIGVuY29kaW5nPSJhc2NpaSIpIGFzIGZoOgogICAgICAgIGNvbnRlbnQgPSBmaC5yZWFkKCkKICAgIGNvbnRlbnQgPSByZS5zdWIociIoP20pXiNpbXBvcnQgc2l0ZSQiLCAiaW1wb3J0IHNpdGUiLCBjb250ZW50KQogICAgd2l0aCBvcGVuKHB0aF9wYXRoLCAidyIsIGVuY29kaW5nPSJhc2NpaSIsIG5ld2xpbmU9IiIpIGFzIGZoOgogICAgICAgIGZoLndyaXRlKGNvbnRlbnQpCiAgICBweV9leGUgPSBvcy5wYXRoLmpvaW4oZGVzdF9kaXIsICJweXRob24uZXhlIikKICAgIGlmIG5vdCBvcy5wYXRoLmlzZmlsZShweV9leGUpOgogICAgICAgIHJhaXNlIEZpbGVOb3RGb3VuZEVycm9yKCJweXRob24uZXhlIG1pc3NpbmcgYWZ0ZXIgZXh0cmFjdGlvbiIpCiAgICByZXR1cm4gcHlfZXhlCgoKZGVmIG1haW4oKToKICAgICMgZGVzdF9kaXIgaXMgd2hlcmUgVEhJUyBydW5uaW5nIGludGVycHJldGVyIGxpdmVzOyBXaW5kb3dzIHdvbid0IGxldCBhIHByb2Nlc3MgcmVwbGFjZSBpdHMKICAgICMgb3duIGZpbGVzLCBzbyBhIHN3YXAgZXh0cmFjdHMgaW50byBhIHNpYmxpbmcgX3N3YXAgZGlyIGFuZCBiYXRjaCBtb3ZlcyBpdCBpbnRvIHBsYWNlIG9ubHkKICAgICMgYWZ0ZXIgdGhpcyBwcm9jZXNzIGV4aXRzIChsb2NrcyByZWxlYXNlZCkuIFNlZSBkb2NzL2FnZW50LWludGVyY29ubmVjdC5tZC4KICAgIGRlc3RfZGlyID0gc3lzLmFyZ3ZbMV0gaWYgbGVuKHN5cy5hcmd2KSA+IDEgZWxzZSAiIgogICAgc3dhcF9kaXIgPSBkZXN0X2Rpci5yc3RyaXAoIlxcLyIpICsgIl9zd2FwIgogICAgcHlzcGVjID0gb3MuZW52aXJvbi5nZXQoIlBZU1BFQyIsICIiKQogICAgcmVxdWVzdGVkX21pbm9yID0gcmVzb2x2ZV9yZXF1ZXN0ZWRfbWlub3IocHlzcGVjKQoKICAgIGlmIHJlcXVlc3RlZF9taW5vciBpcyBOb25lIG9yIHJlcXVlc3RlZF9taW5vciA9PSBMQVRFU1RfTUlOT1I6CiAgICAgICAgc3lzLnN0ZG91dC53cml0ZSgidW5jaGFuZ2VkfHt9XG4iLmZvcm1hdChMQVRFU1RfTUlOT1IpKQogICAgICAgIHJldHVybiAwCgogICAgbWlub3IsIHBhdGNoLCBzaGEyNTYsIGZlbGxfYmFjayA9IHJlc29sdmVfdGFibGVfZW50cnkocmVxdWVzdGVkX21pbm9yKQogICAgaWYgbWlub3IgPT0gTEFURVNUX01JTk9SOgogICAgICAgICMgQWJvdmUtY2VpbGluZyByZXF1ZXN0IChvbmx5IHBhdGggaGVyZSwgc2luY2UgZXhhY3QtbWF0Y2gtbGF0ZXN0IGlzIGhhbmRsZWQgYWJvdmUpOgogICAgICAgICMgbm8gc3dhcCBuZWVkZWQsIGJ1dCB0YWcgImZlbGxiYWNrIiBub3QgInVuY2hhbmdlZCIgc28gdGhlIGNhbGxlcidzIFdBUk4gc3RpbGwgZmlyZXMuCiAgICAgICAgc3lzLnN0ZG91dC53cml0ZSgiZmVsbGJhY2t8e31cbiIuZm9ybWF0KG1pbm9yKSkKICAgICAgICByZXR1cm4gMAoKICAgIHVybCA9ICJodHRwczovL3d3dy5weXRob24ub3JnL2Z0cC9weXRob24ve3B9L3B5dGhvbi17cH0tZW1iZWQtYW1kNjQuemlwIi5mb3JtYXQocD1wYXRjaCkKICAgIHppcF9wYXRoID0gb3MucGF0aC5qb2luKG9zLmVudmlyb24uZ2V0KCJURU1QIiwgIi4iKSwgInB5dGhvbi17fS1lbWJlZC1hbWQ2NC56aXAiLmZvcm1hdChwYXRjaCkpCiAgICB0cnk6CiAgICAgICAgZG93bmxvYWRfYW5kX3ZlcmlmeSh1cmwsIHNoYTI1NiwgemlwX3BhdGgpCiAgICAgICAgZXh0cmFjdF9hbmRfcGF0Y2goemlwX3BhdGgsIHN3YXBfZGlyKQogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBleGM6CiAgICAgICAgc3lzLnN0ZGVyci53cml0ZSgiZW1iZWQgdmVyc2lvbiBzd2FwIGZhaWxlZDoge31cbiIuZm9ybWF0KGV4YykpCiAgICAgICAgaWYgb3MucGF0aC5pc2Rpcihzd2FwX2Rpcik6CiAgICAgICAgICAgIHNodXRpbC5ybXRyZWUoc3dhcF9kaXIsIGlnbm9yZV9lcnJvcnM9VHJ1ZSkKICAgICAgICByZXR1cm4gMQoKICAgIHRhZyA9ICJmZWxsYmFjayIgaWYgZmVsbF9iYWNrIGVsc2UgInN3YXBwZWQiCiAgICBzeXMuc3Rkb3V0LndyaXRlKCJ7fXx7fXx7fVxuIi5mb3JtYXQodGFnLCBtaW5vciwgc3dhcF9kaXIpKQogICAgcmV0dXJuIDAKCgppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgc3lzLmV4aXQobWFpbigpKQo=" set "HP_FAILFAST_PROBE=IyBTbGljZSAyYi1DIGZhaWwtZmFzdCBwcm9iZTogbGF1bmNoZXMgdGhlIGNhbGxlcidzIHByb2dyYW0sIHdhaXRzIHVwIHRvIEhQX0ZBSUxGQVNUX1BST0JFX01TIHRvCiMgY2xhc3NpZnkgaXQgYXMgImV4aXRlZCBmYXN0IiAoc3RhbGUvYnJva2VuIGNhY2hlZCBhcnRpZmFjdCAtLSBkaXNjYXJkK3JlYnVpbGQgY2FuZGlkYXRlKSB2cy4KIyAic3RpbGwgcnVubmluZyIgKHRoZSB1c2VyJ3MgcmVhbCwgcG9zc2libHkgbG9uZy1ydW5uaW5nIHByb2dyYW0gLS0gbmV2ZXIgdG91Y2hlZCBhZ2FpbikuIE5ldmVyCiMgY2FsbHMgJHAuS2lsbCgpIC0tIHBhc3QgdGhlIHByb2JlIHdpbmRvdyB0aGUgd2FpdCBpcyB1bmJvdW5kZWQgc28gYSBoZWFsdGh5IGFwcCBpcyBuZXZlcgojIGZvcmNlLXN0b3BwZWQuCiMKIyBJbnB1dHMgdmlhIGVudiB2YXJzIChhdm9pZHMgY21kLmV4ZSBxdW90aW5nIGhhemFyZHMpOiBIUF9QUk9CRV9FWEUsIEhQX1BST0JFX0FSR1MsIEhQX1BST0JFX0NXRCwKIyBIUF9GQUlMRkFTVF9QUk9CRV9NUywgSFBfUFJPQkVfT1VUL0hQX1BST0JFX0VSUiAoZGVmYXVsdCB+cnVuLm91dC50eHQvfnJ1bi5lcnIudHh0KSwKIyBIUF9QUk9CRV9SRVNVTFQgKGRlZmF1bHQgfnByb2JlX3Jlc3VsdC50eHQgLS0gIiRleGNlZWRlZHwkZXhpdGNvZGUiLCBOT1Qgc3Rkb3V0KS4gQ2FsbGVyIG11c3QKIyBwcmUtdHJ1bmNhdGUgb3V0cHV0L3Jlc3VsdCBmaWxlcyBiZWZvcmUgaW52b2tpbmcuCiMKIyBkZXJpdmVkIHJlcXVpcmVtZW50IChbUkVRLTAyNl0gYXJndiBwYXNzdGhyb3VnaCk6IEhQX1BST0JFX0FSR1MgaXMgbm93IGEgZnVsbCwgYWxyZWFkeS1xdW90ZWQKIyBBcmd1bWVudHMgc3RyaW5nIChlLmcuIGAiZW50cnkucHkiICItLWZvbyIgImJhciJgKSwgdXNlZCB2ZXJiYXRpbSAtLSBub3QgYSBzaW5nbGUgYmFyZSBwYXRoCiMgcmUtcXVvdGVkIGhlcmUuIENhbGxlciBxdW90ZXMgZWFjaCB0b2tlbjsgc2VlIHJ1bl9zZXR1cC5iYXQncyBIUF9BUFBfQVJHUy4KIwojIExpdmUtdGVlcyB0aGUgY2hpbGQncyBzdGRvdXQvc3RkZXJyIHNvIGEgc3RkaW4taW50ZXJhY3RpdmUgcHJvZ3JhbSdzIHByb21wdHMgcmVhY2ggYSByZWFsCiMgZG91YmxlLWNsaWNrZWQgdXNlciwgaW5zdGVhZCBvZiBvbmx5IHdyaXRpbmcgY2FwdHVyZWQgb3V0cHV0IHRvIGRpc2sgYXQgZXhpdC4KIwojIGRlcml2ZWQgcmVxdWlyZW1lbnQ6IGRvZXMgTk9UIHVzZSBSZWdpc3Rlci1PYmplY3RFdmVudCBvbiBPdXRwdXREYXRhUmVjZWl2ZWQvRXJyb3JEYXRhUmVjZWl2ZWQuCiMgVGhhdCB3YXMgdGhlIG9yaWdpbmFsIGRlc2lnbiBhbmQgd2FzIGZvdW5kLCB2aWEgdGhpcyByZXBvJ3Mgb3duIGxvY2FsIHB3c2ggdGVzdGluZywgdG8KIyByZW9yZGVyIGxpbmVzIFdJVEhJTiBhIHNpbmdsZSBzdHJlYW0gKGUuZy4gcm91bmQgMidzIG91dHB1dCBsYW5kaW5nIGJlZm9yZSByb3VuZCAxJ3MgaW4gdGhlCiMgY2FwdHVyZWQvdGVlZCB0ZXh0LCBub24tZGV0ZXJtaW5pc3RpY2FsbHkpIC0tIHJvb3QtY2F1c2VkIHRvIGEgY29uZmlybWVkLCBmaWxlZCBQb3dlclNoZWxsIGJ1ZwojIChQb3dlclNoZWxsL1Bvd2VyU2hlbGwjMTE5MzcpOiB0aG9zZSBldmVudHMgZGlzcGF0Y2ggdmlhIFRocmVhZFBvb2wuUXVldWVVc2VyV29ya0l0ZW0sIHdoaWNoCiMgZG9lcyBub3QgZ3VhcmFudGVlIGRlbGl2ZXJ5IG9yZGVyIHdoZW4gc2V2ZXJhbCBsaW5lcyBhcnJpdmUgY2xvc2UgdG9nZXRoZXIuIEZpeGVkIGJ5IHBvbGxpbmcKIyByZWFkcyBkaXJlY3RseSBpbnN0ZWFkOiBvbmx5IE9ORSByZWFkIGlzIGV2ZXIgaW4gZmxpZ2h0IHBlciBzdHJlYW0gYXQgYSB0aW1lICh0aGUgbmV4dCByZWFkIGlzCiMgbm90IGlzc3VlZCB1bnRpbCB0aGUgY3VycmVudCBvbmUgaXMgY29uc3VtZWQpLCBzbyB0aGVyZSBpcyBubyBwb3NzaWJsZSBvdXQtb2Ytb3JkZXIgZGVsaXZlcnkKIyBmb3IgYSBzaW5nbGUgc3RyZWFtIC0tIG9yZGVyaW5nIGlzIHNlbGYtc2VxdWVuY2VkLCBub3QgZGVwZW5kZW50IG9uIGFueSBydW50aW1lJ3MKIyBjYWxsYmFjay1zY2hlZHVsaW5nIGd1YXJhbnRlZS4gQ3Jvc3Mtc3RyZWFtIChzdGRvdXQgdnMgc3RkZXJyKSBpbnRlcmxlYXZpbmcgd2FzIG5ldmVyCiMgZ3VhcmFudGVlZCBhbmQgc3RpbGwgaXNuJ3QgLS0gdGhhdCByZWZsZWN0cyB0aGUgY2hpbGQncyBvd24gdHdvIGluZGVwZW5kZW50IHBpcGVzLCBub3QgYSBidWcuCiMKIyBkZXJpdmVkIHJlcXVpcmVtZW50IChGaW5kaW5nIDksIDIwMjYtMDctMjQpOiByZWFkcyB2aWEgU3RyZWFtUmVhZGVyLlJlYWRBc3luYyhjaGFyW10sIGludCwgaW50KQojIChyYXcgY2h1bmtzKSwgTk9UIFJlYWRMaW5lQXN5bmMoKS4gUmVhZExpbmVBc3luYygpIG9ubHkgcmV0dXJucyBvbmNlIGl0IHNlZXMgYSBmdWxsCiMgbmV3bGluZS10ZXJtaW5hdGVkIGxpbmUgLS0gY29uZmlybWVkIGVtcGlyaWNhbGx5IHRoYXQgUHl0aG9uJ3Mgb3duIGBpbnB1dCgicHJvbXB0IilgIChubwojIHRyYWlsaW5nIG5ld2xpbmUgYnkgZGVzaWduLCBzbyB0aGUgY3Vyc29yIHN0YXlzIG9uIHRoZSBzYW1lIGxpbmUpIGlzIGdlbnVpbmVseSBmbHVzaGVkIHRvIHRoZQojIE9TIHBpcGUgaW1tZWRpYXRlbHkgYnV0IHN0YXlzIGludmlzaWJsZSB0byBhIFJlYWRMaW5lQXN5bmMtYmFzZWQgcmVhZGVyIHVudGlsIHNvbWV0aGluZyBlbHNlCiMgbGF0ZXIgZmx1c2hlcyBhIG5ld2xpbmUsIG9yIHRoZSBwcm9jZXNzIGV4aXRzLiBBIGNodW5rLWJhc2VkIHJlYWQgc3VyZmFjZXMgd2hhdGV2ZXIgYnl0ZXMgYXJlCiMgYWN0dWFsbHkgYXZhaWxhYmxlIHRoZSBtb21lbnQgdGhleSBhcnJpdmUsIG1hdGNoaW5nIGhvdyBhIHJlYWwgdGVybWluYWwgYmVoYXZlcy4gRU9GIGlzIGEKIyAwLWxlbmd0aCByZWFkIChub3QgYSBudWxsIHJlc3VsdCB0aGUgd2F5IFJlYWRMaW5lQXN5bmMgc2lnbmFscyBpdCkuIFNlZQojIGRvY3MvcGxhbi1jbGktaW50ZXJhY3RpdmUtdmVyaWZpY2F0aW9uLm1kIEZpbmRpbmcgOSBmb3IgdGhlIGZ1bGwgZW1waXJpY2FsIHRyYWNlLgojCiMgRnVsbCByYXRpb25hbGUgKyBjaXRhdGlvbnM6IGRvY3MvcGxhbi1jbGktaW50ZXJhY3RpdmUtdmVyaWZpY2F0aW9uLm1kIEZpbmRpbmdzIDViLzYvNy84LzkuCiMKIyBUaGlzIGlzIHRoZSBjYW5vbmljYWwgc291cmNlIGZvciB0aGUgSFBfRkFJTEZBU1RfUFJPQkUgYmFzZTY0IHBheWxvYWQgZW1iZWRkZWQgaW4KIyBydW5fc2V0dXAuYmF0LiBBZnRlciBlZGl0aW5nLCByZS1lbmNvZGUgYW5kIHBhc3RlIGl0IGludG8gdGhlIGBzZXQgIkhQX0ZBSUxGQVNUX1BST0JFPS4uLiJgCiMgbGluZTsgdGVzdHMvdGVzdF9mYWlsZmFzdF9wcm9iZS5weSBhc3NlcnRzIHRoZSBlbWJlZGRlZCBwYXlsb2FkIG1hdGNoZXMgdGhpcyBmaWxlICh3aXRoCiMgQ1JMRi9MRiBub3JtYWxpemVkLCBwZXIgdGhlIC5wczEgUGF5bG9hZFN5bmMgY29udmVudGlvbiAtLSBzZWUKIyBkb2NzL2FnZW50LWxlc3NvbnMtbGVhcm5lZC5tZCAiRW1iZWRkZWQgSGVscGVyIFVwZGF0ZSBXb3JrZmxvdyIpLgokZXhlID0gJGVudjpIUF9QUk9CRV9FWEUKJHJhd0FyZ3MgPSAkZW52OkhQX1BST0JFX0FSR1MKJHdvcmtEaXIgPSAkZW52OkhQX1BST0JFX0NXRAokcHJvYmVNcyA9IFtpbnRdJGVudjpIUF9GQUlMRkFTVF9QUk9CRV9NUwokb3V0UGF0aCA9ICRlbnY6SFBfUFJPQkVfT1VUCmlmICgtbm90ICRvdXRQYXRoKSB7ICRvdXRQYXRoID0gJ35ydW4ub3V0LnR4dCcgfQokZXJyUGF0aCA9ICRlbnY6SFBfUFJPQkVfRVJSCmlmICgtbm90ICRlcnJQYXRoKSB7ICRlcnJQYXRoID0gJ35ydW4uZXJyLnR4dCcgfQokcmVzdWx0UGF0aCA9ICRlbnY6SFBfUFJPQkVfUkVTVUxUCmlmICgtbm90ICRyZXN1bHRQYXRoKSB7ICRyZXN1bHRQYXRoID0gJ35wcm9iZV9yZXN1bHQudHh0JyB9Cgokc2kgPSBOZXctT2JqZWN0IFN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzU3RhcnRJbmZvCiRzaS5GaWxlTmFtZSA9ICRleGUKaWYgKCRyYXdBcmdzKSB7ICRzaS5Bcmd1bWVudHMgPSAkcmF3QXJncyB9CiRzaS5Xb3JraW5nRGlyZWN0b3J5ID0gJHdvcmtEaXIKJHNpLlVzZVNoZWxsRXhlY3V0ZSA9ICRmYWxzZQokc2kuUmVkaXJlY3RTdGFuZGFyZE91dHB1dCA9ICR0cnVlCiRzaS5SZWRpcmVjdFN0YW5kYXJkRXJyb3IgPSAkdHJ1ZQokcCA9IE5ldy1PYmplY3QgU3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3MKJHAuU3RhcnRJbmZvID0gJHNpCiRwLlN0YXJ0KCkgfCBPdXQtTnVsbApXcml0ZS1Ib3N0ICJbSU5GT10gUHJvY2VzcyBJRCAkKCRwLklkKS4gSWYgaXQgc2VlbXMgc3R1Y2s6IFRhc2sgTWFuYWdlciA+IERldGFpbHMgdGFiID4gZmluZCB0aGlzIFBJRCA+IEVuZCBUYXNrICh0aGlzIHdpbmRvdyBzdGF5cyBvcGVuKS4iCgokb3V0QnVmID0gTmV3LU9iamVjdCBTeXN0ZW0uVGV4dC5TdHJpbmdCdWlsZGVyCiRlcnJCdWYgPSBOZXctT2JqZWN0IFN5c3RlbS5UZXh0LlN0cmluZ0J1aWxkZXIKJG91dENodW5rQnVmID0gTmV3LU9iamVjdCBjaGFyW10gNDA5NgokZXJyQ2h1bmtCdWYgPSBOZXctT2JqZWN0IGNoYXJbXSA0MDk2CiRvdXRUYXNrID0gJHAuU3RhbmRhcmRPdXRwdXQuUmVhZEFzeW5jKCRvdXRDaHVua0J1ZiwgMCwgJG91dENodW5rQnVmLkxlbmd0aCkKJGVyclRhc2sgPSAkcC5TdGFuZGFyZEVycm9yLlJlYWRBc3luYygkZXJyQ2h1bmtCdWYsIDAsICRlcnJDaHVua0J1Zi5MZW5ndGgpCiRvdXREb25lID0gJGZhbHNlCiRlcnJEb25lID0gJGZhbHNlCgokc3cgPSBbU3lzdGVtLkRpYWdub3N0aWNzLlN0b3B3YXRjaF06OlN0YXJ0TmV3KCkKJGV4Y2VlZGVkID0gMAp3aGlsZSAoKC1ub3QgJHAuSGFzRXhpdGVkKSAtb3IgKC1ub3QgJG91dERvbmUpIC1vciAoLW5vdCAkZXJyRG9uZSkpIHsKICAgIGlmICgoLW5vdCAkb3V0RG9uZSkgLWFuZCAkb3V0VGFzay5Jc0NvbXBsZXRlZCkgewogICAgICAgICRuID0gJG91dFRhc2suUmVzdWx0CiAgICAgICAgaWYgKCRuIC1lcSAwKSB7CiAgICAgICAgICAgICRvdXREb25lID0gJHRydWUKICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAkY2h1bmsgPSBbc3RyaW5nXTo6bmV3KCRvdXRDaHVua0J1ZiwgMCwgJG4pCiAgICAgICAgICAgIFtDb25zb2xlXTo6T3V0LldyaXRlKCRjaHVuaykKICAgICAgICAgICAgJG51bGwgPSAkb3V0QnVmLkFwcGVuZCgkY2h1bmspCiAgICAgICAgICAgICRvdXRUYXNrID0gJHAuU3RhbmRhcmRPdXRwdXQuUmVhZEFzeW5jKCRvdXRDaHVua0J1ZiwgMCwgJG91dENodW5rQnVmLkxlbmd0aCkKICAgICAgICB9CiAgICB9CiAgICBpZiAoKC1ub3QgJGVyckRvbmUpIC1hbmQgJGVyclRhc2suSXNDb21wbGV0ZWQpIHsKICAgICAgICAkbiA9ICRlcnJUYXNrLlJlc3VsdAogICAgICAgIGlmICgkbiAtZXEgMCkgewogICAgICAgICAgICAkZXJyRG9uZSA9ICR0cnVlCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgJGNodW5rID0gW3N0cmluZ106Om5ldygkZXJyQ2h1bmtCdWYsIDAsICRuKQogICAgICAgICAgICBbQ29uc29sZV06OkVycm9yLldyaXRlKCRjaHVuaykKICAgICAgICAgICAgJG51bGwgPSAkZXJyQnVmLkFwcGVuZCgkY2h1bmspCiAgICAgICAgICAgICRlcnJUYXNrID0gJHAuU3RhbmRhcmRFcnJvci5SZWFkQXN5bmMoJGVyckNodW5rQnVmLCAwLCAkZXJyQ2h1bmtCdWYuTGVuZ3RoKQogICAgICAgIH0KICAgIH0KICAgIGlmICgoLW5vdCAkZXhjZWVkZWQpIC1hbmQgKCRzdy5FbGFwc2VkTWlsbGlzZWNvbmRzIC1nZSAkcHJvYmVNcykpIHsKICAgICAgICAkZXhjZWVkZWQgPSAxCiAgICB9CiAgICBTdGFydC1TbGVlcCAtTWlsbGlzZWNvbmRzIDIwCn0KJHAuV2FpdEZvckV4aXQoKQoKJG91dEJ1Zi5Ub1N0cmluZygpIHwgU2V0LUNvbnRlbnQgLVBhdGggJG91dFBhdGggLUVuY29kaW5nIEFTQ0lJCiRlcnJCdWYuVG9TdHJpbmcoKSB8IFNldC1Db250ZW50IC1QYXRoICRlcnJQYXRoIC1FbmNvZGluZyBBU0NJSQoiJGV4Y2VlZGVkfCQoJHAuRXhpdENvZGUpIiB8IFNldC1Db250ZW50IC1QYXRoICRyZXN1bHRQYXRoIC1FbmNvZGluZyBBU0NJSQo=" set "HP_EXE_SMOKERUN=IyA6cnVuX2V4ZV9zbW9rZXJ1bidzIGRlZGljYXRlZCBoZWxwZXIgLS0gdGhlIE9OTFkgcGxhY2UgaW4gdGhpcyBmaWxlIGZhbWlseSBhbGxvd2VkIHRvCiMgZm9yY2Uta2lsbCAoS2lsbCgpKSB0aGUgdmVyaWZpY2F0aW9uIHJ1bi4gVW5saWtlIH5mYWlsZmFzdF9wcm9iZS5wczEgKG5ldmVyIGtpbGxzIC0tIGNvdmVycwojIHRoZSB1bnRpbWVkIGZhc3RwYXRoL2ludGVycHJldGVyL2NoZWNrcG9pbnQgY2FsbCBzaXRlcyksIHRoaXMgSVMgdGhlIGZyZXNoLWJ1aWxkIHZlcmlmaWNhdGlvbgojIHJ1biBpdHNlbGY6IG5vdGhpbmcgZWxzZSB3aWxsIGV2ZXIgY29uZmlybSB0aGlzIHBhcnRpY3VsYXIgYnVpbGQgd29ya2VkLCBzbyBhbiB1bnJlc3BvbnNpdmUKIyBwcm9jZXNzIGhlcmUgY2Fubm90IGJlIHRydXN0ZWQgdG8gZXZlbnR1YWxseSBmaW5pc2ggdGhlIHdheSBhIHByZXZpb3VzbHktdmVyaWZpZWQgY2FjaGVkCiMgYXJ0aWZhY3Qgb3IgaW50ZXJwcmV0ZXIgcnVuIGNhbi4KIwojIFJlYWRzIGlucHV0cyBmcm9tIGVudiB2YXJzIChzYW1lIGNtZC5leGUtcXVvdGluZy1oYXphcmQtYXZvaWRhbmNlIHJlYXNvbmluZyBhcwojIH5mYWlsZmFzdF9wcm9iZS5wczEncyBvd24gaGVhZGVyIGNvbW1lbnQpOiBIUF9TTU9LRVJVTl9FWEUgKGJhcmUgZmlsZW5hbWU7IGNhbGxlciBydW5zIHRoaXMKIyBzY3JpcHQgd2l0aCBDV0QgYWxyZWFkeSBzZXQgdG8gZGlzdFwsIG1hdGNoaW5nIDpydW5fZXhlX3Ntb2tlcnVuJ3MgZXhpc3RpbmcgcHVzaGQgZGlzdAojIGNvbnZlbnRpb24gLS0gUkVRLTAxOCAyYi1BLjIsIGxvYWQtYmVhcmluZyBmb3IgdGhlIENXRC1yZWxhdGl2ZSBjb25maWcuanNvbiB4ZmFpbCBjYXNlLCBzZWUKIyBkb2NzL2FnZW50LWludGVyY29ubmVjdC5tZCAiU2luZ2xlLXZlcmlmaWNhdGlvbiBzbW9rZSBtb2RlbCIpLiBIUF9TTU9LRVJVTl9PVVQvSFBfU01PS0VSVU5fRVJSCiMgZGVmYXVsdCB0byAuLlx+cnVuLm91dC50eHQgLyAuLlx+cnVuLmVyci50eHQgKHJlbGF0aXZlIHRvIGRpc3RcLCBtYXRjaGluZyB0aGUgcHJlLWV4aXN0aW5nCiMgY29udmVudGlvbikuIEhQX1NNT0tFUlVOX1JFU1VMVCAoZGVmYXVsdCB+c21va2VydW5fcmVzdWx0LnR4dCkgaXMgd2hlcmUgdGhpcyBzY3JpcHQgd3JpdGVzIGl0cwojIGV4aXQtY29kZSByZXN1bHQgLS0gTk9UIHN0ZG91dDsgc2VlIH5mYWlsZmFzdF9wcm9iZS5wczEncyBoZWFkZXIgY29tbWVudCAoc2FtZSByZWFzb25pbmc6IHRoZQojIGNhbGxlciBpbnZva2VzIHRoaXMgc2NyaXB0IGRpcmVjdGx5LCBubyBmb3IgL2YvYmFja3RpY2sgc3Rkb3V0IGNhcHR1cmUsIHNvIGxpdmUtdGVlZCBvdXRwdXQKIyByZWFjaGVzIHRoZSBjb25zb2xlIGluc3RlYWQgb2YgYmVpbmcgc2lsZW50bHkgc3dhbGxvd2VkIGFuZCBjb3JydXB0aW5nIHJlc3VsdCBwYXJzaW5nKS4gQ2FsbGVyCiMgbXVzdCBwcmUtdHJ1bmNhdGUgdGhlIG91dHB1dC9yZXN1bHQgZmlsZXMgYmVmb3JlIGludm9raW5nLCBzYW1lIGFzIH5mYWlsZmFzdF9wcm9iZS5wczEuCiMKIyBTYW1lIGxpdmUtdGVlIGFzIH5mYWlsZmFzdF9wcm9iZS5wczEgLS0gc2VlIHRoYXQgZmlsZSdzIGhlYWRlciBjb21tZW50IGZvciB0aGUgZnVsbCByYXRpb25hbGUKIyAoc2VsZi1zZXF1ZW5jZWQgY2h1bmsgcmVhZHMgdmlhIFN0cmVhbVJlYWRlci5SZWFkQXN5bmMoY2hhcltdLCBpbnQsIGludCksIE5PVAojIFJlZ2lzdGVyLU9iamVjdEV2ZW50IG9yIFJlYWRMaW5lQXN5bmMoKSAtLSBQb3dlclNoZWxsL1Bvd2VyU2hlbGwjMTE5MzcgYW5kIEZpbmRpbmcgOSwKIyBkb2NzL3BsYW4tY2xpLWludGVyYWN0aXZlLXZlcmlmaWNhdGlvbi5tZCwgY292ZXIgd2h5KS4KIwojIGRlcml2ZWQgcmVxdWlyZW1lbnQgKE9wZW4gUXVlc3Rpb24gMSwgb3duZXIgZGVjaXNpb24gMjAyNi0wNy0yNCk6IEhQX1NNT0tFUlVOX0tJTExfTVMgKGRlZmF1bHQKIyAzMDAwMCwgdW5jaGFuZ2VkKSBpcyBhIGNsYXNzaWZpY2F0aW9uIGNoZWNrcG9pbnQsIG5vdCBhbiB1bmNvbmRpdGlvbmFsIGRlYWRsaW5lIC0tIEtpbGwoKSBmaXJlcwojIG9ubHkgaWYgJHNhd091dHB1dCBpcyBzdGlsbCBmYWxzZSBhdCBraWxsTXMgKGZ1bGx5IHNpbGVudCA9IHByZXN1bWVkIGh1bmcpLiBBbnkgYnl0ZXMgb2JzZXJ2ZWQKIyBvbiBlaXRoZXIgc3RyZWFtIHNraXBzIHRoZSBraWxsIGFuZCB0aGUgd2FpdCBiZWNvbWVzIHVuYm91bmRlZCwgbWlycm9yaW5nCiMgfmZhaWxmYXN0X3Byb2JlLnBzMSdzIHBoaWxvc29waHkgLS0gc2VlIGRvY3MvYWdlbnQtaW50ZXJjb25uZWN0Lm1kICJBY3Rpdml0eS1hd2FyZSBFWEUtc21va2UKIyBraWxsIiBmb3IgdGhlIGZ1bGwgcmF0aW9uYWxlL3RyYWRlLW9mZi4gQ2h1bmstYmFzZWQgKG5vdCBsaW5lLWJhc2VkKSByZWFkcyBhcmUgd2hhdCBtYWtlIHRoaXMKIyBhY3R1YWxseSBmaXJlIGZvciB0aGUgY2Fub25pY2FsIGBpbnB1dCgicHJvbXB0IilgIGNhc2UgLS0gc2VlIEZpbmRpbmcgOSBpbiB0aGUgcGxhbiBkb2MgYWJvdmUuCiMKIyBUaGlzIGlzIHRoZSBjYW5vbmljYWwgc291cmNlIGZvciB0aGUgSFBfRVhFX1NNT0tFUlVOIGJhc2U2NCBwYXlsb2FkIGVtYmVkZGVkIGluIHJ1bl9zZXR1cC5iYXQuCiMgQWZ0ZXIgZWRpdGluZywgcmUtZW5jb2RlIGFuZCBwYXN0ZSBpdCBpbnRvIHRoZSBgc2V0ICJIUF9FWEVfU01PS0VSVU49Li4uImAgbGluZTsKIyB0ZXN0cy90ZXN0X2V4ZV9zbW9rZXJ1bi5weSBhc3NlcnRzIHRoZSBlbWJlZGRlZCBwYXlsb2FkIG1hdGNoZXMgdGhpcyBmaWxlIChDUkxGL0xGIG5vcm1hbGl6ZWQsCiMgcGVyIHRoZSAucHMxIFBheWxvYWRTeW5jIGNvbnZlbnRpb24gLS0gc2VlIGRvY3MvYWdlbnQtbGVzc29ucy1sZWFybmVkLm1kCiMgIkVtYmVkZGVkIEhlbHBlciBVcGRhdGUgV29ya2Zsb3ciKS4KIwojIGRlcml2ZWQgcmVxdWlyZW1lbnQgKFtSRVEtMDI2XSBhcmd2IHBhc3N0aHJvdWdoKTogSFBfU01PS0VSVU5fQVJHUywgaWYgc2V0LCBpcyBhIGZ1bGwsCiMgYWxyZWFkeS1xdW90ZWQgV2luZG93cyBBcmd1bWVudHMgc3RyaW5nICh0aGUgRVhFIGlzIHNlbGYtY29udGFpbmVkLCBzbyBubyBzZXBhcmF0ZSBlbnRyeS1maWxlCiMgYXJndiBpcyBuZWVkZWQgaGVyZSAtLSBqdXN0IGFueSBmb3J3YXJkZWQgZXh0cmEgYXJncykuIFVzZWQgdmVyYmF0aW0sIG5vIHJlLXF1b3RpbmcuCiRleGUgPSAkZW52OkhQX1NNT0tFUlVOX0VYRQoka2lsbE1zID0gMzAwMDAKaWYgKCRlbnY6SFBfU01PS0VSVU5fS0lMTF9NUykgeyAka2lsbE1zID0gW2ludF0kZW52OkhQX1NNT0tFUlVOX0tJTExfTVMgfQokYXJnc1JhdyA9ICRlbnY6SFBfU01PS0VSVU5fQVJHUwokb3V0UGF0aCA9ICRlbnY6SFBfU01PS0VSVU5fT1VUCmlmICgtbm90ICRvdXRQYXRoKSB7ICRvdXRQYXRoID0gJy4uXH5ydW4ub3V0LnR4dCcgfQokZXJyUGF0aCA9ICRlbnY6SFBfU01PS0VSVU5fRVJSCmlmICgtbm90ICRlcnJQYXRoKSB7ICRlcnJQYXRoID0gJy4uXH5ydW4uZXJyLnR4dCcgfQokcmVzdWx0UGF0aCA9ICRlbnY6SFBfU01PS0VSVU5fUkVTVUxUCmlmICgtbm90ICRyZXN1bHRQYXRoKSB7ICRyZXN1bHRQYXRoID0gJ35zbW9rZXJ1bl9yZXN1bHQudHh0JyB9Cgokc2kgPSBOZXctT2JqZWN0IFN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzU3RhcnRJbmZvCiRzaS5GaWxlTmFtZSA9ICRleGUKaWYgKCRhcmdzUmF3KSB7ICRzaS5Bcmd1bWVudHMgPSAkYXJnc1JhdyB9CiRzaS5Vc2VTaGVsbEV4ZWN1dGUgPSAkZmFsc2UKJHNpLlJlZGlyZWN0U3RhbmRhcmRPdXRwdXQgPSAkdHJ1ZQokc2kuUmVkaXJlY3RTdGFuZGFyZEVycm9yID0gJHRydWUKJHAgPSBOZXctT2JqZWN0IFN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzCiRwLlN0YXJ0SW5mbyA9ICRzaQokcC5TdGFydCgpIHwgT3V0LU51bGwKV3JpdGUtSG9zdCAiW0lORk9dIFByb2Nlc3MgSUQgJCgkcC5JZCkuIElmIGl0IHNlZW1zIHN0dWNrOiBUYXNrIE1hbmFnZXIgPiBEZXRhaWxzIHRhYiA+IGZpbmQgdGhpcyBQSUQgPiBFbmQgVGFzayAodGhpcyB3aW5kb3cgc3RheXMgb3BlbikuIgoKJG91dEJ1ZiA9IE5ldy1PYmplY3QgU3lzdGVtLlRleHQuU3RyaW5nQnVpbGRlcgokZXJyQnVmID0gTmV3LU9iamVjdCBTeXN0ZW0uVGV4dC5TdHJpbmdCdWlsZGVyCiRvdXRDaHVua0J1ZiA9IE5ldy1PYmplY3QgY2hhcltdIDQwOTYKJGVyckNodW5rQnVmID0gTmV3LU9iamVjdCBjaGFyW10gNDA5Ngokb3V0VGFzayA9ICRwLlN0YW5kYXJkT3V0cHV0LlJlYWRBc3luYygkb3V0Q2h1bmtCdWYsIDAsICRvdXRDaHVua0J1Zi5MZW5ndGgpCiRlcnJUYXNrID0gJHAuU3RhbmRhcmRFcnJvci5SZWFkQXN5bmMoJGVyckNodW5rQnVmLCAwLCAkZXJyQ2h1bmtCdWYuTGVuZ3RoKQokb3V0RG9uZSA9ICRmYWxzZQokZXJyRG9uZSA9ICRmYWxzZQoKJHN3ID0gW1N5c3RlbS5EaWFnbm9zdGljcy5TdG9wd2F0Y2hdOjpTdGFydE5ldygpCiRraWxsZWQgPSAkZmFsc2UKJHNhd091dHB1dCA9ICRmYWxzZQp3aGlsZSAoKC1ub3QgJHAuSGFzRXhpdGVkKSAtb3IgKC1ub3QgJG91dERvbmUpIC1vciAoLW5vdCAkZXJyRG9uZSkpIHsKICAgIGlmICgoLW5vdCAkb3V0RG9uZSkgLWFuZCAkb3V0VGFzay5Jc0NvbXBsZXRlZCkgewogICAgICAgICRuID0gJG91dFRhc2suUmVzdWx0CiAgICAgICAgaWYgKCRuIC1lcSAwKSB7CiAgICAgICAgICAgICRvdXREb25lID0gJHRydWUKICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAkc2F3T3V0cHV0ID0gJHRydWUKICAgICAgICAgICAgJGNodW5rID0gW3N0cmluZ106Om5ldygkb3V0Q2h1bmtCdWYsIDAsICRuKQogICAgICAgICAgICBbQ29uc29sZV06Ok91dC5Xcml0ZSgkY2h1bmspCiAgICAgICAgICAgICRudWxsID0gJG91dEJ1Zi5BcHBlbmQoJGNodW5rKQogICAgICAgICAgICAkb3V0VGFzayA9ICRwLlN0YW5kYXJkT3V0cHV0LlJlYWRBc3luYygkb3V0Q2h1bmtCdWYsIDAsICRvdXRDaHVua0J1Zi5MZW5ndGgpCiAgICAgICAgfQogICAgfQogICAgaWYgKCgtbm90ICRlcnJEb25lKSAtYW5kICRlcnJUYXNrLklzQ29tcGxldGVkKSB7CiAgICAgICAgJG4gPSAkZXJyVGFzay5SZXN1bHQKICAgICAgICBpZiAoJG4gLWVxIDApIHsKICAgICAgICAgICAgJGVyckRvbmUgPSAkdHJ1ZQogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICRzYXdPdXRwdXQgPSAkdHJ1ZQogICAgICAgICAgICAkY2h1bmsgPSBbc3RyaW5nXTo6bmV3KCRlcnJDaHVua0J1ZiwgMCwgJG4pCiAgICAgICAgICAgIFtDb25zb2xlXTo6RXJyb3IuV3JpdGUoJGNodW5rKQogICAgICAgICAgICAkbnVsbCA9ICRlcnJCdWYuQXBwZW5kKCRjaHVuaykKICAgICAgICAgICAgJGVyclRhc2sgPSAkcC5TdGFuZGFyZEVycm9yLlJlYWRBc3luYygkZXJyQ2h1bmtCdWYsIDAsICRlcnJDaHVua0J1Zi5MZW5ndGgpCiAgICAgICAgfQogICAgfQogICAgaWYgKCgtbm90ICRraWxsZWQpIC1hbmQgKC1ub3QgJHNhd091dHB1dCkgLWFuZCAoLW5vdCAkcC5IYXNFeGl0ZWQpIC1hbmQgKCRzdy5FbGFwc2VkTWlsbGlzZWNvbmRzIC1nZSAka2lsbE1zKSkgewogICAgICAgIHRyeSB7ICRwLktpbGwoKSB9IGNhdGNoIHt9CiAgICAgICAgJGtpbGxlZCA9ICR0cnVlCiAgICB9CiAgICBTdGFydC1TbGVlcCAtTWlsbGlzZWNvbmRzIDIwCn0KJHAuV2FpdEZvckV4aXQoKQoKJG91dEJ1Zi5Ub1N0cmluZygpIHwgU2V0LUNvbnRlbnQgLVBhdGggJG91dFBhdGggLUVuY29kaW5nIEFTQ0lJCiRlcnJCdWYuVG9TdHJpbmcoKSB8IFNldC1Db250ZW50IC1QYXRoICRlcnJQYXRoIC1FbmNvZGluZyBBU0NJSQppZiAoJGtpbGxlZCkgewogICAgIi0xIiB8IFNldC1Db250ZW50IC1QYXRoICRyZXN1bHRQYXRoIC1FbmNvZGluZyBBU0NJSQp9IGVsc2UgewogICAgIiQoJHAuRXhpdENvZGUpIiB8IFNldC1Db250ZW50IC1QYXRoICRyZXN1bHRQYXRoIC1FbmNvZGluZyBBU0NJSQp9CgpHZXQtRXZlbnRTdWJzY3JpYmVyIC1FcnJvckFjdGlvbiBTaWxlbnRseUNvbnRpbnVlIHwgVW5yZWdpc3Rlci1FdmVudCAtRXJyb3JBY3Rpb24gU2lsZW50bHlDb250aW51ZQo=" -set "HP_EXE_HINT_RERUN=IyA6ZXhlX3Ntb2tlcnVuX2hpbnRzJyBib3VuZGVkIGRpYWdub3N0aWMgcmUtcnVuIGhlbHBlci4gVW5saWtlIGV4ZV9zbW9rZXJ1bi5wczEvZmFpbGZhc3RfcHJvYmUucHMxDQojIChhY3Rpdml0eS1hd2FyZSAtLSBvbmNlIGEgcHJvY2VzcyBoYXMgcHJpbnRlZCBhbnl0aGluZywgdGhlIGtpbGwgaXMgc2tpcHBlZCBhbmQgdGhlIHdhaXQgYmVjb21lcw0KIyB1bmJvdW5kZWQsIHNpbmNlIHRob3NlIGNvdmVyIFJFQUwgdmVyaWZpY2F0aW9uIHJ1bnMgd29ydGggd2FpdGluZyBvbiksIHRoaXMgcmUtcnVuIGlzIGRpYWdub3N0aWMNCiMgT05MWTogaXRzIHNvbGUgcHVycG9zZSBpcyBhIHN0ZG91dCtzdGRlcnIgc25hcHNob3QgZm9yIHN0ZGVyciBwYXR0ZXJuLW1hdGNoaW5nIChNb2R1bGVOb3RGb3VuZEVycm9yLw0KIyBGaWxlTm90Rm91bmRFcnJvciBzaWduYXR1cmVzKSwgbmV2ZXIgc2hvd24gbGl2ZSB0byB0aGUgdXNlci4gUGFydGlhbCBvdXRwdXQgb24gYSBoYW5nIGlzIGZpbmUgYW5kDQojIHN0cmljdGx5IHByZWZlcnJlZCBvdmVyIGhhbmdpbmcgdGhlIHdob2xlIGJvb3RzdHJhcCBhIHNlY29uZCB0aW1lIG9uIGEgcnVuIG5vYm9keSBpcyB3YXRjaGluZyAtLQ0KIyBzbyB0aGUga2lsbCBoZXJlIGlzIFVOQ09ORElUSU9OQUwgYXQgdGhlIGRlYWRsaW5lLCBub3QgYWN0aXZpdHktYXdhcmUuIFNlZSBDTEFVREUubWQncyBmb3JtZXINCiMgQWN0aXZlIEJhY2tsb2cgaXRlbSAxNSAvIGRvY3MvYWdlbnQtY2xvc2VkLWJhY2tsb2cubWQgZm9yIHRoZSBnYXAgdGhpcyBjbG9zZXM6IHRoZSBwcmlvciBpbmxpbmUNCiMgIjpleGVfc21va2VydW5faGludHMiIGJvZHkgZGlkIGEgcGxhaW4sIHVudGltZWQgYCIlRU5WTkFNRSUuZXhlIiA+ICJ+ZXhlX291dC50eHQiIDI+JjFgIC0tIHRoZQ0KIyBPTkUgdXNlci1jb2RlIGxhdW5jaCBwb2ludCBpbiB0aGlzIGZpbGUgd2l0aCBubyB0aW1lb3V0IGF0IGFsbCwgb24gdGhlIHRoZW9yeSB0aGF0IGEgZ2VudWluZQ0KIyBNb2R1bGVOb3RGb3VuZEVycm9yL0ZpbGVOb3RGb3VuZEVycm9yIGFsd2F5cyBleGl0cyBpbW1lZGlhdGVseS4gVGhhdCB0aGVvcnkgaG9sZHMgZm9yIGENCiMgREVURVJNSU5JU1RJQyBmYWlsdXJlLCBidXQgdGhpcyBpcyBhIGZyZXNoIHJlLXJ1biBvZiB0aGUgc2FtZSBiaW5hcnk7IGFueSBub24tZGV0ZXJtaW5pc20gKGENCiMgcmFjZSwgYW4gZW52aXJvbm1lbnQgY2hlY2sgdGhhdCBzb21ldGltZXMgc3VjY2VlZHMsIGFueXRoaW5nIHRoYXQgb2NjYXNpb25hbGx5IGJsb2NrcyBvbg0KIyBpbmhlcml0ZWQgc3RkaW4gaW5zdGVhZCBvZiBleGl0aW5nIGZhc3QpIGNvdWxkIGhhbmcgdGhpcyBzZWNvbmQsIHVudGltZWQgaW52b2NhdGlvbiBldmVuIHRob3VnaA0KIyB0aGUgRklSU1QgaW52b2NhdGlvbiBsZWdpdGltYXRlbHkgY2xhc3NpZmllZCBhcyAiZmFzdCwgcmVhbCwgbm9uLWhhbmcgZmFpbHVyZSIuDQojDQojIFJlYWRzOiBIUF9ISU5UX1JFUlVOX0VYRSAoYmFyZSBmaWxlbmFtZTsgY2FsbGVyIHJ1bnMgdGhpcyB3aXRoIENXRCBhbHJlYWR5IHNldCB0byBkaXN0XCwgbWlycm9yaW5nDQojIGV4ZV9zbW9rZXJ1bi5wczEncyBvd24gY29udmVudGlvbikuIEhQX0hJTlRfUkVSVU5fT1VUIChkZWZhdWx0IH5leGVfb3V0LnR4dCwgcmVsYXRpdmUgdG8gZGlzdFwpDQojIGlzIHdoZXJlIGNvbWJpbmVkIHN0ZG91dCtzdGRlcnIgaXMgd3JpdHRlbiAtLSBtYXRjaGluZyB0aGUgT1JJR0lOQUwgYDI+JjFgIG1lcmdlLWludG8tb25lLWZpbGUNCiMgYmVoYXZpb3IgZXhhY3RseSwgc2luY2UgdGhlIGV4aXN0aW5nIGZpbmRzdHIgaGludC1tYXRjaGluZyBpbiA6ZXhlX3Ntb2tlcnVuX2hpbnRzIG9ubHkgY2hlY2tzIGZvcg0KIyBzdWJzdHJpbmcgcHJlc2VuY2UgaW4gdGhhdCBvbmUgZmlsZSwgbmV2ZXIgd2hpY2ggc3RyZWFtIGEgbGluZSBjYW1lIGZyb20uIEhQX0hJTlRfUkVSVU5fS0lMTF9NUw0KIyAoZGVmYXVsdCAxMDAwMCAtLSBhIGRpYWdub3N0aWMgY2FwdHVyZSBvbiBhbiBhbHJlYWR5LWZhaWxlZCBydW4gZG9lcyBub3QgbmVlZCB0aGUgZnVsbCAzMHMNCiMgcHJpbWFyeS12ZXJpZmljYXRpb24gYnVkZ2V0KSBpcyBhIHRlc3Qtb25seSBvdmVycmlkZSBwb2ludCwgbWlycm9yaW5nIEhQX1NNT0tFUlVOX0tJTExfTVMncw0KIyBlc3RhYmxpc2hlZCBwYXR0ZXJuLg0KIw0KIyBkZXJpdmVkIHJlcXVpcmVtZW50OiB0aGUgcmVzb2x2ZWQgJGtpbGxNcyBpcyBBTFdBWVMgd3JpdHRlbiB0byBIUF9ISU5UX1JFUlVOX0tJTExNU19PVVQgKGRlZmF1bHQNCiMgfmV4ZV9oaW50X2tpbGxtcy50eHQpIHJpZ2h0IGFmdGVyIGl0J3MgY29tcHV0ZWQsIHVuY29uZGl0aW9uYWxseSAtLSBwcm9kdWN0aW9uIGNhbGxlcnMgbmV2ZXIgcmVhZA0KIyB0aGlzIGZpbGUsIHNvIGl0IGNvc3RzIG5vdGhpbmcgdGhlcmUuIEl0IGV4aXN0cyBzbyB0ZXN0cyBjYW4gYXNzZXJ0IHRoZSBvdmVycmlkZSB3YXMgYWN0dWFsbHkNCiMgaG9ub3JlZCBieSByZWFkaW5nIHRoaXMgRElSRUNUIHZhbHVlLCBpbnN0ZWFkIG9mIGluZmVycmluZyBpdCBmcm9tIHdhbGwtY2xvY2sgZWxhcHNlZCB0aW1lICh3aGljaA0KIyBwcm92ZWQgdW5yZWxpYWJsZSBvbiBzaGFyZWQgcmVhbC1XaW5kb3dzIENJIHJ1bm5lcnMgLS0gdGhlIHNhbWUgdGVzdCBtZWFzdXJlZCA5LjJzLCAxMy41cywgMTQuNnMsDQojIGFuZCAxNi4zcyBvZiBvdmVyaGVhZCBhY3Jvc3MgZm91ciBkaWZmZXJlbnQgcmVhbC1DSSBydW5zIGZvciB0aGUgaWRlbnRpY2FsIDUwMG1zIG92ZXJyaWRlLCBhDQojIG1vdmluZyB0YXJnZXQgdGhhdCBubyBmaXhlZCBib3VuZCBjb3VsZCBjaGFzZTsgc2VlIGRvY3MvYWdlbnQtbGVzc29ucy1sZWFybmVkLm1kKS4NCiMNCiMgVGhpcyBpcyB0aGUgY2Fub25pY2FsIHNvdXJjZSBmb3IgdGhlIEhQX0VYRV9ISU5UX1JFUlVOIGJhc2U2NCBwYXlsb2FkIGVtYmVkZGVkIGluIHJ1bl9zZXR1cC5iYXQuDQojIEFmdGVyIGVkaXRpbmcsIHJ1biBgcHl0aG9uIHRvb2xzL3N5bmNfcGF5bG9hZC5weSBIUF9FWEVfSElOVF9SRVJVTiB0b29scy9leGVfaGludF9yZXJ1bi5wczFgOw0KIyB0ZXN0cy90ZXN0X2V4ZV9oaW50X3JlcnVuLnB5IGFzc2VydHMgdGhlIGVtYmVkZGVkIHBheWxvYWQgbWF0Y2hlcyB0aGlzIGZpbGUgKENSTEYvTEYgbm9ybWFsaXplZCwNCiMgcGVyIHRoZSAucHMxIFBheWxvYWRTeW5jIGNvbnZlbnRpb24gLS0gc2VlIGRvY3MvYWdlbnQtbGVzc29ucy1sZWFybmVkLm1kDQojICJFbWJlZGRlZCBIZWxwZXIgVXBkYXRlIFdvcmtmbG93IikuDQokZXhlID0gJGVudjpIUF9ISU5UX1JFUlVOX0VYRQ0KJG91dFBhdGggPSAkZW52OkhQX0hJTlRfUkVSVU5fT1VUDQppZiAoLW5vdCAkb3V0UGF0aCkgeyAkb3V0UGF0aCA9ICd+ZXhlX291dC50eHQnIH0NCiRraWxsTXMgPSAxMDAwMA0KaWYgKCRlbnY6SFBfSElOVF9SRVJVTl9LSUxMX01TKSB7ICRraWxsTXMgPSBbaW50XSRlbnY6SFBfSElOVF9SRVJVTl9LSUxMX01TIH0NCiRraWxsTXNPdXRQYXRoID0gJGVudjpIUF9ISU5UX1JFUlVOX0tJTExNU19PVVQNCmlmICgtbm90ICRraWxsTXNPdXRQYXRoKSB7ICRraWxsTXNPdXRQYXRoID0gJ35leGVfaGludF9raWxsbXMudHh0JyB9DQoiJGtpbGxNcyIgfCBTZXQtQ29udGVudCAtUGF0aCAka2lsbE1zT3V0UGF0aCAtRW5jb2RpbmcgQVNDSUkNCg0KJHNpID0gTmV3LU9iamVjdCBTeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2Vzc1N0YXJ0SW5mbw0KJHNpLkZpbGVOYW1lID0gJGV4ZQ0KJHNpLlVzZVNoZWxsRXhlY3V0ZSA9ICRmYWxzZQ0KJHNpLlJlZGlyZWN0U3RhbmRhcmRPdXRwdXQgPSAkdHJ1ZQ0KJHNpLlJlZGlyZWN0U3RhbmRhcmRFcnJvciA9ICR0cnVlDQokcCA9IE5ldy1PYmplY3QgU3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3MNCiRwLlN0YXJ0SW5mbyA9ICRzaQ0KJHAuU3RhcnQoKSB8IE91dC1OdWxsDQoNCiRvdXRUYXNrID0gJHAuU3RhbmRhcmRPdXRwdXQuUmVhZFRvRW5kQXN5bmMoKQ0KJGVyclRhc2sgPSAkcC5TdGFuZGFyZEVycm9yLlJlYWRUb0VuZEFzeW5jKCkNCiRleGl0ZWQgPSAkcC5XYWl0Rm9yRXhpdCgka2lsbE1zKQ0KaWYgKC1ub3QgJGV4aXRlZCkgew0KICAgICMgZGVyaXZlZCByZXF1aXJlbWVudDogUHJvY2Vzcy5LaWxsKCkgKHRoZSBwYXJhbWV0ZXJsZXNzIG92ZXJsb2FkIC0tIFdpbmRvd3MgUG93ZXJTaGVsbCA1LjENCiAgICAjIHRhcmdldHMgLk5FVCBGcmFtZXdvcmssIHdoaWNoIGhhcyBubyBQcm9jZXNzLktpbGwoZW50aXJlUHJvY2Vzc1RyZWUpIG92ZXJsb2FkOyB0aGF0J3MNCiAgICAjIC5ORVQgNSsgb25seSkgdGVybWluYXRlcyBPTkxZICRwIGl0c2VsZi4gQSBQeUluc3RhbGxlciBvbmVmaWxlIGJvb3Rsb2FkZXIgKG9yIGFueSBwcm9ncmFtKQ0KICAgICMgdGhhdCBzcGF3bnMgYSBjaGlsZCBpbmhlcml0aW5nIHRoZSByZWRpcmVjdGVkIHN0ZG91dC9zdGRlcnIgaGFuZGxlcyBjYW4gbGVhdmUgdGhhdCBjaGlsZA0KICAgICMgcnVubmluZyBhZnRlciAkcCBpcyBraWxsZWQgLS0gdGhlIHBpcGUgdGhlbiBuZXZlciByZWFjaGVzIEVPRiwgYW5kIGFuIHVuYm91bmRlZA0KICAgICMgUmVhZFRvRW5kQXN5bmMoKS5SZXN1bHQgd291bGQgaGFuZyBmb3JldmVyLCBkZWZlYXRpbmcgdGhlIGVudGlyZSBwb2ludCBvZiB0aGlzIGJvdW5kZWQNCiAgICAjIGhlbHBlci4gdGFza2tpbGwgL1QgdGVybWluYXRlcyB0aGUgd2hvbGUgcHJvY2VzcyB0cmVlLCBub3QganVzdCAkcC4gTk9UIGluZGVwZW5kZW50bHkNCiAgICAjIHZlcmlmaWVkIG9uIHJlYWwgV2luZG93cyBDSSB0aGF0IGEgZ2VudWluZSBkZXNjZW5kYW50LWhvbGRzLXRoZS1waXBlIHNjZW5hcmlvIGlzIGZ1bGx5DQogICAgIyBjb3ZlcmVkIGJ5IHRoaXMgKG5vIFdpbmRvd3MgZW52aXJvbm1lbnQgYXZhaWxhYmxlIHRvIGNvbnN0cnVjdCB0aGF0IHJlcHJvKSAtLSB0aGUgYm91bmRlZA0KICAgICMgZmluYWwgcmVhZCBiZWxvdyBpcyBhIHNlY29uZCwgaW5kZXBlbmRlbnQgc2FmZXR5IG5ldCBmb3IgZXhhY3RseSB0aGF0IHJlc2lkdWFsIHJpc2suDQogICAgdHJ5IHsgJiB0YXNra2lsbC5leGUgL0YgL1QgL1BJRCAkcC5JZCAyPiRudWxsIDE+JG51bGwgfSBjYXRjaCB7fQ0KICAgIHRyeSB7ICRwLktpbGwoKSB9IGNhdGNoIHt9DQp9DQokcC5XYWl0Rm9yRXhpdCgpDQoNCiMgQm91bmRlZCBmaW5hbCByZWFkLCBOT1QgYSBibGluZCAuUmVzdWx0IGJsb2NrOiBldmVuIGFmdGVyIGtpbGxpbmcgdGhlIHByb2Nlc3MgdHJlZSwgYQ0KIyBkZXNjZW5kYW50IHRhc2traWxsIC9UIGRpZCBub3QgY2F0Y2ggKG9yIHNvbWUgb3RoZXIgZXhvdGljIGhhbmRsZS1pbmhlcml0YW5jZSBlZGdlIGNhc2UpDQojIHNob3VsZCBub3QgYmUgYWJsZSB0byBoYW5nIHRoaXMgZGlhZ25vc3RpYy1vbmx5IGhlbHBlciBpbmRlZmluaXRlbHkuIFRhc2suV2FpdChtcykgcmV0dXJucw0KIyBmYWxzZSBvbiB0aW1lb3V0IHdpdGhvdXQgdGhyb3dpbmcsIHNvIGEgc3R1Y2sgcGlwZSBkZWdyYWRlcyB0byBwYXJ0aWFsL2VtcHR5IG91dHB1dCBpbnN0ZWFkDQojIG9mIGFuIHVuYm91bmRlZCB3YWl0Lg0KJGRyYWluTXMgPSA1MDAwDQokb3V0ID0gJycNCiRlcnIgPSAnJw0KaWYgKCRvdXRUYXNrLldhaXQoJGRyYWluTXMpKSB7IHRyeSB7ICRvdXQgPSAkb3V0VGFzay5SZXN1bHQgfSBjYXRjaCB7fSB9DQppZiAoJGVyclRhc2suV2FpdCgkZHJhaW5NcykpIHsgdHJ5IHsgJGVyciA9ICRlcnJUYXNrLlJlc3VsdCB9IGNhdGNoIHt9IH0NCigkb3V0ICsgJGVycikgfCBTZXQtQ29udGVudCAtUGF0aCAkb3V0UGF0aCAtRW5jb2RpbmcgQVNDSUkNCg==" +set "HP_EXE_HINT_RERUN=IyA6ZXhlX3Ntb2tlcnVuX2hpbnRzJyBib3VuZGVkIGRpYWdub3N0aWMgcmUtcnVuIGhlbHBlci4gVW5saWtlIGV4ZV9zbW9rZXJ1bi5wczEvZmFpbGZhc3RfcHJvYmUucHMxDQojIChhY3Rpdml0eS1hd2FyZSAtLSBvbmNlIGEgcHJvY2VzcyBoYXMgcHJpbnRlZCBhbnl0aGluZywgdGhlIGtpbGwgaXMgc2tpcHBlZCBhbmQgdGhlIHdhaXQgYmVjb21lcw0KIyB1bmJvdW5kZWQsIHNpbmNlIHRob3NlIGNvdmVyIFJFQUwgdmVyaWZpY2F0aW9uIHJ1bnMgd29ydGggd2FpdGluZyBvbiksIHRoaXMgcmUtcnVuIGlzIGRpYWdub3N0aWMNCiMgT05MWTogaXRzIHNvbGUgcHVycG9zZSBpcyBhIHN0ZG91dCtzdGRlcnIgc25hcHNob3QgZm9yIHN0ZGVyciBwYXR0ZXJuLW1hdGNoaW5nIChNb2R1bGVOb3RGb3VuZEVycm9yLw0KIyBGaWxlTm90Rm91bmRFcnJvciBzaWduYXR1cmVzKSwgbmV2ZXIgc2hvd24gbGl2ZSB0byB0aGUgdXNlci4gUGFydGlhbCBvdXRwdXQgb24gYSBoYW5nIGlzIGZpbmUgYW5kDQojIHN0cmljdGx5IHByZWZlcnJlZCBvdmVyIGhhbmdpbmcgdGhlIHdob2xlIGJvb3RzdHJhcCBhIHNlY29uZCB0aW1lIG9uIGEgcnVuIG5vYm9keSBpcyB3YXRjaGluZyAtLQ0KIyBzbyB0aGUga2lsbCBoZXJlIGlzIFVOQ09ORElUSU9OQUwgYXQgdGhlIGRlYWRsaW5lLCBub3QgYWN0aXZpdHktYXdhcmUuIFNlZSBDTEFVREUubWQncyBmb3JtZXINCiMgQWN0aXZlIEJhY2tsb2cgaXRlbSAxNSAvIGRvY3MvYWdlbnQtY2xvc2VkLWJhY2tsb2cubWQgZm9yIHRoZSBnYXAgdGhpcyBjbG9zZXM6IHRoZSBwcmlvciBpbmxpbmUNCiMgIjpleGVfc21va2VydW5faGludHMiIGJvZHkgZGlkIGEgcGxhaW4sIHVudGltZWQgYCIlRU5WTkFNRSUuZXhlIiA+ICJ+ZXhlX291dC50eHQiIDI+JjFgIC0tIHRoZQ0KIyBPTkUgdXNlci1jb2RlIGxhdW5jaCBwb2ludCBpbiB0aGlzIGZpbGUgd2l0aCBubyB0aW1lb3V0IGF0IGFsbCwgb24gdGhlIHRoZW9yeSB0aGF0IGEgZ2VudWluZQ0KIyBNb2R1bGVOb3RGb3VuZEVycm9yL0ZpbGVOb3RGb3VuZEVycm9yIGFsd2F5cyBleGl0cyBpbW1lZGlhdGVseS4gVGhhdCB0aGVvcnkgaG9sZHMgZm9yIGENCiMgREVURVJNSU5JU1RJQyBmYWlsdXJlLCBidXQgdGhpcyBpcyBhIGZyZXNoIHJlLXJ1biBvZiB0aGUgc2FtZSBiaW5hcnk7IGFueSBub24tZGV0ZXJtaW5pc20gKGENCiMgcmFjZSwgYW4gZW52aXJvbm1lbnQgY2hlY2sgdGhhdCBzb21ldGltZXMgc3VjY2VlZHMsIGFueXRoaW5nIHRoYXQgb2NjYXNpb25hbGx5IGJsb2NrcyBvbg0KIyBpbmhlcml0ZWQgc3RkaW4gaW5zdGVhZCBvZiBleGl0aW5nIGZhc3QpIGNvdWxkIGhhbmcgdGhpcyBzZWNvbmQsIHVudGltZWQgaW52b2NhdGlvbiBldmVuIHRob3VnaA0KIyB0aGUgRklSU1QgaW52b2NhdGlvbiBsZWdpdGltYXRlbHkgY2xhc3NpZmllZCBhcyAiZmFzdCwgcmVhbCwgbm9uLWhhbmcgZmFpbHVyZSIuDQojDQojIFJlYWRzOiBIUF9ISU5UX1JFUlVOX0VYRSAoYmFyZSBmaWxlbmFtZTsgY2FsbGVyIHJ1bnMgdGhpcyB3aXRoIENXRCBhbHJlYWR5IHNldCB0byBkaXN0XCwgbWlycm9yaW5nDQojIGV4ZV9zbW9rZXJ1bi5wczEncyBvd24gY29udmVudGlvbikuIEhQX0hJTlRfUkVSVU5fT1VUIChkZWZhdWx0IH5leGVfb3V0LnR4dCwgcmVsYXRpdmUgdG8gZGlzdFwpDQojIGlzIHdoZXJlIGNvbWJpbmVkIHN0ZG91dCtzdGRlcnIgaXMgd3JpdHRlbiAtLSBtYXRjaGluZyB0aGUgT1JJR0lOQUwgYDI+JjFgIG1lcmdlLWludG8tb25lLWZpbGUNCiMgYmVoYXZpb3IgZXhhY3RseSwgc2luY2UgdGhlIGV4aXN0aW5nIGZpbmRzdHIgaGludC1tYXRjaGluZyBpbiA6ZXhlX3Ntb2tlcnVuX2hpbnRzIG9ubHkgY2hlY2tzIGZvcg0KIyBzdWJzdHJpbmcgcHJlc2VuY2UgaW4gdGhhdCBvbmUgZmlsZSwgbmV2ZXIgd2hpY2ggc3RyZWFtIGEgbGluZSBjYW1lIGZyb20uIEhQX0hJTlRfUkVSVU5fS0lMTF9NUw0KIyAoZGVmYXVsdCAxMDAwMCAtLSBhIGRpYWdub3N0aWMgY2FwdHVyZSBvbiBhbiBhbHJlYWR5LWZhaWxlZCBydW4gZG9lcyBub3QgbmVlZCB0aGUgZnVsbCAzMHMNCiMgcHJpbWFyeS12ZXJpZmljYXRpb24gYnVkZ2V0KSBpcyBhIHRlc3Qtb25seSBvdmVycmlkZSBwb2ludCwgbWlycm9yaW5nIEhQX1NNT0tFUlVOX0tJTExfTVMncw0KIyBlc3RhYmxpc2hlZCBwYXR0ZXJuLg0KIw0KIyBkZXJpdmVkIHJlcXVpcmVtZW50OiB0aGUgcmVzb2x2ZWQgJGtpbGxNcyBpcyBBTFdBWVMgd3JpdHRlbiB0byBIUF9ISU5UX1JFUlVOX0tJTExNU19PVVQgKGRlZmF1bHQNCiMgfmV4ZV9oaW50X2tpbGxtcy50eHQpIHJpZ2h0IGFmdGVyIGl0J3MgY29tcHV0ZWQsIHVuY29uZGl0aW9uYWxseSAtLSBwcm9kdWN0aW9uIGNhbGxlcnMgbmV2ZXIgcmVhZA0KIyB0aGlzIGZpbGUsIHNvIGl0IGNvc3RzIG5vdGhpbmcgdGhlcmUuIEl0IGV4aXN0cyBzbyB0ZXN0cyBjYW4gYXNzZXJ0IHRoZSBvdmVycmlkZSB3YXMgYWN0dWFsbHkNCiMgaG9ub3JlZCBieSByZWFkaW5nIHRoaXMgRElSRUNUIHZhbHVlLCBpbnN0ZWFkIG9mIGluZmVycmluZyBpdCBmcm9tIHdhbGwtY2xvY2sgZWxhcHNlZCB0aW1lICh3aGljaA0KIyBwcm92ZWQgdW5yZWxpYWJsZSBvbiBzaGFyZWQgcmVhbC1XaW5kb3dzIENJIHJ1bm5lcnMgLS0gdGhlIHNhbWUgdGVzdCBtZWFzdXJlZCA5LjJzLCAxMy41cywgMTQuNnMsDQojIGFuZCAxNi4zcyBvZiBvdmVyaGVhZCBhY3Jvc3MgZm91ciBkaWZmZXJlbnQgcmVhbC1DSSBydW5zIGZvciB0aGUgaWRlbnRpY2FsIDUwMG1zIG92ZXJyaWRlLCBhDQojIG1vdmluZyB0YXJnZXQgdGhhdCBubyBmaXhlZCBib3VuZCBjb3VsZCBjaGFzZTsgc2VlIGRvY3MvYWdlbnQtbGVzc29ucy1sZWFybmVkLm1kKS4NCiMNCiMgVGhpcyBpcyB0aGUgY2Fub25pY2FsIHNvdXJjZSBmb3IgdGhlIEhQX0VYRV9ISU5UX1JFUlVOIGJhc2U2NCBwYXlsb2FkIGVtYmVkZGVkIGluIHJ1bl9zZXR1cC5iYXQuDQojIEFmdGVyIGVkaXRpbmcsIHJ1biBgcHl0aG9uIHRvb2xzL3N5bmNfcGF5bG9hZC5weSBIUF9FWEVfSElOVF9SRVJVTiB0b29scy9leGVfaGludF9yZXJ1bi5wczFgOw0KIyB0ZXN0cy90ZXN0X2V4ZV9oaW50X3JlcnVuLnB5IGFzc2VydHMgdGhlIGVtYmVkZGVkIHBheWxvYWQgbWF0Y2hlcyB0aGlzIGZpbGUgKENSTEYvTEYgbm9ybWFsaXplZCwNCiMgcGVyIHRoZSAucHMxIFBheWxvYWRTeW5jIGNvbnZlbnRpb24gLS0gc2VlIGRvY3MvYWdlbnQtbGVzc29ucy1sZWFybmVkLm1kDQojICJFbWJlZGRlZCBIZWxwZXIgVXBkYXRlIFdvcmtmbG93IikuDQokZXhlID0gJGVudjpIUF9ISU5UX1JFUlVOX0VYRQ0KJG91dFBhdGggPSAkZW52OkhQX0hJTlRfUkVSVU5fT1VUDQppZiAoLW5vdCAkb3V0UGF0aCkgeyAkb3V0UGF0aCA9ICd+ZXhlX291dC50eHQnIH0NCiRraWxsTXMgPSAxMDAwMA0KaWYgKCRlbnY6SFBfSElOVF9SRVJVTl9LSUxMX01TKSB7ICRraWxsTXMgPSBbaW50XSRlbnY6SFBfSElOVF9SRVJVTl9LSUxMX01TIH0NCiRraWxsTXNPdXRQYXRoID0gJGVudjpIUF9ISU5UX1JFUlVOX0tJTExNU19PVVQNCmlmICgtbm90ICRraWxsTXNPdXRQYXRoKSB7ICRraWxsTXNPdXRQYXRoID0gJ35leGVfaGludF9raWxsbXMudHh0JyB9DQoiJGtpbGxNcyIgfCBTZXQtQ29udGVudCAtUGF0aCAka2lsbE1zT3V0UGF0aCAtRW5jb2RpbmcgQVNDSUkNCg0KJHNpID0gTmV3LU9iamVjdCBTeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2Vzc1N0YXJ0SW5mbw0KJHNpLkZpbGVOYW1lID0gJGV4ZQ0KJHNpLlVzZVNoZWxsRXhlY3V0ZSA9ICRmYWxzZQ0KJHNpLlJlZGlyZWN0U3RhbmRhcmRPdXRwdXQgPSAkdHJ1ZQ0KJHNpLlJlZGlyZWN0U3RhbmRhcmRFcnJvciA9ICR0cnVlDQokcCA9IE5ldy1PYmplY3QgU3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3MNCiRwLlN0YXJ0SW5mbyA9ICRzaQ0KJHAuU3RhcnQoKSB8IE91dC1OdWxsDQoNCiRvdXRUYXNrID0gJHAuU3RhbmRhcmRPdXRwdXQuUmVhZFRvRW5kQXN5bmMoKQ0KJGVyclRhc2sgPSAkcC5TdGFuZGFyZEVycm9yLlJlYWRUb0VuZEFzeW5jKCkNCiRleGl0ZWQgPSAkcC5XYWl0Rm9yRXhpdCgka2lsbE1zKQ0KaWYgKC1ub3QgJGV4aXRlZCkgew0KICAgICMgZGVyaXZlZCByZXF1aXJlbWVudDogUHJvY2Vzcy5LaWxsKCkgKHRoZSBwYXJhbWV0ZXJsZXNzIG92ZXJsb2FkIC0tIFdpbmRvd3MgUG93ZXJTaGVsbCA1LjENCiAgICAjIHRhcmdldHMgLk5FVCBGcmFtZXdvcmssIHdoaWNoIGhhcyBubyBQcm9jZXNzLktpbGwoZW50aXJlUHJvY2Vzc1RyZWUpIG92ZXJsb2FkOyB0aGF0J3MNCiAgICAjIC5ORVQgNSsgb25seSkgdGVybWluYXRlcyBPTkxZICRwIGl0c2VsZi4gQSBQeUluc3RhbGxlciBvbmVmaWxlIGJvb3Rsb2FkZXIgKG9yIGFueSBwcm9ncmFtKQ0KICAgICMgdGhhdCBzcGF3bnMgYSBjaGlsZCBpbmhlcml0aW5nIHRoZSByZWRpcmVjdGVkIHN0ZG91dC9zdGRlcnIgaGFuZGxlcyBjYW4gbGVhdmUgdGhhdCBjaGlsZA0KICAgICMgcnVubmluZyBhZnRlciAkcCBpcyBraWxsZWQgLS0gdGhlIHBpcGUgdGhlbiBuZXZlciByZWFjaGVzIEVPRiwgYW5kIGFuIHVuYm91bmRlZA0KICAgICMgUmVhZFRvRW5kQXN5bmMoKS5SZXN1bHQgd291bGQgaGFuZyBmb3JldmVyLCBkZWZlYXRpbmcgdGhlIGVudGlyZSBwb2ludCBvZiB0aGlzIGJvdW5kZWQNCiAgICAjIGhlbHBlci4gdGFza2tpbGwgL1QgdGVybWluYXRlcyB0aGUgd2hvbGUgcHJvY2VzcyB0cmVlLCBub3QganVzdCAkcC4gQ29uZmlybWVkIG9uIHJlYWwNCiAgICAjIFdpbmRvd3MgQ0kgKHR3byBsYW5lcyBvbiBQUiAjNDEwKTogYSBncmFuZGNoaWxkLWluaGVyaXRzLXRoZS1waXBlIHJlZ3Jlc3Npb24gdGVzdCdzDQogICAgIyByZXR1cm5jb2RlL3RpbWluZyBhc3NlcnRpb25zIHBhc3NlZCB0aGVyZSwgbWVhbmluZyB0aGUgdGFza2tpbGwgL1QgcGF0aCBpdHNlbGYgd2FzDQogICAgIyBnZW51aW5lbHkgZXhlcmNpc2VkLCBub3QganVzdCB0aGUgZmFsbGJhY2sgYmVsb3cgLS0gdGhlIGJvdW5kZWQgZmluYWwgcmVhZCBzdGlsbCBzdGF5cyBhcw0KICAgICMgYSBzZWNvbmQsIGluZGVwZW5kZW50IHNhZmV0eSBuZXQgZm9yIHdoYXRldmVyIGEgZGVzY2VuZGFudCB0YXNra2lsbCAvVCBtaWdodCBzdGlsbCBtaXNzLg0KICAgIHRyeSB7ICYgdGFza2tpbGwuZXhlIC9GIC9UIC9QSUQgJHAuSWQgMj4kbnVsbCAxPiRudWxsIH0gY2F0Y2gge30NCiAgICB0cnkgeyAkcC5LaWxsKCkgfSBjYXRjaCB7fQ0KfQ0KJHAuV2FpdEZvckV4aXQoKQ0KDQojIEJvdW5kZWQgZmluYWwgcmVhZCwgTk9UIGEgYmxpbmQgLlJlc3VsdCBibG9jazogZXZlbiBhZnRlciBraWxsaW5nIHRoZSBwcm9jZXNzIHRyZWUsIGENCiMgZGVzY2VuZGFudCB0YXNra2lsbCAvVCBkaWQgbm90IGNhdGNoIChvciBzb21lIG90aGVyIGV4b3RpYyBoYW5kbGUtaW5oZXJpdGFuY2UgZWRnZSBjYXNlKQ0KIyBzaG91bGQgbm90IGJlIGFibGUgdG8gaGFuZyB0aGlzIGRpYWdub3N0aWMtb25seSBoZWxwZXIgaW5kZWZpbml0ZWx5LiBUYXNrLldhaXQobXMpIHJldHVybnMNCiMgZmFsc2Ugb24gdGltZW91dCB3aXRob3V0IHRocm93aW5nLCBzbyBhIHN0dWNrIHBpcGUgZGVncmFkZXMgdG8gcGFydGlhbC9lbXB0eSBvdXRwdXQgaW5zdGVhZA0KIyBvZiBhbiB1bmJvdW5kZWQgd2FpdC4NCiRkcmFpbk1zID0gNTAwMA0KJG91dCA9ICcnDQokZXJyID0gJycNCmlmICgkb3V0VGFzay5XYWl0KCRkcmFpbk1zKSkgeyB0cnkgeyAkb3V0ID0gJG91dFRhc2suUmVzdWx0IH0gY2F0Y2gge30gfQ0KaWYgKCRlcnJUYXNrLldhaXQoJGRyYWluTXMpKSB7IHRyeSB7ICRlcnIgPSAkZXJyVGFzay5SZXN1bHQgfSBjYXRjaCB7fSB9DQooJG91dCArICRlcnIpIHwgU2V0LUNvbnRlbnQgLVBhdGggJG91dFBhdGggLUVuY29kaW5nIEFTQ0lJDQo=" set "HP_INSTALLER_TIMEOUT=IyBSdW5zIGFuIGV4dGVybmFsIGluc3RhbGxlciBleGVjdXRhYmxlIHdpdGggYSBnZW5lcm91cywgY29uZmlndXJhYmxlIHRpbWVvdXQgY2VpbGluZy4KIwojIENsb3NlcyBDTEFVREUubWQgQWN0aXZlIEJhY2tsb2cgaXRlbSAxNDogdGhlIHRocmVlICJzdGFydCAiIiAvd2FpdCIgZXh0ZXJuYWwtaW5zdGFsbGVyCiMgbGF1bmNoZXMgKE1pbmljb25kYSBBbGxVc2VycywgTWluaWNvbmRhIEp1c3RNZSwgTkktVklTQSkgcHJldmlvdXNseSBoYWQgbm8gcHJvY2Vzcy1sZXZlbAojIHRpbWVvdXQgYXQgYWxsLCB1bmxpa2UgdGhpcyBmaWxlJ3MgZGVsaWJlcmF0ZWx5LXdyYXBwZWQgdXNlci1jb2RlIGxhdW5jaGVzIC0tIGEgZ2VudWluZWx5CiMgc3R1Y2sgaW5zdGFsbGVyIChhIHNpbGVudGx5LWJsb2NraW5nIFVBQy9yZWJvb3QgcHJvbXB0LCBhIHdlZGdlZCBzdWItaW5zdGFsbGVyKSBjb3VsZCBoYW5nCiMgdGhlIHdob2xlIGJvb3RzdHJhcCBmb3JldmVyIHdpdGggemVybyByZWNvdXJzZS4KIwojIERlbGliZXJhdGVseSBOT1QgbW9kZWxlZCBvbiB0b29scy9leGVfc21va2VydW4ucHMxJ3MgfjMwcyBhZ2dyZXNzaXZlIGtpbGwgd2luZG93IC0tIHRoZXNlIGFyZQojIFJFQUwgaW5zdGFsbGVyIHByb2Nlc3NlcywgYW5kIGtpbGxpbmcgb25lIHRvbyBlYXJseSAod2hpbGUgaXQncyBzdGlsbCBsZWdpdGltYXRlbHkgd3JpdGluZwojIGZpbGVzL3JlZ2lzdHJ5IGtleXMpIHJpc2tzIGEgd29yc2Ugb3V0Y29tZSB0aGFuIGEgc2xvdy1idXQtc3VjY2VlZGluZyBpbnN0YWxsOiBhIGdlbnVpbmVseQojIGNvcnJ1cHRlZCBoYWxmLWluc3RhbGxlZCB0YXJnZXQuIFRoZSB0aW1lb3V0IGhlcmUgaXMgYSBnZW5lcm91cyBzYWZldHkgQ0VJTElORyBhZ2FpbnN0IGEgdHJ1bHkKIyBodW5nIHByb2Nlc3MsIG5vdCBhIHJlc3BvbnNpdmVuZXNzIGNoZWNrIC0tIHNlZSB0aGUgcGVyLWNhbGwtc2l0ZSB0aW1lb3V0IHZhbHVlcyBpbgojIHJ1bl9zZXR1cC5iYXQncyBvd24gY29tbWVudHMgZm9yIHRoZSByZWFsLXdvcmxkIHJlc2VhcmNoIGJlaGluZCBlYWNoIG51bWJlciAoZG9jdW1lbnRlZAojIE1pbmljb25kYS9OSS1WSVNBIGluc3RhbGwtdGltZSByZXBvcnRzLCBub3QgZ3Vlc3NlcykuCiMKIyBJbnB1dHMgdmlhIGVudiB2YXJzIChhdm9pZHMgY21kLmV4ZSBxdW90aW5nIGhhemFyZHMsIG1hdGNoZXMgfmZhaWxmYXN0X3Byb2JlLnBzMSdzIGNvbnRyYWN0KToKIyAgIEhQX0lOU1RBTExFUl9FWEUgICAgICAgICAgLSBwYXRoIHRvIHRoZSBpbnN0YWxsZXIgZXhlY3V0YWJsZS4KIyAgIEhQX0lOU1RBTExFUl9BUkdTICAgICAgICAgLSBhIHNpbmdsZSwgYWxyZWFkeS1wcmVwYXJlZCBBcmd1bWVudHMgc3RyaW5nIChjYWxsZXIncwojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXNwb25zaWJpbGl0eSB0byBxdW90ZS9qb2luIHRva2VuczsgbWF0Y2hlcyBIUF9QUk9CRV9BUkdTJ3MKIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3duICJjYWxsZXIgcHJvdmlkZXMgYSByZWFkeSBzdHJpbmciIGNvbnRyYWN0KS4KIyAgIEhQX0lOU1RBTExFUl9USU1FT1VUX01TICAgLSB0aW1lb3V0IGluIG1pbGxpc2Vjb25kcyBiZWZvcmUgdGhlIHByb2Nlc3MgaXMgZm9yY2Uta2lsbGVkLgojICAgSFBfSU5TVEFMTEVSX1JFU1VMVCAgICAgICAtIG91dHB1dCByZXN1bHQgZmlsZSBwYXRoIChkZWZhdWx0IH5pbnN0YWxsZXJfcmVzdWx0LnR4dCksCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdyaXR0ZW4gYXMgIjxleGl0Y29kZT58PHRpbWVkb3V0OjAvMT4iLgojCiMgVXNlU2hlbGxFeGVjdXRlPSR0cnVlIChub3QgJGZhbHNlKSBpcyBkZWxpYmVyYXRlOiBpdCBwcmVzZXJ2ZXMgdGhlIHNhbWUgVUFDLWVsZXZhdGlvbi12aWEtCiMgbWFuaWZlc3QgYmVoYXZpb3IgInN0YXJ0ICIiIC93YWl0IiBhbHJlYWR5IGhhZCAoU2hlbGxFeGVjdXRlIGlzIHdoYXQgc3VwcG9ydHMgYW4gZXhlJ3Mgb3duCiMgInJlcXVpcmVBZG1pbmlzdHJhdG9yIiBtYW5pZmVzdCBzaGltIHRyYW5zcGFyZW50bHkpLiBObyBzdGRvdXQvc3RkZXJyIHJlZGlyZWN0aW9uIGlzIG5lZWRlZCAtLQojIHRoZXNlIGluc3RhbGxlcnMgcnVuIHNpbGVudGx5ICgvUywgLS1xdWlldCkgLS0gc28gdGhpcyBkb2Vzbid0IGNvc3QgYW55dGhpbmcgbXkgY2FsbGVyIG5lZWRzLgojCiMgT24gdGltZW91dCwgdXNlcyB0YXNra2lsbCAvRiAvVCAobm90ICRwLktpbGwoKSkgZGVsaWJlcmF0ZWx5OiBXaW5kb3dzIFBvd2VyU2hlbGwgNS4xJ3MKIyBTeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2Vzcy5LaWxsKCkgKC5ORVQgRnJhbWV3b3JrKSBoYXMgbm8gImtpbGwgdGhlIHdob2xlIHByb2Nlc3MgdHJlZSIKIyBvdmVybG9hZCAtLSB0aGF0J3MgYSAuTkVUIENvcmUgMy4wKyBhZGRpdGlvbiB0aGlzIHJ1bnRpbWUgZG9lc24ndCBoYXZlIC0tIHNvIGl0IHdvdWxkIG9ubHkKIyBraWxsIHRoZSBkaXJlY3QgcHJvY2VzcywgcG90ZW50aWFsbHkgb3JwaGFuaW5nIGNoaWxkIHN1Yi1pbnN0YWxsZXIgcHJvY2Vzc2VzICh0aGlzIHJlcG8ncyBvd24KIyBOSS1WSVNBIGNvbW1lbnQgYWxyZWFkeSBub3RlcyAiTkkgaW5zdGFsbGVycyBtYXkgc3Bhd24gY2hpbGQgcHJvY2Vzc2VzIikuIHRhc2traWxsJ3MgL1QgZmxhZwojIGtpbGxzIHRoZSBmdWxsIHByb2Nlc3MgdHJlZSByZWdhcmRsZXNzIG9mIC5ORVQgcnVudGltZSB2ZXJzaW9uLgojCiMgVGhpcyBpcyB0aGUgY2Fub25pY2FsIHNvdXJjZSBmb3IgdGhlIEhQX0lOU1RBTExFUl9USU1FT1VUIGJhc2U2NCBwYXlsb2FkIGVtYmVkZGVkIGluCiMgcnVuX3NldHVwLmJhdC4gQWZ0ZXIgZWRpdGluZywgcmUtc3luYyB3aXRoIHRvb2xzL3N5bmNfcGF5bG9hZC5weTsgdGVzdHMvdGVzdF9ydW5faW5zdGFsbGVyXwojIHdpdGhfdGltZW91dC5weSBhc3NlcnRzIHRoZSBlbWJlZGRlZCBwYXlsb2FkIG1hdGNoZXMgdGhpcyBmaWxlIChDUkxGL0xGIG5vcm1hbGl6ZWQsIHBlciB0aGUKIyAucHMxIFBheWxvYWRTeW5jIGNvbnZlbnRpb24pLgojCiMgZGVyaXZlZCByZXF1aXJlbWVudDogYSByZWFsIFdpbmRvd3MgQ0kgcnVuIGNhdWdodCBhIGdlbnVpbmUgYnVnIHRoaXMgcmVwbydzIG90aGVyIGVtaXR0ZWQKIyAucHMxIGhlbHBlcnMgbmV2ZXIgaGl0OiB3aGVuIGEgbmF0aXZlIEVYRSBpbnZva2VkIHZpYSB0aGUgIiYiIGNhbGwgb3BlcmF0b3IgKHRhc2traWxsLmV4ZQojIGhlcmUgLS0gdGhlIE9OTFkgbmF0aXZlLWNvbW1hbmQgaW52b2NhdGlvbiBpbiB0aGlzIGZpbGU7IGV4ZV9zbW9rZXJ1bi5wczEvZmFpbGZhc3RfcHJvYmUucHMxCiMgb25seSBldmVyIGxhdW5jaCB0aGVpciBtb25pdG9yZWQgcHJvY2VzcyB2aWEgLk5FVCdzIFByb2Nlc3MgQVBJLCB3aGljaCBkb2VzIG5vdCBzZXQKIyAkTEFTVEVYSVRDT0RFKSBzZXRzICRMQVNURVhJVENPREUgdG8gYSBub256ZXJvIHZhbHVlLCBwd3NoIC1GaWxlIHNpbGVudGx5IGluaGVyaXRzIHRoYXQgYXMKIyBJVFMgT1dOIHByb2Nlc3MgZXhpdCBjb2RlIHdoZW4gdGhlIHNjcmlwdCBlbmRzIHdpdGhvdXQgYW4gZXhwbGljaXQgImV4aXQiIHN0YXRlbWVudCAtLSBldmVuCiMgdGhvdWdoIHRhc2traWxsIHdhcyBub3QgdGhlIGxhc3Qgc3RhdGVtZW50IGV4ZWN1dGVkIGFuZCBpdHMgb3duIGZhaWx1cmUgd2FzIGFscmVhZHkgY2F1Z2h0CiMgYW5kIHN3YWxsb3dlZCBieSB0cnkvY2F0Y2guIFRoaXMgaXMgYSB3ZWxsLWtub3duIFBvd2VyU2hlbGwgZ290Y2hhLCBub3Qgc3BlY2lmaWMgdG8gdGFza2tpbGw6CiMgYW55IGVhcmxpZXIgbmF0aXZlLWNvbW1hbmQgZmFpbHVyZSBjYW4gbGVhayB0aHJvdWdoIGFzIHRoZSB3aG9sZSBzY3JpcHQncyBleGl0IGNvZGUgdW5sZXNzCiMgZXhwbGljaXRseSByZXNldC4gdGFza2tpbGwgY2FuIGxlZ2l0aW1hdGVseSByZXR1cm4gbm9uemVybyBoZXJlIChlLmcuIHRoZSB0aW1lZC1vdXQgcHJvY2VzcwojIGFscmVhZHkgZXhpdGVkIG9uIGl0cyBvd24gaW4gdGhlIHJhY2UgYmV0d2VlbiBXYWl0Rm9yRXhpdCh0aW1lb3V0TXMpIHJldHVybmluZyBmYWxzZSBhbmQKIyB0YXNra2lsbCBhY3R1YWxseSBydW5uaW5nKSB3aXRob3V0IHRoYXQgYmVpbmcgYSByZWFsIHByb2JsZW0gLS0gdGhlIHJlc3VsdCBGSUxFIChub3QgdGhlCiMgc2NyaXB0J3Mgb3duIHByb2Nlc3MgZXhpdCBjb2RlKSBpcyB0aGlzIHNjcmlwdCdzIHJlYWwgY29udHJhY3Qgd2l0aCBpdHMgY2FsbGVyLCBzbyB0aGUgZml4IGlzCiMgYW4gZXhwbGljaXQgImV4aXQgMCIgYXMgdGhlIHNjcmlwdCdzIGxhc3Qgc3RhdGVtZW50LCBndWFyYW50ZWVpbmcgcHdzaCdzIG93biBleGl0IGNvZGUgaXMKIyBhbHdheXMgY2xlYW4gcmVnYXJkbGVzcyBvZiB3aGF0ICRMQVNURVhJVENPREUgaGFwcGVuZWQgdG8gYmUgbGVmdCBob2xkaW5nLgokZXhlID0gJGVudjpIUF9JTlNUQUxMRVJfRVhFCiRhcmdTdHIgPSAkZW52OkhQX0lOU1RBTExFUl9BUkdTCiR0aW1lb3V0TXMgPSBbaW50XSRlbnY6SFBfSU5TVEFMTEVSX1RJTUVPVVRfTVMKJHJlc3VsdFBhdGggPSAkZW52OkhQX0lOU1RBTExFUl9SRVNVTFQKaWYgKC1ub3QgJHJlc3VsdFBhdGgpIHsgJHJlc3VsdFBhdGggPSAnfmluc3RhbGxlcl9yZXN1bHQudHh0JyB9Cgokc2kgPSBOZXctT2JqZWN0IFN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzU3RhcnRJbmZvCiRzaS5GaWxlTmFtZSA9ICRleGUKaWYgKCRhcmdTdHIpIHsgJHNpLkFyZ3VtZW50cyA9ICRhcmdTdHIgfQokc2kuVXNlU2hlbGxFeGVjdXRlID0gJHRydWUKJHAgPSBOZXctT2JqZWN0IFN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzCiRwLlN0YXJ0SW5mbyA9ICRzaQokcC5TdGFydCgpIHwgT3V0LU51bGwKCmlmICgkcC5XYWl0Rm9yRXhpdCgkdGltZW91dE1zKSkgewogICAgIiQoJHAuRXhpdENvZGUpfDAiIHwgU2V0LUNvbnRlbnQgLVBhdGggJHJlc3VsdFBhdGggLUVuY29kaW5nIEFTQ0lJCn0gZWxzZSB7CiAgICB0cnkgeyAmIHRhc2traWxsLmV4ZSAvRiAvVCAvUElEICRwLklkIDI+JG51bGwgfCBPdXQtTnVsbCB9IGNhdGNoIHt9CiAgICAiMXwxIiB8IFNldC1Db250ZW50IC1QYXRoICRyZXN1bHRQYXRoIC1FbmNvZGluZyBBU0NJSQp9CmV4aXQgMAo=" :: --- Embedded helper: HP_PREP_REQUIREMENTS (~prep_requirements.py) --- :: Purpose: diff --git a/tools/exe_hint_rerun.ps1 b/tools/exe_hint_rerun.ps1 index 5450f65c..071a222a 100644 --- a/tools/exe_hint_rerun.ps1 +++ b/tools/exe_hint_rerun.ps1 @@ -64,10 +64,11 @@ if (-not $exited) { # that spawns a child inheriting the redirected stdout/stderr handles can leave that child # running after $p is killed -- the pipe then never reaches EOF, and an unbounded # ReadToEndAsync().Result would hang forever, defeating the entire point of this bounded - # helper. taskkill /T terminates the whole process tree, not just $p. NOT independently - # verified on real Windows CI that a genuine descendant-holds-the-pipe scenario is fully - # covered by this (no Windows environment available to construct that repro) -- the bounded - # final read below is a second, independent safety net for exactly that residual risk. + # helper. taskkill /T terminates the whole process tree, not just $p. Confirmed on real + # Windows CI (two lanes on PR #410): a grandchild-inherits-the-pipe regression test's + # returncode/timing assertions passed there, meaning the taskkill /T path itself was + # genuinely exercised, not just the fallback below -- the bounded final read still stays as + # a second, independent safety net for whatever a descendant taskkill /T might still miss. try { & taskkill.exe /F /T /PID $p.Id 2>$null 1>$null } catch {} try { $p.Kill() } catch {} } From f8ac851a0c59da0d6b8fae47d583e04a81b9b946 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 11:11:34 +0000 Subject: [PATCH 2/7] Address CodeRabbit review: fix stale TOC anchors, MD031, wording, add >9-picker test - Fix Part VI's own local scenario index: 4 links still pointed at the pre-reorg anchors (#scenario-34.. through #scenario-37..) even though their visible text had already been renumbered to 28-31. The main table of contents was verified programmatically during the reorg; this local bulleted list inside Part VI's scope note was a second, separate index the check didn't cover. Verified no other stale anchors exist anywhere else in the file via a full anchor-vs-header scan. - Fix 2 markdownlint MD031 violations (missing blank lines around fences). - Trim a residual mechanism claim from the UNC-check writeup (the removed check's exact findstr/backslash-parsing behavior was never independently verified, per an existing CodeRabbit learning from PR #408). - Extend the sourcing-convention paragraph to explicitly cover Part VII's composite walkthroughs (spliced from independently-real fragments) as a third case, distinct from a verbatim capture or a source-only quote. - Fix run_setup.bat's new >9-candidates Tip wording: "to avoid the alphabetical fallback next time" instead of "to skip this question next time" -- no question was actually asked in that branch. - Add tests/selfapps_entry_picker.ps1's second scenario (self.entry.picker. overflow): stages 10 candidate files, asserts the numbered menu is skipped, the overflow log line and Tip guidance both fire, and the alphabetical default is kept. Closes the CI-coverage gap the demo doc itself flagged. Registered in docs/agent-ndjson.md. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW --- docs/agent-ndjson.md | 1 + docs/demo-bootstrapper-output.md | 40 +++++++++++-------- run_setup.bat | 2 +- tests/selfapps_entry_picker.ps1 | 67 ++++++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+), 18 deletions(-) diff --git a/docs/agent-ndjson.md b/docs/agent-ndjson.md index 00a1fe30..fb525c9d 100644 --- a/docs/agent-ndjson.md +++ b/docs/agent-ndjson.md @@ -27,6 +27,7 @@ self.failfast.probe.fastfail, self.failfast.probe.alive, self.failfast.probe, self.checkpoint.accept, self.checkpoint.decline, self.entry.entry1, self.entry.entryA, self.entry.entryB, self.entry.entryC, self.entry.entryD, self.entry.helper.invoke.absent, self.entry.results, self.entry.spaced-path, self.entry.picker, +self.entry.picker.overflow, self.entry.req011.crossdir, self.entry.req011.sameDir, self.isolation.req010.pythonpath, entry.single.direct, entry.expected, helper.invoke, self.envname.hyphen, self.size.tripwire, diff --git a/docs/demo-bootstrapper-output.md b/docs/demo-bootstrapper-output.md index b1e42a3a..91dcf7d7 100644 --- a/docs/demo-bootstrapper-output.md +++ b/docs/demo-bootstrapper-output.md @@ -25,9 +25,12 @@ distinction is between explaining what the reader is looking at right now versus this document itself was assembled. **Sourcing convention:** every quoted block is either copied verbatim from a real GitHub Actions -job log (cited with run ID, job ID, lane, and test file) or, where noted, taken directly from -`run_setup.bat`'s current source because no CI run has exercised that exact wording yet -- always -labeled explicitly which case applies, never presented as a real capture when it isn't. +job log (cited with run ID, job ID, lane, and test file), 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 -- assembled by splicing several independently-real fragments together, +each already cited in its own originating scenario, with the splice itself called out as +`[Extrapolated Branch]`. Always labeled explicitly which case applies, never presented as a single +real capture when it isn't. **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 @@ -184,9 +187,8 @@ Tue 07/28/2026 4:29:43.96 [INFO] REQ-015: Appending standard ignores to .gitign Before that 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 -- see `docs/agent-lessons-learned.md` -for the C-runtime backslash-before-quote parsing hazard a check like this has to get right) are all -silent on an ordinary, non-UNC path. +UNC/network paths detected...` banner when it genuinely fires) are all silent on an ordinary, +non-UNC path. **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 @@ -1759,10 +1761,10 @@ control-flow plumbing with no independently observable behavior of its own (e.g. `:mgc_gi_done`), or a narrow edge case not worth a dedicated scenario (e.g. `:cascade_consent_no_ choice_exe`, reached only on a Windows image stripped of `choice.exe`). -- [Scenario 28: Interactive entry picker -- multiple `.py` files, no clear winner (REQ-002)](#scenario-34-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-35-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-36-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-37-exe-smoke-run-diagnostic-hints-companion-to-scenario-22) +- [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) --- @@ -1809,13 +1811,14 @@ a number, or simply waiting, never sees that line. Either way, the resolution is If MORE than 9 candidate files exist, the picker's own numbered menu is skipped entirely (no menu can address more than the `123456789` `choice /C` charset) and the alphabetical pick is kept, logged -as `[INFO] REQ-002: candidates exceed picker limit; keeping (alphabetical).`. The Tip -block (the same three-item list shown above -- drag-and-drop, a preferred filename, or a -`__main__` block) still prints right after that log line, since it's exactly the guidance a user -who just hit a >9-file folder needs most to avoid landing here again -- this used to be silently -skipped along with the rest of the menu, fixed in the same pass that wrote this scenario. -`[Extrapolated Branch]`, cited from `:pick_entry_interactive`, not independently captured in CI -(would need a 10th-plus stub `.py` file staged, which no current test does). +as `[INFO] REQ-002: candidates exceed picker limit; keeping (alphabetical).`. The same +three-item Tip list shown above (drag-and-drop, a preferred filename, or a `__main__` block) still +prints right after that log line -- worded "to avoid the alphabetical fallback next time" here, +since no question was actually asked to skip -- because it's exactly the guidance a user who just +hit a >9-file folder needs most to avoid landing here again; this used to be silently skipped along +with the rest of the menu, fixed in the same pass that wrote this scenario. `[Extrapolated Branch]`, +cited from `:pick_entry_interactive`, not independently captured in CI (would need a 10th-plus stub +`.py` file staged -- a real coverage gap, not yet a dedicated test). --- @@ -2446,9 +2449,11 @@ used to (before this fix) trigger an incorrect PyInstaller rebuild attempt again EXE. **Source:** confirmed in real CI run `29877805447`, uv lane, job `88792048278`: + ``` {"details":{"appStdoutFound":true,"noRepairRebuild":true,"successLogged":true,"skipGuardLogged":true,"exeExists":true,"statusState":"ok","bootstrapExit":0,"smokerunNonzeroLogged":true,"attemptLogged":true,"log":"~nuitka_tiera_hidden_skip_bootstrap.log"},"req":"REQ-AV","pass":true,"desc":"AV-Safe Build Path Tier A: hidden-import auto-recovery correctly skips (never rebuilds via PyInstaller) against a Nuitka-built EXE","id":"self.exe.tiera.hidden_skip","lane":"uv"} ``` + The test only dumps a full console log to CI when a scenario fails; since this one passes, the exact console text below is reconstructed from `run_setup.bat`'s source rather than copied from a console dump -- the NDJSON row's `skipGuardLogged`/`noRepairRebuild` fields are the test's own @@ -2477,6 +2482,7 @@ non-gating), four scenarios sharing one row id. **Source:** confirmed in real CI run `29877805447`, uv lane, job `88792048278`, all four scenarios passing: + ``` {"lane":"uv","details":{"log":"~optbuild_accept_bootstrap.log","statusState":"ok","scenario":"accept","successLogged":true,"promptShown":true,"tmpExeGone":true,"exeExists":true,"bootstrapExit":0,"acceptedLogged":true,"appStillRuns":true},"desc":"AV-Safe Build Path requirement 9 (accept): a real optimized build succeeds, verifies, and is swapped into place","req":"REQ-AV","id":"self.optbuild.offer","pass":true} {"req":"REQ-AV","lane":"uv","desc":"AV-Safe Build Path requirement 9 (forcefail): a failed optimized build leaves the original PyInstaller EXE completely untouched","id":"self.optbuild.offer","details":{"originalStillRuns":true,"bootstrapExit":0,"log":"~optbuild_forcefail_bootstrap.log","tmpExeGone":true,"promptShown":true,"exeExists":true,"testHookFired":true,"scenario":"forcefail","statusState":"ok","noSuccessMsg":true},"pass":true} diff --git a/run_setup.bat b/run_setup.bat index 9a4da7b2..2fee7ddb 100644 --- a/run_setup.bat +++ b/run_setup.bat @@ -2642,7 +2642,7 @@ for /f "usebackq delims=" %%F in ("~entry.menu") do set /a HP_PICK_N+=1 if %HP_PICK_N% LSS 2 ( del "~entry.menu" >nul 2>&1 & exit /b 0 ) if %HP_PICK_N% GTR 9 ( call :log "[INFO] REQ-002: %HP_PICK_N% candidates exceed picker limit; keeping %HP_ENTRY% (alphabetical)." - echo Tip: to skip this question next time, do any one of these: + echo Tip: to avoid the alphabetical fallback next time, do any one of these: echo 1. Drag a .py file onto run_setup.bat -- drop it on the batch file icon to run echo that file directly. It must be in this same folder. echo 2. Rename your main script to one of: main.py, app.py, run.py, or cli.py. diff --git a/tests/selfapps_entry_picker.ps1 b/tests/selfapps_entry_picker.ps1 index 3a7dd2c5..8758dae8 100644 --- a/tests/selfapps_entry_picker.ps1 +++ b/tests/selfapps_entry_picker.ps1 @@ -10,6 +10,14 @@ # test verifies the picker runs, shows the menu, and resolves to the default without # hanging. True human selection needs a console and is out of CI scope. # +# Second scenario (self.entry.picker.overflow): 10 non-PREFERRED, no-__main__ files -- +# one past :pick_entry_interactive's own >9 GTR-check limit (choice /C only supports the +# 123456789 charset). Asserts the numbered menu is skipped entirely (no +# "Multiple Python files detected" prompt), the overflow log line and its Tip guidance +# both print, and the alphabetically-first file is still chosen -- closes the gap flagged +# in docs/demo-bootstrapper-output.md's picker scenario ("a real coverage gap, not yet a +# dedicated test"). +# # Lane: conda-full only (one real bootstrap; behavior is provider-independent). param() $ErrorActionPreference = 'Continue' @@ -92,4 +100,63 @@ Write-NdjsonRow ([ordered]@{ }) if (-not $pass) { exit 1 } + +# --- Second scenario: >9 candidates -> numbered menu skipped, Tip guidance still shown --- +$overflowDir = Join-Path $here '~selftest_entry_picker_overflow' +if (Test-Path -LiteralPath $overflowDir) { Remove-Item -LiteralPath $overflowDir -Recurse -Force } +New-Item -ItemType Directory -Force -Path $overflowDir | Out-Null +Copy-Item -LiteralPath (Join-Path $repo 'run_setup.bat') -Destination $overflowDir -Force +1..10 | ForEach-Object { + $letter = [char](96 + $_) + Set-Content -LiteralPath (Join-Path $overflowDir "${letter}_app.py") -Value "print('entry-$letter')`n" -Encoding ASCII +} + +$prevForce2 = if (Test-Path Env:HP_TEST_FORCE_PICKER) { $env:HP_TEST_FORCE_PICKER } else { $null } +$prevPip2 = if (Test-Path Env:HP_SKIP_PIPREQS) { $env:HP_SKIP_PIPREQS } else { $null } +$env:HP_TEST_FORCE_PICKER = '1' +$env:HP_SKIP_PIPREQS = '1' +$overflowLog = '~entry_picker_overflow_bootstrap.log' +$overflowExit = -1 +Push-Location -LiteralPath $overflowDir +try { + cmd /c "call run_setup.bat > $overflowLog 2>&1" + $overflowExit = $LASTEXITCODE +} finally { + Pop-Location + if ($null -eq $prevForce2) { Remove-Item Env:HP_TEST_FORCE_PICKER -ErrorAction SilentlyContinue } else { $env:HP_TEST_FORCE_PICKER = $prevForce2 } + if ($null -eq $prevPip2) { Remove-Item Env:HP_SKIP_PIPREQS -ErrorAction SilentlyContinue } else { $env:HP_SKIP_PIPREQS = $prevPip2 } +} + +$overflowLogPath = Join-Path $overflowDir $overflowLog +$overflowSetupLog = Join-Path $overflowDir '~setup.log' +$overflowLogText = if (Test-Path $overflowLogPath) { Get-Content -LiteralPath $overflowLogPath -Raw -Encoding ASCII } else { '' } +$overflowSetupTxt = if (Test-Path $overflowSetupLog) { Get-Content -LiteralPath $overflowSetupLog -Raw -Encoding ASCII } else { '' } +$overflowCombined = $overflowLogText + "`n" + $overflowSetupTxt + +$overflowMenuAbsent = $overflowCombined -notmatch 'Multiple Python files detected' +# :pick_entry_interactive logs %HP_ENTRY% directly here, unlike :record_chosen_entry's separate +# "Chosen entry:" line -- HP_ENTRY at this point is find_entry.py's own bare-filename stdout +# (os.path.normpath() on a bare name adds no ".\" prefix), so no prefix is expected below. +$overflowLimitLogged = $overflowCombined -match [regex]::Escape('candidates exceed picker limit; keeping a_app.py (alphabetical)') +$overflowTipShown = $overflowCombined -match [regex]::Escape('Tip: to avoid the alphabetical fallback next time') +$overflowChosenDefault = $overflowCombined -match 'Chosen entry:.*a_app\.py' + +$overflowPass = ($overflowExit -eq 0) -and $overflowMenuAbsent -and $overflowLimitLogged -and $overflowTipShown -and $overflowChosenDefault + +Write-NdjsonRow ([ordered]@{ + id='self.entry.picker.overflow' + req='REQ-002' + pass=$overflowPass + desc='More than 9 candidate files skips the numbered menu entirely but still shows Tip guidance, keeping the alphabetical default' + details=[ordered]@{ + exitCode = $overflowExit + menuAbsent = $overflowMenuAbsent + limitLogged = $overflowLimitLogged + tipShown = $overflowTipShown + chosenDefault = $overflowChosenDefault + log = $overflowLog + } +}) + +if (-not $overflowPass) { exit 1 } exit 0 From f457cdcc9f12e1e18d53f3862889f3ae79941390 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 11:18:28 +0000 Subject: [PATCH 3/7] Address second CodeRabbit review round: sourcing labels, stale coverage note - Sourcing convention: use explicit REAL CI CAPTURE / source-excerpt / [Extrapolated Branch] labels instead of prose that called a source-only excerpt a "job log" -- a source excerpt is not a CI capture and shouldn't be described as one. - Scenario 28's >9-candidates paragraph still said "a real coverage gap, not yet a dedicated test" after the previous commit had already added self.entry.picker.overflow -- fixed to point at the real test/NDJSON registration while keeping [Extrapolated Branch] for the exact console text (that test only dumps a full log to CI on failure). Declined (reason given inline, no code change): CodeRabbit's outside-diff suggestion to add an explicit `exit 0` to tools/exe_hint_rerun.ps1. Its caller (run_setup.bat's EXE-hint-rerun call site) never checks the PowerShell process's own exit code, and three commands run before the first `if errorlevel` check that matters afterward (each of which resets ERRORLEVEL), so the omission is provably inconsequential. Neither sibling helper (exe_smokerun.ps1, failfast_probe.ps1) has an explicit exit code either -- adding one here alone would be inconsistent with an established, already-shipped pattern rather than fixing a real gap. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW --- docs/demo-bootstrapper-output.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/demo-bootstrapper-output.md b/docs/demo-bootstrapper-output.md index 91dcf7d7..78e6a9a7 100644 --- a/docs/demo-bootstrapper-output.md +++ b/docs/demo-bootstrapper-output.md @@ -24,13 +24,13 @@ the kind of content this doc wants (see Scenario 29, Scenario 39 for two kept in distinction is between explaining what the reader is looking at right now versus narrating how this document itself was assembled. -**Sourcing convention:** every quoted block is either copied verbatim from a real GitHub Actions -job log (cited with run ID, job ID, lane, and test file), 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 -- assembled by splicing several independently-real fragments together, -each already cited in its own originating scenario, with the splice itself called out as -`[Extrapolated Branch]`. Always labeled explicitly which case applies, never presented as a single -real capture when it isn't. +**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 @@ -1816,9 +1816,13 @@ three-item Tip list shown above (drag-and-drop, a preferred filename, or a `__ma prints right after that log line -- worded "to avoid the alphabetical fallback next time" here, since no question was actually asked to skip -- because it's exactly the guidance a user who just hit a >9-file folder needs most to avoid landing here again; this used to be silently skipped along -with the rest of the menu, fixed in the same pass that wrote this scenario. `[Extrapolated Branch]`, -cited from `:pick_entry_interactive`, not independently captured in CI (would need a 10th-plus stub -`.py` file staged -- a real coverage gap, not yet a dedicated test). +with the rest of the menu, fixed in the same pass that wrote this scenario. `[Extrapolated Branch]` +for the exact console text, cited from `:pick_entry_interactive` -- the branch itself is now +covered by `self.entry.picker.overflow` (`tests/selfapps_entry_picker.ps1`, `conda-full` lane, +registered in `docs/agent-ndjson.md`), which stages 10 candidate files and asserts the overflow log +line, the Tip guidance, and the alphabetical default all fire correctly; that test only dumps a +full console log to CI on failure, so the exact text below is still not an independently captured +console dump. --- From 2593d90998054352e04eab8a6468a02169f6b1d0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 12:33:34 +0000 Subject: [PATCH 4/7] Make cache-lane self-heal regression-testable and its outcome visible Owner asked whether Item 19's cache self-heal fix is holding, then asked for a way to make a regression in it actually surface -- not silently absorbed. Digging into the first real post-fix run's raw job log found direct proof the fix works (corrupted-prefix-match cache -> deleted -> fresh install -> fresh save, all in one run), but also confirmed two real gaps: nothing recorded whether a run's self-heal fired at all, and even a hard failure inside the `cache` lane could never surface as a real CI failure (job-level continue-on-error covers that whole lane). - Extract the inline health-check-and-heal PowerShell out of batch-check.yml into tools/ci_cache_selfheal.ps1, a small parameterized script with 4 distinct exit codes -- including a new one for "self-heal itself failed to clear the stale directory" (the regression case: this fix reverting to its own pre-fix trap). - Add tests/test_ci_cache_selfheal.ps1: a deterministic test exercising all 4 outcomes against a scratch temp directory (including a genuine locked-file reproduction of the heal-failure case), wired into the `real` lane -- a GATING lane -- so a regression here fails CI for real, unlike the ambient `cache` lane which structurally cannot. - Add self.cache.selfheal.fired: an always-emitted visibility row when the ambient cache lane's own self-heal branch fires, recording whether it actually succeeded -- queryable on the diagnostics site instead of requiring a raw-log dig to notice an organic occurrence happened. - Add a loud ::error:: tripwire for the self-heal-failed case, mirroring the existing diag.conda.available.gate pattern -- explicitly documented as not a substitute for the gating test above. - Document the investigation and the fix in docs/agent-closed-backlog.md's Item 19 entry (in place, not appended), including the exact log lines that proved the original fix fired and worked. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW --- .github/workflows/batch-check.yml | 114 ++++++++++++++++--------- docs/agent-closed-backlog.md | 46 ++++++++++ docs/agent-ndjson.md | 29 ++++++- tests/test_ci_cache_selfheal.ps1 | 136 ++++++++++++++++++++++++++++++ tools/ci_cache_selfheal.ps1 | 69 +++++++++++++++ 5 files changed, 351 insertions(+), 43 deletions(-) create mode 100644 tests/test_ci_cache_selfheal.ps1 create mode 100644 tools/ci_cache_selfheal.ps1 diff --git a/.github/workflows/batch-check.yml b/.github/workflows/batch-check.yml index f481558b..37cc0701 100644 --- a/.github/workflows/batch-check.yml +++ b/.github/workflows/batch-check.yml @@ -95,48 +95,24 @@ jobs: env: HP_CACHE_EXACT_HIT: ${{ steps.conda_cache_restore.outputs.cache-hit }} run: | - $condaMain = 'C:\Users\Public\Documents\Miniconda3\condabin\conda.bat' - $condaAlt = 'C:\Users\Public\Documents\Miniconda3\Scripts\conda.bat' - $condaBat = if (Test-Path $condaMain) { $condaMain } elseif (Test-Path $condaAlt) { $condaAlt } else { $null } - if ($null -eq $condaBat) { - Write-Host "No conda binary found; fresh install will proceed normally." - } else { - $output = & cmd /c "`"$condaBat`" info" 2>&1 - if ($LASTEXITCODE -ne 0) { - if ($env:HP_CACHE_EXACT_HIT -eq 'true') { - # derived requirement: an EXACT primary-key hit that's corrupted can never be - # replaced in place (a GitHub Actions cache entry's blob is immutable once saved - # under a key) -- see docs/agent-closed-backlog.md's Item 19 (cache-lane - # self-perpetuating-corruption) for the full trace. Keep the original - # skip-this-run behavior for this narrow case; fully closing it needs an explicit - # cache-deletion API call, a smaller follow-on not implemented here. - Write-Host "::warning::Conda binary health check failed (exit=$LASTEXITCODE) on an EXACT cache-key hit; cache corrupted, skipping fast-path tests this run." - 'HP_CACHE_CORRUPTED=1' | Out-File -FilePath $env:GITHUB_ENV -Encoding ascii -Append - } else { - # derived requirement: a restore-keys PREFIX match (the common case -- the cache - # key hashes run_setup.bat, which changes on nearly every PR) is not a guarantee - # the restored blob is still valid. Treating this like HP_CACHE_CORRUPTED=1 was - # the actual bug: the ONLY step that can do a fresh install is gated on that same - # flag, so a poisoned blob could never be replaced -- a permanent, self- - # perpetuating loop. Fix: treat it like a genuine cache miss instead -- delete the - # stale directory and let the run fall through to a real fresh install and a real - # fresh save under the current key, breaking the loop. - Write-Host "::warning::Conda binary health check failed (exit=$LASTEXITCODE) on a restore-keys prefix match; deleting stale cache directory and proceeding as a fresh install." - Remove-Item -LiteralPath 'C:\Users\Public\Documents\Miniconda3' -Recurse -Force -ErrorAction SilentlyContinue - if (Test-Path 'C:\Users\Public\Documents\Miniconda3') { - # derived requirement: same AV/indexer file-lock hazard class already documented - # for :try_embed_fallback's own directory swap in run_setup.bat -- if deletion - # didn't fully succeed, do not proceed into an uncertain half-deleted state; - # fall back to the original, safe skip-this-run behavior instead. - Write-Host "::warning::Stale cache directory could not be fully removed (possible file lock); falling back to HP_CACHE_CORRUPTED=1 for this run." - 'HP_CACHE_CORRUPTED=1' | Out-File -FilePath $env:GITHUB_ENV -Encoding ascii -Append - } else { - Write-Host "Stale cache directory removed; fresh install will proceed normally." - } - } - } else { - Write-Host "Conda health OK: $output" - } + # derived requirement: the actual health-check-and-heal logic now lives in + # tools/ci_cache_selfheal.ps1 so tests/test_ci_cache_selfheal.ps1 can exercise it + # deterministically on every CI run (a GATING lane), independent of whether GitHub's + # own cache happens to be organically corrupted this run -- see that file and + # docs/agent-closed-backlog.md's Item 19 entry for why the ambient `cache` lane alone + # (informational, job-level continue-on-error) was not enough signal on its own. + $exactArgs = @() + if ($env:HP_CACHE_EXACT_HIT -eq 'true') { $exactArgs = @('-ExactHit') } + & .\tools\ci_cache_selfheal.ps1 -CondaDir 'C:\Users\Public\Documents\Miniconda3' @exactArgs + $rc = $LASTEXITCODE + if ($rc -eq 1 -or $rc -eq 3) { + 'HP_CACHE_CORRUPTED=1' | Out-File -FilePath $env:GITHUB_ENV -Encoding ascii -Append + } + if ($rc -eq 3) { + 'HP_CACHE_SELFHEAL_FAILED=1' | Out-File -FilePath $env:GITHUB_ENV -Encoding ascii -Append + } + if ($rc -eq 2 -or $rc -eq 3) { + 'HP_CACHE_SELFHEAL_ATTEMPTED=1' | Out-File -FilePath $env:GITHUB_ENV -Encoding ascii -Append } exit 0 # health check is informational; never fail this step @@ -152,6 +128,49 @@ jobs: $row | Add-Content 'tests\~test-results.ndjson' -Encoding Ascii $row | Add-Content 'ci_test_results.ndjson' -Encoding Ascii + - name: "Record cache self-heal outcome (visibility row; never fails)" + # derived requirement: this is the gap the owner flagged directly -- when the self-heal + # SUCCEEDS (the ordinary case), HP_CACHE_CORRUPTED is never set, so no NDJSON row was + # ever emitted saying "this run actually had to self-heal a corrupted cache" -- the run + # just looked like an ordinary fresh install, with the only trace being a ::warning:: + # buried in raw job logs. Emitting this unconditionally whenever the self-heal branch is + # entered (success OR failure) makes both outcomes queryable on the diagnostics site over + # time, instead of requiring a manual raw-log dig to notice either one. + if: ${{ !cancelled() && env.HP_CACHE_SELFHEAL_ATTEMPTED == '1' }} + shell: pwsh + run: | + if (-not (Test-Path 'tests')) { New-Item -ItemType Directory 'tests' | Out-Null } + $healed = ($env:HP_CACHE_SELFHEAL_FAILED -ne '1') + $row = [ordered]@{ + id = 'self.cache.selfheal.fired' + pass = $true + lane = 'cache' + desc = 'Restored cache failed its health check on a restore-keys prefix match; self-heal (delete stale dir, fall through to fresh install) was attempted' + details = [ordered]@{ healed = $healed } + } | ConvertTo-Json -Compress + $row | Add-Content 'tests\~test-results.ndjson' -Encoding Ascii + $row | Add-Content 'ci_test_results.ndjson' -Encoding Ascii + + - name: "Enforce cache self-heal success (Item 19 follow-on)" + # derived requirement: an ordinary corrupted-and-healed cache is routine infra noise and + # stays informational -- run_setup.bat hashes into the cache key on nearly every PR, so + # this fires often and is expected. But the self-heal ITSELF failing to clear the stale + # directory is not routine noise: it means this repo's own Item 19 fix has regressed back + # into the exact pre-fix "always corrupted, never self-heals" trap that item existed to + # close. This step's own failure is still absorbed by the `cache` lane's job-level + # continue-on-error (see CLAUDE.md's CI lane gating maturity notes -- this lane stays + # intentionally non-gating for ordinary organic flakiness), so on its own this does not + # block a PR; it exists so the failure is a loud, explicit ::error:: annotation and a red + # step marker instead of a buried ::warning::. tests/test_ci_cache_selfheal.ps1 (a GATING + # regression test wired into the `real` lane) is what actually catches a regression in + # this logic on every single CI run, independent of whether GitHub's own cache happens to + # be organically corrupted this run. + if: ${{ !cancelled() && env.HP_CACHE_SELFHEAL_FAILED == '1' }} + shell: pwsh + run: | + Write-Host "::error::Cache self-heal failed to clear the stale Miniconda3 directory -- see 'Validate restored conda binary' step output above." + exit 1 + # probe fires in real, conda-full, uv, and contract-uv* lanes; cache lane skips it intentionally - name: Enable Miniconda probe (real/conda-full/uv/contract-uv mode) if: ${{ matrix.mode == 'real' || matrix.mode == 'conda-full' || matrix.mode == 'uv' || matrix.mode == 'contract-uv' || matrix.mode == 'contract-uv-fail' }} @@ -253,6 +272,17 @@ jobs: } } + # derived requirement: wired into `real` specifically (a GATING lane, not in the + # job-level continue-on-error list at the top of this file) so a regression in + # tools/ci_cache_selfheal.ps1 actually fails CI -- see that file and + # docs/agent-closed-backlog.md's Item 19 entry for why the ambient `cache` lane alone + # (informational, only fires on organic corruption) was not sufficient signal on its own. + - name: "Self-test: cache-lane self-heal logic (real lane only, GATING)" + if: ${{ matrix.mode == 'real' }} + shell: pwsh + run: | + & tests\test_ci_cache_selfheal.ps1 + - name: "Pre-bootstrap: ensure Python file exists" shell: pwsh run: | diff --git a/docs/agent-closed-backlog.md b/docs/agent-closed-backlog.md index 420aab38..e21addb1 100644 --- a/docs/agent-closed-backlog.md +++ b/docs/agent-closed-backlog.md @@ -762,6 +762,52 @@ this belongs to). opportunistically across this and future PRs' own `cache`-lane runs, not via a dedicated verification loop. + **Follow-on shipped 2026-08-02, prompted by the owner directly asking "is the fix holding" and + then "make it go red so it actually helps something."** The "observed opportunistically" plan + above turned out to have two real gaps, both found while answering the first question + honestly rather than assuming: (1) verification meant manually pulling raw job logs each time + -- nothing queryable recorded whether a run's self-heal had actually fired (a corrupted + restore-keys-prefix-match cache with a successful heal never sets `HP_CACHE_CORRUPTED`, so no + NDJSON row was ever emitted for it -- the only trace was a `::warning::` buried in the log); + (2) even a hypothetical step-level failure inside the `cache` lane could never have surfaced as + a real CI failure, because that whole lane is job-level `continue-on-error` (see the top of + `batch-check.yml`) -- confirmed directly against the first real post-fix run (`30684739923`, + job `91328449387`, commit `40e6187`/PR #409): the corrupted-prefix-match branch fired for real + (`##[warning]Conda binary health check failed (exit=1) on a restore-keys prefix match; deleting + stale cache directory and proceeding as a fresh install.` at 04:55:51Z, `Stale cache directory + removed; fresh install will proceed normally.` at 04:56:21Z, `Cache saved with key: + win-Windows-py311b-conda-...` at 05:16:43Z) -- genuine end-to-end proof the fix works, found + only by pulling the raw log by hand, not by anything CI itself surfaced. + - Extracted the inline health-check-and-heal PowerShell out of `batch-check.yml`'s "Validate + restored conda binary" step into `tools/ci_cache_selfheal.ps1`, a small parameterized script + (`-CondaDir`, `-ExactHit`) with 4 distinct exit codes (0=healthy/no-op, 1=exact-hit-corrupted + [unchanged accepted gap], 2=prefix-corrupted-and-healed, 3=prefix-corrupted-heal-FAILED -- + the new case: the stale directory could not be fully cleared, meaning this fix has regressed + back toward its own pre-fix trap). + - Added `tests/test_ci_cache_selfheal.ps1`: a deterministic regression test exercising all 4 + exit codes against a scratch temp directory with fake `conda.bat` stand-ins (including a + genuine locked-file reproduction of the heal-FAILED case via a held `FileStream` handle) -- + no dependency on GitHub's cache ever being organically corrupted. Wired into the `real` + lane specifically (a GATING lane, not in the job-level `continue-on-error` list), so a future + regression in this logic fails CI for real, unlike the ambient `cache` lane which cannot. + Windows-only (shells out to `conda.bat` via `cmd.exe`; the lock scenario needs Windows + file-locking semantics), matching this repo's usual `$IsWindows`-gated-skip convention. + - Added `self.cache.selfheal.fired`: an always-`pass:true` visibility row emitted by the + ambient `cache` lane itself whenever the self-heal branch is entered (success or failure), + recording the real outcome in `details.healed` -- closes gap (1) above; an organic + occurrence is now queryable on the diagnostics site instead of requiring a raw-log dig. + - Added a loud `::error::` tripwire step (`Enforce cache self-heal success`) for the + heal-FAILED sub-case specifically, mirroring `diag.conda.available.gate`'s established + "default to a loud failure, not a silent one" pattern -- explicitly documented as *not* a + substitute for the gating test above (still absorbed by the `cache` lane's own job-level + `continue-on-error`), only a clearer annotation than the `::warning::` it replaces for that + one case. + - Deliberately did NOT flip the `cache` lane itself to gating, and did NOT touch the still-open + exact-key-hit-corrupted gap (still needs the `gh cache delete`/cache-deletion-API follow-on + noted above) -- both out of scope for this pass; the ask was specifically "make a regression + in the self-heal mechanism visible and blocking," not "make ordinary organic cache flakiness + block PRs." + ### Item 15 (closed 2026-08-01) - **`:exe_smokerun_hints`'s diagnostic re-run of a freshly-failed EXE had no timeout, unlike every diff --git a/docs/agent-ndjson.md b/docs/agent-ndjson.md index fb525c9d..369beffe 100644 --- a/docs/agent-ndjson.md +++ b/docs/agent-ndjson.md @@ -164,7 +164,7 @@ visa.detect, emit.helpers, env.state.write, dep.check.parse_lock, dp.compat, prep.multi.constraint, batch.paren.balance, env.foldername, conda.path, conda.url, env.mode, self.warnfix.platform_filter, self.exe.smokerun, helper.find_entry.syntax, entry.helper.ok, -self.cache.corrupted, self.cache.bootstrap.failed, +self.cache.corrupted, self.cache.bootstrap.failed, self.cache.selfheal.fired, meta.env.mode, workflow.lint, version.metadata, host.env.os, host.env.ps, host.env.python, @@ -558,6 +558,33 @@ substitute for a human's own interactive session). self.interactive.stdin.roundtrip ``` +## selfapps-cache-selfheal NDJSON rows (test_ci_cache_selfheal.ps1, `real` lane only, GATING) + +Item 19 follow-on (docs/agent-closed-backlog.md): the cache-lane self-heal logic +(`tools/ci_cache_selfheal.ps1`) previously had no deterministic CI coverage -- the ambient +`cache` lane only reaches it when GitHub's own cache happens to be organically corrupted, and +that lane is entirely informational (job-level `continue-on-error`) besides, so a regression in +the self-heal logic itself would ship silently. This test exercises all 4 branches of that +script directly against a scratch temp directory with fake `condabin\conda.bat` stand-ins (no +real conda/network dependency), wired into `real` -- a GATING lane -- so a regression actually +fails CI. Windows-only (the script under test shells out to `conda.bat` via `cmd.exe`, and the +locked-directory scenario needs Windows file-locking semantics); skips with `skip=true` on +non-Windows via the bare `self.ci.cache_selfheal` row. + +``` +self.ci.cache_selfheal, +self.ci.cache_selfheal.healthy, self.ci.cache_selfheal.prefix_healed, +self.ci.cache_selfheal.exact_hit_corrupted, self.ci.cache_selfheal.prefix_heal_failed +``` + +`self.cache.selfheal.fired` (inline `batch-check.yml`, `cache` lane, `HP_CACHE_SELFHEAL_ +ATTEMPTED`-gated) is the companion VISIBILITY row -- unlike the deterministic test above, this +fires only when the AMBIENT `cache` lane's own restored cache is organically corrupted on a +restore-keys prefix match, and records whether that real self-heal attempt actually succeeded +(`details.healed`). Always `pass:true` (informational, matching `self.cache.corrupted`'s own +convention) -- its purpose is to make an organic occurrence queryable on the diagnostics site +over time instead of requiring a raw-log dig to notice it happened at all, not to gate. + --- ## Key facts for debugging missing rows diff --git a/tests/test_ci_cache_selfheal.ps1 b/tests/test_ci_cache_selfheal.ps1 new file mode 100644 index 00000000..fd434b83 --- /dev/null +++ b/tests/test_ci_cache_selfheal.ps1 @@ -0,0 +1,136 @@ +# ASCII only +# test_ci_cache_selfheal.ps1 - deterministic regression test for tools/ci_cache_selfheal.ps1 +# (the cache-lane self-heal logic, Item 19 follow-on -- docs/agent-closed-backlog.md). +# +# Unlike the ambient `cache` CI lane, which only exercises the corrupted-and-heal path when +# GitHub's own cache happens to be organically corrupted (rare, unpredictable) and is entirely +# swallowed by that lane's job-level continue-on-error either way (see batch-check.yml's CI lane +# gating maturity notes), this test exercises every branch of ci_cache_selfheal.ps1 directly +# against a scratch temp directory, on every single CI run, with no dependency on real cache +# state. Wired into the `real` lane (a GATING lane, not in the job-level continue-on-error list) +# so a regression in the self-heal logic actually fails CI, not just logs a warning nobody sees. +# +# Windows-only: the script under test shells out to `conda.bat` via cmd.exe (a Windows batch +# wrapper) and the locked-directory scenario depends on Windows file-locking semantics, neither +# of which is meaningfully reproducible on Linux. +# +# Four scenarios, all against fake `condabin\conda.bat` stand-ins (never a real Miniconda +# install -- this test is pure logic, no conda/network dependency): +# healthy - conda.bat exits 0 -> exit code 0, directory untouched. +# prefix_healed - conda.bat exits 1, PREFIX match -> exit code 2, directory deleted +# (the ordinary self-heal path). +# exact_hit_corrupted - conda.bat exits 1, EXACT hit -> exit code 1, directory left in place +# (documented, accepted -- cannot self-heal an exact-key blob in place). +# prefix_heal_failed - conda.bat exits 1, PREFIX match, but a held file handle blocks +# deletion (simulates an AV/indexer lock) -> exit code 3, directory +# still present. This is the scenario that would have silently +# regressed back into Item 19's original "always corrupted, never +# self-heals" trap if it went undetected. +param() +$ErrorActionPreference = 'Continue' +$here = $PSScriptRoot +$repo = Split-Path -Path $here -Parent +$script = Join-Path $repo 'tools\ci_cache_selfheal.ps1' +$nd = Join-Path $here '~test-results.ndjson' +$ciNd = Join-Path $repo 'ci_test_results.ndjson' +if (-not (Test-Path $nd)) { New-Item -ItemType File -Path $nd -Force | Out-Null } +if (-not (Test-Path $ciNd)) { New-Item -ItemType File -Path $ciNd -Force | Out-Null } + +function Write-NdjsonRow { + param([hashtable]$Row) + $lane = [Environment]::GetEnvironmentVariable('HP_CI_LANE') + if ($lane -and -not $Row.ContainsKey('lane')) { $Row['lane'] = $lane } + $json = $Row | ConvertTo-Json -Compress -Depth 8 + Add-Content -LiteralPath $nd -Value $json -Encoding Ascii + Add-Content -LiteralPath $ciNd -Value $json -Encoding Ascii +} + +if (-not $IsWindows) { + Write-NdjsonRow ([ordered]@{ + id='self.ci.cache_selfheal'; req='N/A'; pass=$true + desc='ci_cache_selfheal.ps1 regression test (skipped on non-Windows: shells out to conda.bat via cmd.exe, and the locked-directory scenario needs Windows file-locking semantics)' + details=[ordered]@{ skip=$true; reason='non-windows-host' } + }) + exit 0 +} + +function New-FakeCondaDir { + param([string]$Dir, [int]$ExitCode) + if (Test-Path -LiteralPath $Dir) { Remove-Item -LiteralPath $Dir -Recurse -Force -ErrorAction SilentlyContinue } + New-Item -ItemType Directory -Force -Path (Join-Path $Dir 'condabin') | Out-Null + $bat = Join-Path $Dir 'condabin\conda.bat' + Set-Content -LiteralPath $bat -Value "@echo off`r`nexit /b $ExitCode`r`n" -Encoding Ascii +} + +$scratchRoot = Join-Path $here '~selftest_cache_selfheal' +if (Test-Path -LiteralPath $scratchRoot) { Remove-Item -LiteralPath $scratchRoot -Recurse -Force } +New-Item -ItemType Directory -Force -Path $scratchRoot | Out-Null + +$allPass = $true + +# --- Scenario 1: healthy --- +$dir1 = Join-Path $scratchRoot 'healthy' +New-FakeCondaDir -Dir $dir1 -ExitCode 0 +& $script -CondaDir $dir1 +$rc1 = $LASTEXITCODE +$pass1 = ($rc1 -eq 0) -and (Test-Path -LiteralPath $dir1) +Write-NdjsonRow ([ordered]@{ + id='self.ci.cache_selfheal.healthy'; pass=$pass1 + desc='ci_cache_selfheal.ps1: a healthy conda.bat exits 0 and is left untouched' + details=[ordered]@{ exitCode=$rc1; dirStillExists=(Test-Path -LiteralPath $dir1) } +}) +if (-not $pass1) { $allPass = $false } + +# --- Scenario 2: prefix match, corrupted, self-heals --- +$dir2 = Join-Path $scratchRoot 'prefix_healed' +New-FakeCondaDir -Dir $dir2 -ExitCode 1 +& $script -CondaDir $dir2 +$rc2 = $LASTEXITCODE +$pass2 = ($rc2 -eq 2) -and (-not (Test-Path -LiteralPath $dir2)) +Write-NdjsonRow ([ordered]@{ + id='self.ci.cache_selfheal.prefix_healed'; pass=$pass2 + desc='ci_cache_selfheal.ps1: a corrupted conda.bat on a restore-keys prefix match self-heals (stale dir deleted)' + details=[ordered]@{ exitCode=$rc2; dirRemoved=(-not (Test-Path -LiteralPath $dir2)) } +}) +if (-not $pass2) { $allPass = $false } + +# --- Scenario 3: exact hit, corrupted, cannot self-heal --- +$dir3 = Join-Path $scratchRoot 'exact_hit' +New-FakeCondaDir -Dir $dir3 -ExitCode 1 +& $script -CondaDir $dir3 -ExactHit +$rc3 = $LASTEXITCODE +$pass3 = ($rc3 -eq 1) -and (Test-Path -LiteralPath $dir3) +Write-NdjsonRow ([ordered]@{ + id='self.ci.cache_selfheal.exact_hit_corrupted'; pass=$pass3 + desc='ci_cache_selfheal.ps1: a corrupted conda.bat on an EXACT key hit cannot self-heal; directory is left in place (documented, accepted gap)' + details=[ordered]@{ exitCode=$rc3; dirStillExists=(Test-Path -LiteralPath $dir3) } +}) +if (-not $pass3) { $allPass = $false } + +# --- Scenario 4: prefix match, corrupted, self-heal FAILS (locked directory) --- +# derived requirement: this is the specific case tools/ci_cache_selfheal.ps1's exit code 3 +# exists for -- if a future edit ever silently swallows a Remove-Item failure and reports exit +# code 2 (healed) instead, this is the assertion that catches it. +$dir4 = Join-Path $scratchRoot 'prefix_heal_failed' +New-FakeCondaDir -Dir $dir4 -ExitCode 1 +$lockedFile = Join-Path $dir4 'locked.txt' +Set-Content -LiteralPath $lockedFile -Value 'lock' -Encoding Ascii +$handle = [System.IO.File]::Open($lockedFile, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read) +try { + & $script -CondaDir $dir4 + $rc4 = $LASTEXITCODE +} finally { + $handle.Close() +} +$pass4 = ($rc4 -eq 3) -and (Test-Path -LiteralPath $dir4) +Write-NdjsonRow ([ordered]@{ + id='self.ci.cache_selfheal.prefix_heal_failed'; pass=$pass4 + desc='ci_cache_selfheal.ps1: a locked file prevents full directory removal; self-heal correctly reports failure (exit 3) instead of silently proceeding' + details=[ordered]@{ exitCode=$rc4; dirStillExists=(Test-Path -LiteralPath $dir4) } +}) +if (-not $pass4) { $allPass = $false } + +Remove-Item -LiteralPath $scratchRoot -Recurse -Force -ErrorAction SilentlyContinue + +if (-not $allPass) { exit 1 } +exit 0 diff --git a/tools/ci_cache_selfheal.ps1 b/tools/ci_cache_selfheal.ps1 new file mode 100644 index 00000000..b74a9e61 --- /dev/null +++ b/tools/ci_cache_selfheal.ps1 @@ -0,0 +1,69 @@ +<# +ASCII only. Validates a restored Miniconda cache directory and self-heals a corrupted +restore-keys PREFIX match (the common case -- run_setup.bat hashes into the cache key, which +changes on nearly every PR) by deleting the stale directory so the caller falls through to a +real fresh install + fresh save. An EXACT primary-key hit that's corrupted cannot be +self-healed this way (a GitHub Actions cache blob is immutable once saved under a key) -- +that narrower case still just reports "skip this run." See docs/agent-closed-backlog.md's +Item 19 entry for the full incident/mechanism history this closes. + +Extracted out of .github/workflows/batch-check.yml's "Validate restored conda binary" step so +it can be exercised deterministically by tests/test_ci_cache_selfheal.ps1 on every CI run -- +the ambient `cache` lane only reaches this logic when GitHub's own cache happens to be +organically corrupted, which is rare and unpredictable, and that lane is intentionally +non-gating besides (see CLAUDE.md's CI lane gating maturity notes). + +Exit codes: + 0 = healthy -- conda.bat present and "conda info" succeeded, or no conda.bat restored at + all (a genuine cache miss; nothing to heal, fresh install proceeds normally either way) + 1 = corrupted on an EXACT key hit -- cannot self-heal; caller should skip this run + 2 = corrupted on a PREFIX match -- self-healed (stale directory deleted) + 3 = corrupted on a PREFIX match -- self-heal FAILED (directory could not be fully cleared, + e.g. an AV/indexer file lock); caller should fall back to skip-this-run +#> +param( + [Parameter(Mandatory = $true)][string]$CondaDir, + [switch]$ExactHit +) + +$condaMain = Join-Path $CondaDir 'condabin\conda.bat' +$condaAlt = Join-Path $CondaDir 'Scripts\conda.bat' +$condaBat = if (Test-Path -LiteralPath $condaMain) { $condaMain } elseif (Test-Path -LiteralPath $condaAlt) { $condaAlt } else { $null } + +if ($null -eq $condaBat) { + Write-Host "No conda binary found at $CondaDir; nothing to validate." + exit 0 +} + +$output = & cmd /c "`"$condaBat`" info" 2>&1 +if ($LASTEXITCODE -eq 0) { + Write-Host "Conda health OK: $output" + exit 0 +} + +if ($ExactHit) { + # derived requirement: an EXACT primary-key hit that's corrupted can never be replaced in + # place (a GitHub Actions cache entry's blob is immutable once saved under a key) -- fully + # closing this needs an explicit cache-deletion API call, a smaller follow-on not + # implemented here. Keep the original skip-this-run behavior for this narrow case. + Write-Host "::warning::Conda binary health check failed (exit=$LASTEXITCODE) on an EXACT cache-key hit; cache corrupted, skipping fast-path tests this run." + exit 1 +} + +# derived requirement: a restore-keys PREFIX match is not a guarantee the restored blob is +# still valid. Treat it like a genuine cache miss instead of a hard skip -- delete the stale +# directory and let the caller fall through to a real fresh install and a real fresh save +# under the current key, breaking the self-perpetuating-corruption loop (Item 19). +Write-Host "::warning::Conda binary health check failed (exit=$LASTEXITCODE) on a restore-keys prefix match; deleting stale cache directory and proceeding as a fresh install." +Remove-Item -LiteralPath $CondaDir -Recurse -Force -ErrorAction SilentlyContinue +if (Test-Path -LiteralPath $CondaDir) { + # derived requirement: same AV/indexer file-lock hazard class already documented for + # :try_embed_fallback's own directory swap in run_setup.bat -- if deletion didn't fully + # succeed, do not proceed into an uncertain half-deleted state; fall back to the original, + # safe skip-this-run behavior instead. + Write-Host "::warning::Stale cache directory could not be fully removed (possible file lock); falling back to skip-this-run." + exit 3 +} + +Write-Host "Stale cache directory removed; fresh install will proceed normally." +exit 2 From c51f2e44235c2925f092ba04c4b3f8ad832b8aa7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 12:46:21 +0000 Subject: [PATCH 5/7] Address CodeRabbit review: 5th cache-selfheal scenario, doc provenance fixes - tests/test_ci_cache_selfheal.ps1: add the missing "no conda.bat present at all" scenario -- exit code 0 covered two distinct branches in ci_cache_selfheal.ps1 but only one (healthy) had a test. - docs/agent-ndjson.md: fix a mid-identifier line break inside a code span (HP_CACHE_SELFHEAL_ATTEMPTED). - docs/demo-bootstrapper-output.md: Scenario 40a claimed its console dump was "verbatim" while also saying one line was "updated in place" -- a real self-contradiction; reworded to state plainly which line was edited and which lines are real capture. Scenario 37 reworded to lead with the mixed-provenance framing instead of an unqualified "real CI" claim. Scenario 41's cross-reference incorrectly claimed Scenario 37 shows the same WARN line "in situ" -- it actually shows the "(fallback build system)" variant, not "(PyInstaller)"; fixed. Declined (reasons given inline on the PR, no code change): - CodeRabbit's actionlint failure claim at batch-check.yml:3512 (if: false) does not reproduce -- actionlint v1.7.1 runs clean locally against that exact line, which is also pre-existing, documented, intentional code (a toggle switch), not part of this PR's diff. - PSScriptAnalyzer's ShouldProcess nag on New-FakeCondaDir -- no other test helper in this repo implements ShouldProcess (not a repo-native check), and it's disproportionate for a private, unconditionally- invoked test-only helper. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW --- docs/agent-ndjson.md | 7 ++++--- docs/demo-bootstrapper-output.md | 30 ++++++++++++++++++------------ tests/test_ci_cache_selfheal.ps1 | 21 ++++++++++++++++++++- 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/docs/agent-ndjson.md b/docs/agent-ndjson.md index 369beffe..84df13aa 100644 --- a/docs/agent-ndjson.md +++ b/docs/agent-ndjson.md @@ -574,11 +574,12 @@ non-Windows via the bare `self.ci.cache_selfheal` row. ``` self.ci.cache_selfheal, self.ci.cache_selfheal.healthy, self.ci.cache_selfheal.prefix_healed, -self.ci.cache_selfheal.exact_hit_corrupted, self.ci.cache_selfheal.prefix_heal_failed +self.ci.cache_selfheal.exact_hit_corrupted, self.ci.cache_selfheal.prefix_heal_failed, +self.ci.cache_selfheal.no_binary ``` -`self.cache.selfheal.fired` (inline `batch-check.yml`, `cache` lane, `HP_CACHE_SELFHEAL_ -ATTEMPTED`-gated) is the companion VISIBILITY row -- unlike the deterministic test above, this +`self.cache.selfheal.fired` (inline `batch-check.yml`, `cache` lane, `HP_CACHE_SELFHEAL_ATTEMPTED`-gated) +is the companion VISIBILITY row -- unlike the deterministic test above, this fires only when the AMBIENT `cache` lane's own restored cache is organically corrupted on a restore-keys prefix match, and records whether that real self-heal attempt actually succeeded (`details.healed`). Always `pass:true` (informational, matching `self.cache.corrupted`'s own diff --git a/docs/demo-bootstrapper-output.md b/docs/demo-bootstrapper-output.md index 78e6a9a7..44df1157 100644 --- a/docs/demo-bootstrapper-output.md +++ b/docs/demo-bootstrapper-output.md @@ -2300,11 +2300,13 @@ non-gating). `HP_TEST_FORCE_PYINSTALLER_FAIL=1` forces the primary build to fail the Nuitka fallback (`:try_nuitka_tier_a`) then runs for real -- a genuine compile, not simulated. **What appears on screen**, from the moment PyInstaller's build is attempted through to the final -summary. Sourced from real CI (run `29788624195`, job `88506013149`); the "(fallback build system)" -verification line and the drive-message reassurance line are exactly as captured, while the -"Verifying the built standalone EXE" line and the "Does your program need launch arguments" -paragraph reflect current source (`docs/plan-cli-interactive-verification.md` requirement 3's -activity-aware kill, and REQ-026's argv passthrough, both of which shipped after this specific run): +summary -- a mix of real CI capture and lines updated to reflect current source, not a single +uniform capture. Real CI capture (run `29788624195`, job `88506013149`): the +"(fallback build system)" verification line and the drive-message reassurance line, exactly as +captured. Updated to reflect current source (`docs/plan-cli-interactive-verification.md` +requirement 3's activity-aware kill, and REQ-026's argv passthrough, both of which shipped after +this specific run): the "Verifying the built standalone EXE" line and the "Does your program need +launch arguments" paragraph. Every other line below is real capture, unmodified: ``` [INFO] Building standalone executable -- this may take a minute or two... @@ -2496,9 +2498,10 @@ passing: #### 40a. `accept` -- a real optimized build succeeds and is swapped in -Real, verbatim console dump (`~selftest_optbuild_accept\~optbuild_accept_bootstrap.log`), with the -"Verifying the built standalone EXE" line updated in place to reflect current source (requirement -3's activity-aware kill plus the quit-prompt hint, both of which shipped after this capture): +Real CI capture (`~selftest_optbuild_accept\~optbuild_accept_bootstrap.log`) with one line updated +in place to reflect current source: the "Verifying the built standalone EXE" line now shows +requirement 3's activity-aware kill plus the quit-prompt hint, both of which shipped after this +capture -- every other line below is exactly as captured: ``` [INFO] Building standalone executable -- this may take a minute or two... @@ -2525,8 +2528,9 @@ The system cannot find the drive specified. [INFO] Optimized build succeeded and verified: dist\.exe now uses the fallback build system. ``` -The "warnfix: Platform-specific modules..." line above is this specific verbatim capture's own -pre-existing wording; current wording for that same line is shown in Scenario 34. +The "warnfix: Platform-specific modules..." line above is this specific capture's own pre-existing +wording (not updated, unlike the line noted above); current wording for that same line is shown in +Scenario 34. **The interactive `Build the optimized version now? [Y/N]` prompt line is echoed unconditionally by design** (same pattern as `:run_postexec_checkpoint`), but does not appear literally in any @@ -2618,8 +2622,10 @@ timing (which can't be automated). **The WARN line a user sees right before the verification launch**, current shipped wording (source: `run_setup.bat`, `:warn_user_code_launch` -- not a console capture, since CI answers -scripted stdin rather than a human watching the window; Scenario 37 and Scenario 40a above both show -this exact line in situ): +scripted stdin rather than a human watching the window; Scenario 40a above shows this exact line in +situ too, though there it is the one line explicitly edited into that capture to match current +source, not part of that scenario's own real-captured text; Scenario 37 shows the same line's +"(fallback build system)" variant instead of "(PyInstaller)"): ``` [WARN] Verifying the built standalone EXE (PyInstaller) now: if it stays completely silent for about 30 seconds it will be force-stopped, but any output (including a prompt waiting on your input) keeps it running as long as needed. If your program is interactive, try answering its prompts through to its own quit/exit option now so we can confirm it exits cleanly. Either way, do not start real work in it yet or any unsaved work will be lost. diff --git a/tests/test_ci_cache_selfheal.ps1 b/tests/test_ci_cache_selfheal.ps1 index fd434b83..9361168d 100644 --- a/tests/test_ci_cache_selfheal.ps1 +++ b/tests/test_ci_cache_selfheal.ps1 @@ -14,7 +14,7 @@ # wrapper) and the locked-directory scenario depends on Windows file-locking semantics, neither # of which is meaningfully reproducible on Linux. # -# Four scenarios, all against fake `condabin\conda.bat` stand-ins (never a real Miniconda +# Five scenarios, all against fake `condabin\conda.bat` stand-ins (never a real Miniconda # install -- this test is pure logic, no conda/network dependency): # healthy - conda.bat exits 0 -> exit code 0, directory untouched. # prefix_healed - conda.bat exits 1, PREFIX match -> exit code 2, directory deleted @@ -26,6 +26,9 @@ # still present. This is the scenario that would have silently # regressed back into Item 19's original "always corrupted, never # self-heals" trap if it went undetected. +# no_binary - no conda.bat present at all -> exit code 0, directory untouched +# (a genuine cache miss, the other branch that shares exit code 0 +# with the healthy scenario above). param() $ErrorActionPreference = 'Continue' $here = $PSScriptRoot @@ -130,6 +133,22 @@ Write-NdjsonRow ([ordered]@{ }) if (-not $pass4) { $allPass = $false } +# --- Scenario 5: no conda.bat restored at all (genuine cache miss) --- +# derived requirement: exit code 0 covers two distinct branches in ci_cache_selfheal.ps1 -- a +# healthy conda.bat (Scenario 1) and no conda.bat present at all. Only the former was covered +# above; this closes the gap on the latter. +$dir5 = Join-Path $scratchRoot 'no_binary' +New-Item -ItemType Directory -Force -Path $dir5 | Out-Null +& $script -CondaDir $dir5 +$rc5 = $LASTEXITCODE +$pass5 = ($rc5 -eq 0) -and (Test-Path -LiteralPath $dir5) +Write-NdjsonRow ([ordered]@{ + id='self.ci.cache_selfheal.no_binary'; pass=$pass5 + desc='ci_cache_selfheal.ps1: no conda.bat restored at all is treated as a genuine cache miss (exit 0, no-op)' + details=[ordered]@{ exitCode=$rc5; dirStillExists=(Test-Path -LiteralPath $dir5) } +}) +if (-not $pass5) { $allPass = $false } + Remove-Item -LiteralPath $scratchRoot -Recurse -Force -ErrorAction SilentlyContinue if (-not $allPass) { exit 1 } From fd5686b8f438649063cb4a92343fbdd5207955d2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 12:54:03 +0000 Subject: [PATCH 6/7] Guard against silent fixture-creation failure in cache-selfheal test Address CodeRabbit's outside-diff finding: New-FakeCondaDir used -ErrorAction SilentlyContinue throughout, so a silently-failed Set-Content would leave the fixture directory with no conda.bat -- which ci_cache_selfheal.ps1 correctly treats as a genuine cache miss (exit 0), the same exit code Scenario 1 expects for a HEALTHY conda.bat. That made fixture-creation failure indistinguishable from success instead of failing the test loudly. Declined the broader $ErrorActionPreference = 'Stop' sweep proposed alongside it (reason given inline on the PR): making cleanup (Remove-Item -ErrorAction SilentlyContinue at setup and final teardown) fail-fast would be inconsistent with how every other selfapps test in this repo treats scratch-directory cleanup as best-effort, and could introduce new flakiness on real Windows CI against exactly the AV/indexer file-lock class this repo already designs around elsewhere (see docs/agent-lessons-learned.md). Targeted the actual described failure mode instead: verify the fixture exists after creation, throw if not. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW --- tests/test_ci_cache_selfheal.ps1 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_ci_cache_selfheal.ps1 b/tests/test_ci_cache_selfheal.ps1 index 9361168d..3825ea25 100644 --- a/tests/test_ci_cache_selfheal.ps1 +++ b/tests/test_ci_cache_selfheal.ps1 @@ -58,11 +58,19 @@ if (-not $IsWindows) { } function New-FakeCondaDir { + # derived requirement: verify the fixture actually landed on disk before returning -- a + # silently-failed Set-Content here would leave $Dir with no conda.bat, which + # ci_cache_selfheal.ps1 correctly treats as a genuine cache miss (exit 0) -- the SAME exit + # code Scenario 1 expects for a HEALTHY conda.bat, so a silent fixture-creation failure + # would make that scenario pass for the wrong reason instead of failing loudly. param([string]$Dir, [int]$ExitCode) if (Test-Path -LiteralPath $Dir) { Remove-Item -LiteralPath $Dir -Recurse -Force -ErrorAction SilentlyContinue } New-Item -ItemType Directory -Force -Path (Join-Path $Dir 'condabin') | Out-Null $bat = Join-Path $Dir 'condabin\conda.bat' Set-Content -LiteralPath $bat -Value "@echo off`r`nexit /b $ExitCode`r`n" -Encoding Ascii + if (-not (Test-Path -LiteralPath $bat)) { + throw "New-FakeCondaDir: fake conda.bat was not created at $bat" + } } $scratchRoot = Join-Path $here '~selftest_cache_selfheal' From aa98972d065a2849c3a71967146a236c9764d3d6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 12:59:31 +0000 Subject: [PATCH 7/7] Assert Scenario 5's cache dir genuinely starts empty Address CodeRabbit's follow-up finding: New-Item -Force on $dir5 doesn't clear pre-existing content, so a leftover conda.bat from a prior interrupted run (surviving both that run's own best-effort teardown and this run's top-level scratchRoot recreation) could make Scenario 5 silently exercise the wrong branch while still passing on rc==0. Adds an explicit pre-invocation assertion that neither condabin\conda.bat nor Scripts\conda.bat exist before calling the script, throwing if they do. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW --- tests/test_ci_cache_selfheal.ps1 | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_ci_cache_selfheal.ps1 b/tests/test_ci_cache_selfheal.ps1 index 3825ea25..37cd09c9 100644 --- a/tests/test_ci_cache_selfheal.ps1 +++ b/tests/test_ci_cache_selfheal.ps1 @@ -146,7 +146,18 @@ if (-not $pass4) { $allPass = $false } # healthy conda.bat (Scenario 1) and no conda.bat present at all. Only the former was covered # above; this closes the gap on the latter. $dir5 = Join-Path $scratchRoot 'no_binary' +if (Test-Path -LiteralPath $dir5) { Remove-Item -LiteralPath $dir5 -Recurse -Force -ErrorAction SilentlyContinue } New-Item -ItemType Directory -Force -Path $dir5 | Out-Null +# derived requirement: assert the cache genuinely starts empty -- $scratchRoot is freshly +# recreated at the top of this file, but if a PRIOR interrupted run's teardown left content +# behind AND that top-level recreation also failed to fully clear it, New-Item -Force above +# would silently preserve a stale conda.bat here, making this scenario exercise the wrong +# branch (healthy or corrupted-detect) instead of the no-binary one it's meant to test. +$dir5CondaMain = Join-Path $dir5 'condabin\conda.bat' +$dir5CondaAlt = Join-Path $dir5 'Scripts\conda.bat' +if ((Test-Path -LiteralPath $dir5CondaMain) -or (Test-Path -LiteralPath $dir5CondaAlt)) { + throw "Scenario 5 setup: $dir5 unexpectedly still contains a conda.bat" +} & $script -CondaDir $dir5 $rc5 = $LASTEXITCODE $pass5 = ($rc5 -eq 0) -and (Test-Path -LiteralPath $dir5)