# CLAUDE.md -- AI Assistant Guide for Python_vs_Windows This file is the orientation document for AI assistants (Claude, Codex, Copilot, etc.) working in this repository. It summarizes structure, workflows, and conventions. **Do not duplicate content from these authoritative sources -- reference them instead:** - **[README.md](./README.md)** -- Product requirements, Prime Directive specification, CI contract - **[AGENTS.md](./AGENTS.md)** -- Agent iteration rules, scope policy, style rules, sanity checks - **[CONTRIBUTING.md](./CONTRIBUTING.md)** -- Fork/branch/PR workflow and iteration priorities **Supplemental context loaded automatically by Claude Code:** @docs/agent-interconnect.md @docs/agent-lessons-learned.md **Reference-only, NOT auto-loaded -- read on demand, not every session:** - `docs/agent-ndjson.md` -- the NDJSON row registry by lane. Read it when adding/renaming/removing a row, or debugging why a row is missing from a CI artifact. Moved out of auto-load 2026-08-30 (~17k tokens of pure registry content with no forward-looking guidance most sessions need). - `docs/agent-cold-storage.md` -- shelved ideas gated on a specific, checkable trigger. - `docs/agent-closed-backlog.md` -- the full historical record of completed work, plus any Active Backlog item below that was later fully closed (see that file's own header). **AGENT DIRECTIVE -- keep the knowledge docs current.** When a change teaches or invalidates a lesson, update the relevant doc in the SAME commit, editing existing entries (not only appending): - `docs/agent-interconnect.md` -- cross-component dependencies ("touch A, must understand B"). - `docs/agent-lessons-learned.md` -- standalone hazards, rules, budgets, procedures. Most are batch/CMD syntax quirks and Windows shell gotchas; record those here. - `docs/agent-ndjson.md` -- the NDJSON row registry (add/rename/remove rows here too; NOT auto-loaded, read it on demand when touching this). - When an Active Backlog item is fully resolved, move it out of this file entirely into `docs/agent-closed-backlog.md`'s "Closed Active Backlog Items" section (keep its original number). Do not let a closed item linger here -- this file's size is a per-session cost. --- ## What This Repo Does `run_setup.bat` is a self-contained Windows batch file that bootstraps any Python application on a clean Windows 10+ machine with internet access. Drop it alongside `.py` files and double-click -- it handles Miniconda, conda environments, dependency installation, and optionally produces a PyInstaller EXE. No admin rights required for the core flow. The target audience is beginners who have been handed Python code and want it to run. Getting the code running takes priority over preserving constraints. --- ## Repository Map ``` run_setup.bat Main bootstrapper (self-contained, ~157 KB) -- the deliverable run_tests.bat Static test orchestrator (calls harness.ps1) tests/ harness.ps1 Core NDJSON test validator (reads test-results.ndjson) selftest.ps1 Bootstrap self-tests (empty repo + stub hello_stub.py) selftests.ps1 Console message validation (replays bootstrap log) selfapps_entry.ps1 Entry selection precedence tests selfapps_envsmoke.ps1 Full environment verification (conda/pip/imports) selfapps_single.ps1 Single Python file bootstrap test selfapps_reqspec.ps1 Requirements specifier parsing tests (~= compatible release) selfapps_pandas_excel.ps1 Pandas/openpyxl heuristic tests selfapps_pipgap.ps1 pip gap-fill safety net (runs run_setup.bat: conda misses opencv-python, pip fills it) selfapps_isolation.ps1 REQ-010/REQ-011 behavioral tests (unconditional, HP_CI_SKIP_ENV=1) dynamic_tests.py Python-side entry detection and version precedence tests test_*.py Python unit tests (14 files, see Testing section) tools/ apply_patch.py Unified/legacy diff parser and applier (used by inline_model_fix) find_entry.py REQ-002 entry-point selector (canonical source for HP_FIND_ENTRY) check_delimiters.py Bracket/quote balance validator (.bat/.ps1/.py/.yml/.json) check_crlf.py CRLF byte-uniformity checker/fixer for .bat/.cmd files (--fix mode) check_workflows_yaml.py YAML syntax validation via PyYAML inline_model_fix.py AI quick-fix orchestrator (OpenAI Responses API -- CI use only) sanitize_iterate_payload.py NDJSON redaction, deduplication, truncation iterate_gate.ps1 Iterate gate decision logic ps-compileall.ps1 PowerShell syntax checker run_prechecks.ps1 Pre-execution validation diag/ publish_index.py GitHub Pages diagnostics site publisher ndjson_fail_list.py Failing test extractor (builds model context) ndjson_fail_list.ps1 Legacy fail list extractor docs/ agent-interconnect.md Cross-component dependency map (loaded via @ import) agent-lessons-learned.md Standalone hazards/rules/quirks/procedures (loaded via @ import) agent-ndjson.md NDJSON row registry by lane (NOT auto-loaded) agent-closed-backlog.md Full historical record of completed work (NOT auto-loaded) agent-cold-storage.md Shelved ideas gated on a trigger (NOT auto-loaded) agent-scratchlog.md Internal working notes, freely prunable (NOT auto-loaded) open-questions.md Unresolved maintainer-decision items (NOT auto-loaded) .github/workflows/ batch-check.yml Main CI pipeline (3-lane Windows matrix, ~40 KB) codeql.yml CodeQL security analysis workflow-lint.yml YAML/workflow lint (actionlint + PyYAML) pr-automerge.yml Auto-merge handler for all non-draft same-repo PRs (blocks on 'no-automerge' label) tools/ (continued) poll_public_diag.ps1 Diagnostics polling helper (moved from scripts/) ``` --- ## Development Branch Always develop on the branch specified in the session's system instructions or PR context. Push with: ``` git push -u origin ``` --- ## Mandatory Sanity Checks Run `tools/run_sanity_sweep.sh [extra-file ...]` before every commit -- compileall, pyflakes, `check_delimiters.py .` (whole repo), `check_crlf.py`, yamllint, actionlint, an ASCII sweep, `git diff --stat`, a PowerShell AST parse of every `tests/`/`tools/` `.ps1` file, and `pytest tests/test_*.py`, with a clear per-check pass/fail summary. Extra args extend the ASCII sweep's default file list (illustrative, not exhaustive -- add whatever the current change touches). README.md-only changes still run it as a baseline (catches an accidental non-doc diff). It does not auto-install missing tools (`pwsh`/`actionlint`/`yamllint`/`pyflakes`) -- see AGENTS.md's "Style and robustness" section for install steps, and its "Recurring tooling" section for what the sweep does and does not do. The script itself is the single source of truth for exactly what runs; do not hand-copy its steps elsewhere. --- ## Key Conventions Full rules in **AGENTS.md**. The most critical: | Rule | Why it matters | |------|---------------| | ASCII only -- no emojis, curly quotes, em-dashes | Batch/cmd parsing breaks on non-ASCII | | `.bat/.cmd` use CRLF (byte-uniform, `-text`); `.ps1` uses CRLF (normalized `eol=crlf`); everything else LF | Controlled by `.gitattributes` -- do NOT edit manually. `.bat/.cmd` are enforced by `tools/check_crlf.py` (sanity sweep + gating CI), not by git normalization -- see `docs/agent-lessons-learned.md`'s ".bat files: -text, not eol=crlf" entry | | `--override-channels -c conda-forge` on all installs | Prevents defaults channel contamination | | Tilde-prefix temp files (`~setup.log`, etc.) | Easy to gitignore; survive crashes | | Avoid `EnableDelayedExpansion`; if needed, wrap tightly | Parent shells with `/V:ON` cause collisions | | `call "%CONDA_BAT%" ...` for all conda invocations | Keeps parent batch running after conda | | No PSGallery downloads in CI | Proxy blocks it; use syntax-only validation | | Tag non-obvious constraints: `# derived requirement: ` | Prevents future regression on subtle fixes | | Never use `$IsWindows` in a `.ps1` file -- use `[System.Environment]::OSVersion.Platform -ne [System.PlatformID]::Win32NT` | `$IsWindows` is undefined (reads `$null`/falsy) under Windows PowerShell 5.1, silently skipping real Windows execution -- fixed one file at a time across 4+ PRs before `tools/check_delimiters.py` (run repo-wide by the sanity sweep's "DELIMITER CHECK" step) started catching it mechanically; see `docs/agent-lessons-learned.md` | --- ## run_setup.bat Rules This is the deliverable. Treat changes carefully. 1. **Self-contained**: no committed helper files; all helpers are base64-encoded inside the batch file under `:define_helper_payloads`. To refresh a helper with a canonical `tools/` source, use `python tools/sync_payload.py HP_VARNAME tools/the_file.py` -- do not hand-roll the encode/splice/write sequence (see docs/agent-lessons-learned.md's "Embedded Helper Update Workflow" and the near-miss that motivated automating it). Current embedded payloads (in addition to legacy `HP_FAST_CHECK`): | Payload | Decodes to | Purpose | Canonical source | |---------|-----------|---------|-------------------| | `HP_DEP_CHECK` | `~dep_check.py` | Compares pipreqs output against `~environment.lock.txt`; exits 0 (skip) or 1 (install needed) | `tools/dep_check.py` | | `HP_ENV_STATE` | `~env_state.py` | Reads/writes `~env.state.json` to cache conda env validity across runs | `tools/env_state.py` | | `HP_PYPROJ_DEPS` | `~pyproj_deps.py` | Parses `pyproject.toml` `[project].dependencies`, one dep/line; exit 0/1/2 = ok/not-found/malformed TOML; feeds the layered dep resolution block | `tools/pyproj_deps.py` | | `HP_PREP_REQUIREMENTS` | `~prep_requirements.py` | Applies REQ-005.8 heuristic dep-augmentation rules; strips pip extras (`[excel]`) before name lookup | `tools/prep_requirements.py` | | `HP_COLLECT_SUBMODULES` | `~collect_submodules.py` | Pre-build `--collect-submodules=PKG` flags for curated dynamic-import packages (sklearn/matplotlib/scipy/plotly), double-gated on used-by-source AND installed-in-interpreter so a fat global env never bloats a lean app's EXE | `tools/collect_submodules.py` | | `HP_HIDDEN_IMPORT_SCAN` | `~hidden_import_scan.py` | Slice 2 `--hidden-import` auto-recovery target selection for `:hidden_import_recover`; strict `ModuleNotFoundError` + installed-in-build-interpreter gate (a typo or `ImportError: cannot import name` costs zero rebuilds); capped at 3 rebuilds | `tools/hidden_import_scan.py` | | `HP_DLL_PCT_SANITIZE` | `~dll_pct_sanitize.ps1` | Strips `%`/`^` from env var values for `:log`'s UNQUOTED-echo safety in the native-DLL bundling loop; emitted as a real `.ps1` (invoked via `-File`) so cmd.exe's own tokenizer never parses its body -- see `docs/agent-lessons-learned.md`'s ":log echoes UNQUOTED" entry for why the earlier inline `-Command` version needed three separate fixes | `tools/dll_pct_sanitize.ps1` | | `HP_MIGRATE_GITATTRIBUTES` | `~migrate_gitattributes.ps1` | Item 60: replaces a pre-existing `.gitattributes`' stale `*.bat eol=crlf`/`*.cmd eol=crlf` lines with `-text`, in place, EXACT-line-match only so unrelated/user-hand-edited content is never touched; called unconditionally from `:merge_git_config` regardless of whether the append block ran or was skipped | `tools/migrate_gitattributes.ps1` | Each payload's canonical `tools/` source has a `PayloadSync` unit test asserting byte-equality between the embedded base64 and the source file (see the Testing section below for which test file covers which payload). 2. **Delimiter-check after every edit**: ```bash python tools/check_delimiters.py run_setup.bat ``` 3. **Three code paths exist** (cache / real / conda-full lanes) -- test all three after significant changes by checking CI results across all lanes. 4. **Bootstrap status contract**: every run writes `~bootstrap.status.json`: ```json {"state":"ok|no_python_files|error","exitCode":0,"pyFiles":0} ``` CI harnesses and `tests/selftest.ps1` read this. See README.md for full contract. --- ## CI Overview Three test lanes run in parallel on Windows Server runners: | Lane | Purpose | Gated? | |------|---------|--------| | `cache` | Reuses cached Miniconda | No (continue-on-error) | | `real` | Fresh Miniconda download (authoritative) | Yes | | `conda-full` | Forces conda-only, no venv/system fallbacks | Yes | The `real` and `conda-full` lanes gate PR merges. `cache` is informational. Key outputs: `tests/~test-results.ndjson` (machine-readable), `~bootstrap.status.json`, `bootstrap.log`, `~setup.log`. **Auto-patcher**: only `tools/inline_model_fix.py` via the "Model quick-fix (inline)" job in `batch-check.yml`. No other agent or job may commit auto-fixes. See AGENTS.md. **Diagnostics site**: https://mixmansoundude.github.io/Python_vs_Windows/ ### CI Pipeline Architecture (key facts for debugging) **CI job step ordering (within each lane job):** 1. Selfapps scripts run (each appends rows to both NDJSON files). 2. Artifacts are uploaded (NDJSON snapshot of selfapps rows). 3. "Verdict from NDJSON" CI step reads pre-harness selfapps rows (has_failures verdict). 4. Dynamic tests run. 5. `run_tests.bat` runs `tests/harness.ps1` (reads and deletes `tests/~test-results.ndjson`, writes harness static check rows back to the same file). 6. `tests/selftest.ps1` runs the bootstrapper self-tests (empty repo + stub). **selftest.ps1 vs selftests.ps1:** - `selftest.ps1` -- runs run_setup.bat on a real (empty) app directory, validates `~bootstrap.status.json` was written, exercises the stub fast-path and dep-check flows. - `selftests.ps1` -- replays a captured bootstrap log and validates console message patterns (does NOT re-run run_setup.bat). Different scope; name similarity is a trap. **HP_CI_SKIP_ENV=1 mode:** - Causes run_setup.bat to skip conda env creation entirely and use system Python. - The `:ci_skip_entry` subroutine (lines ~1090-1196) handles this path. - Only `selfapps_isolation.ps1` exercises this path directly. - Exit code from this path is 0 on success (`:after_env_skip` calls `:write_status ok 0` then `exit /b 0`). The known "exit 255" artifact in `selfapps_single.ps1` referred to PowerShell `$LASTEXITCODE` drift, not the batch exit code itself. See `@docs/agent-ndjson.md` for the complete NDJSON row registry by lane. See `@docs/agent-interconnect.md` for the uv-first provider interconnectedness map. --- ## Testing ### Python unit tests (cross-platform, run locally) ```bash python -m pytest tests/test_*.py -v ``` Test files and what they cover: | File | Covers | |------|--------| | `test_apply_patch.py` | Unified/legacy diff parsing and application | | `test_entry_selection.py` | Entry file precedence (main > app > run > cli > __main__) | | `test_find_entry.py` | find_entry.py selection matrix: substantive `__main__` detection, alphabetical fallback (REQ-002), HP_FIND_ENTRY base64 sync | | `test_entry_single.py` | Single Python file bootstrap (Windows only, skipped on Linux) | | `test_inline_model_fix_decision.py` | Model quick-fix decision logic | | `test_ndjson_fail_list.py` | Fail list extraction from NDJSON artifacts | | `test_poll_public_diag_logging.py` | Diagnostics polling and logging | | `test_ps_colon_scan.py` | PowerShell scoped variable detection ($var:) | | `test_check_delimiters_import.py` | Delimiter checker import guard | | `test_check_crlf.py` | CRLF byte-uniformity checker/fixer (detection, safe-write --fix, CLI check/fix modes) | | `test_fast_check_pattern.py` | HP_FAST_CHECK infra-dir exclusion regex ($infraPattern) | | `test_heuristics.py` | Heuristic dep-augmentation rules (REQ-005: all 6 rules, extras syntax, capitalization, kill-switch, idempotency) | | `test_parse_warn.py` | PyInstaller warn-file translation table (REQ-007: 5.x and 6.x formats, all TRANSLATIONS entries) | | `test_publish_index_regex.py` | Regex patterns in diagnostics publisher | | `test_sanitize_iterate_payload.py` | NDJSON redaction and deduplication | | `test_collect_submodules.py` | `--collect-submodules` double-gate (used AND installed), adversarial import-scan cases, AST-failure regex fallback, HP_COLLECT_SUBMODULES payload sync | | `test_hidden_import_scan.py` | `--hidden-import` auto-recovery strictness (ModuleNotFoundError + installed only), typo/ImportError/circular-import non-triggers, tried-list loop guard, HP_HIDDEN_IMPORT_SCAN payload sync | | `test_check_ndjson_registry.py` | NDJSON registry cross-check: brace expansion, all four code emission patterns, log-file parsing, pass/fail end-to-end paths | | `test_migrate_gitattributes.py` | Item 60: exact-match-only `.gitattributes` migration (fresh, idempotent, missing-file, asymmetric, already-fixed, near-miss-partial-text cases), HP_MIGRATE_GITATTRIBUTES payload sync | ### Static harness (Windows-only, requires PowerShell) ```batch run_tests.bat ``` Runs `tests/harness.ps1` which validates NDJSON output structure and counts pass/fail. ### Dynamic tests (run automatically by CI) Triggered when `~bootstrap.status.json` reports `state=ok`. Runs `tests/dynamic_tests.bat` or `tests/dynamic_tests.py` if present. See README.md section Dynamic test rules. ### Adding new tests - Python unit tests: `tests/test_.py` - PowerShell scenario tests: `tests/selfapps_.ps1` - Wire new PS scenarios into `tests/harness.ps1` (NDJSON rows) and `batch-check.yml` job steps. --- ## Tools Directory The `tools/` folder holds standalone helpers for CI and development. Add new helpers here rather than embedding non-trivial logic inline in `.yml`, `.bat`, or `.ps1` files. ```bash # Validate delimiter balance python tools/check_delimiters.py run_setup.bat python tools/check_delimiters.py . # all supported files # Validate YAML workflows python tools/check_workflows_yaml.py # Apply a patch (dry-run first) python tools/apply_patch.py patch.diff --dry-run python tools/apply_patch.py patch.diff ``` `tools/inline_model_fix.py` is called by CI only. It stages context, calls the OpenAI Responses API, extracts a fenced diff, and applies it via `tools/apply_patch.py`. --- ## Common Pitfalls - **Batch special characters**: `&`, `%`, `^`, `!`, `~` in variable values require quoting or escaping. `%` in particular must be doubled (`%%`) inside `for` loops. - **`pipreqs` misses runtime deps**: static analysis cannot see plugin-style imports like `pandas.read_excel` needing `openpyxl`. See README.md section Dependency strategy. - **PowerShell `-or` / `-and`**: outside a conditional, these are parsed as parameter names and produce cryptic "parameter name 'or'" errors. `check_delimiters.py` flags these. - **YAML multi-line `run:` blocks with PowerShell**: indentation and quote nesting interact. Test modified YAML with actionlint before committing. - **Line endings**: if you edit `.bat` or `.ps1` files with a LF-only editor, git corrects them on commit per `.gitattributes`, but local execution may fail before the commit. - **conda defaults channel**: any conda command without `--override-channels -c conda-forge` risks pulling from defaults, which violates the repo policy. - **Tilde-prefixed files in gitignore**: `~setup.log`, `~bootstrap.status.json`, etc. are never committed. Do not remove the tilde prefix. - **Extra packages after requirements.txt edits**: packages removed from requirements.txt are NOT uninstalled from the conda env (harmless, documented). Only affects the lock/state fast paths. --- ## Dependency Strategy (summary) `pipreqs` is discovery only. `requirements.txt` is a hint, not authority. conda-forge is truth. See README.md section Dependency strategy for the full explanation including the PIL/pillow and cv2/opencv mapping limitation. --- ## Dependency Discovery: pipreqs pin rationale (as of 2026-06-18) **pipreqs is pinned to 0.4.13, NOT 0.5.0 -- do not "upgrade" this pin.** 0.5.0 hard-caps `Requires-Python <3.13` (via its Jupyter-scanning `ipython==8.12.3` dependency), which would silently disable pipreqs entirely on this bootstrapper's always-latest-conda-forge-Python target and fall every run back to warnfix; 0.4.13 has no upper Python cap and runs fine on 3.14. The only feature lost by staying on 0.4.13 is `.ipynb` scanning, already non-functional on latest Python since 0.5.0 can't run there anyway. Full version-comparison rationale in `docs/agent-closed-backlog.md`'s "Dependency Strategy Rationale" reference section. The `pipreqs.flags` CI gate validates invocation flags only, not the pinned version -- it does not authorize changing 0.4.13; the setup log line `[INFO] pipreqs installed successfully` confirms pipreqs is active on a given run. ## Dependency Discovery: pipreqs invocation (bootstrap determinism) **pipreqs is invoked via `python -m pipreqs.pipreqs`, NOT the console script.** Deterministic (no PATH/activation dependency) and safe only because pipreqs is permanently version-pinned (0.4.13), making its internal module structure stable by contract -- the exact tradeoff this repo's Bootstrap Architecture Principles (below) codify generally. See `run_setup.bat`'s own invocation comment (search `python -m pipreqs.pipreqs`) for the in-code pointer, and `docs/agent-closed-backlog.md`'s "Dependency Strategy Rationale" reference section for the full constraints/comparison writeup. ## Dependency Discovery Fallback: warnfix (secondary safety net) If pipreqs install ever fails (e.g., a future Python drops a stdlib API pipreqs needs, or docopt/yarg cannot build), the bootstrapper still falls back to `warnfix`: 1. PyInstaller builds the EXE (static analysis finds many imports) 2. Read the `warn` file (list of modules PyInstaller couldn't find) 3. Parse warn file via `parse_warn.py`: extract top-level, delayed, and conditional imports 4. Filter modules warnfix must never install as an application dependency: a generic `if mod.startswith("_"): continue` rule drops leading-underscore internals by name pattern alone; the `SKIP` frozenset additionally covers packaging/import-machinery internals (`pkg_resources`, `distutils`, `setuptools`, `importlib`+submodules), `collections` (+`.abc`), Unix-only platform modules absent on Windows, and Python-2-only stdlib shims (`cStringIO`, `StringIO`) that still surface via real packages' own Python 2/3 compat code. Every `SKIP` entry is covered automatically by `tests/test_parse_warn.py::ParseWarnFileEdgeCasesTest::test_every_skip_entry_filtered_in_realistic_warn_line` -- no separate test needed per addition. Full per-group rationale and the real CI evidence (`self.layered_e2e.chain`) that motivated the Unix-platform and Python-2-shim groups in `docs/agent-closed-backlog.md`'s "Dependency Strategy Rationale" reference section. 5. Install detected missing packages via conda or pip 6. Rebuild EXE 7. Retry interpreter smoke test **Warnfix coverage:** Warnfix detects and handles: - [x] Top-level imports (e.g., `import colorama`) - [x] Delayed imports (e.g., `def load(): import requests`) - [x] Conditional imports (e.g., `if sys.platform == 'win32': import winreg`) - [ ] Optional/try-except imports (intentionally skipped, guarded by try-except) - [ ] Dynamic imports (e.g., `importlib.import_module(name)`) **User recommendation:** For Python 3.13+ or to avoid fallback latency, provide explicit dependencies: - **Option 1:** Add `requirements.txt` (comma-separated or newline-separated, any format pip understands) - **Option 2:** Add `pyproject.toml` with `[project]` section and `dependencies` field (PEP 508 format) - **Option 3:** Add PEP 723 inline metadata: `# /// script` block at the top of your `.py` file (Python 3.11+) See README.md section Dependency strategy for full details. --- ## Bootstrap Architecture Principles This system prioritizes **deterministic execution during bootstrap** over packaging purity -- these six principles govern tool invocation, dependency handling, and error handling in `run_setup.bat` (the pipreqs invocation strategy above is the canonical worked example): 1. **Bootstrap reliability > API correctness.** A feature depending on "maybe PATH is set" or "activation might work" is invalid for bootstrap paths -- determinism is non-negotiable. 2. **Never depend on console scripts during bootstrap** (`pipreqs`, `pytest`, etc. all require `Scripts/` on PATH and activation state neither is guaranteed) -- use explicit interpreter paths or direct Python APIs instead. 3. **All execution must be interpreter-anchored**: every tool invocation roots in an explicit Python executable path (`%HP_PY%` or `%CONDA_PREFIX%\python.exe`), never PATH/activation. 4. **Pinned dependencies are assumed stable** -- for a version-frozen tool (pipreqs 0.4.13), relying on its internal module structure as stable by contract is an acceptable coupling. 5. **Bootstrap must fail fast and explicitly** -- no silent fallbacks unless explicitly logged. 6. **Non-obvious decisions must be self-documenting** (e.g. `python -m pipreqs.pipreqs` instead of `pipreqs`) so a future maintainer isn't tempted to "fix" it incorrectly. --- ## Iteration Loop Rules See **AGENTS.md** section Iteration Contract for the full policy. Key points: 1. Freeze scope for the current loop -- new requirements go to backlog. 2. Fix CI first (pipeline/config), then test errors, then test assertions, then product code. 3. After going green, verify no false positives. 4. Implement exactly ONE missing feature slice per loop. 5. Add exactly ONE missing test per loop. --- ## Active Backlog Items deferred to future loops. This list is for genuine future work -- a decision already made, a fact confirmed with no action needed, or a recurring/periodic check belongs in `docs/agent-closed-backlog.md`'s "Known Findings" section, `docs/agent-lessons-learned.md`, or "Periodic Maintenance Checks" below instead; a promising idea deliberately shelved pending a specific, named trigger belongs in "Cold Storage" below instead of here (see that section's own scope note for the distinction from this one). Item numbers are informal labels for cross-referencing within a session or PR, not a guaranteed-unique ID scheme -- pick anything that looks free in the list below when filing a new item; do not cross-check it against `docs/agent-closed-backlog.md`'s history first, and do not renumber an item if it later turns out to coincidentally repeat an older, already-closed item's number. (Owner decision 2026-07-31, see `docs/agent-closed-backlog.md`'s Known Findings section: the earlier renumber-on-collision convention was more rigor than a plain-text backlog needs -- a real uniqueness guarantee belongs in an actual issue tracker, e.g. GitHub Issues, not a hand-maintained numbering scheme here.) Once an item is fully resolved it is removed from here entirely and archived (keeping its original number) in `docs/agent-closed-backlog.md`, which is why the numbering below does not start at 1 and has gaps. **If this list is currently empty, state that explicitly in large bold text (see below) rather than leaving the section silently blank** -- an empty section with no marker is easy to mistake for a rendering glitch or an accidental deletion; an explicit statement is not. **House rule: a blocked item needs a registered, traceable question, not just prose.** If an item's remaining work is blocked on the maintainer's own decision, access, or input -- not on further agent implementation -- it must have a corresponding entry in `docs/open-questions.md` naming exactly what decision/access is needed, added in the SAME commit that identifies the item as blocked. Describing the blocker only in this file's own prose is not enough: `docs/open-questions.md` is the one place designed to be scanned for "what does the agent need from you" without reading the whole backlog, and a blocker that exists only here is easy for both the maintainer and a future agent to lose track of. Once the maintainer answers, remove the question doc entry and fold the outcome into wherever it belongs (this file, the interconnect/lessons- learned docs, etc.) -- see `docs/open-questions.md`'s own header for that half of the convention. Conversely, an item with genuinely nothing left to implement -- a standing procedure now proven working, a decision already made and acted on, a fact confirmed with no action needed -- does not belong here at all regardless of whether it once looked open; close it out per the general rule two paragraphs up, and if it documents a reusable procedure, give that procedure a permanent home (`docs/agent-lessons-learned.md`, an existing process section) rather than leaving it implied only by a now-closed backlog entry. All items below stem from a 2026-08-09 Opus 5 release-readiness deep dive of `run_setup.bat`'s default (zero-flag, double-click) Prime Directive path, scoped to the most common real user runs -- first run and every repeat run -- with findings cross-checked against real CI evidence and this repo's own docs before filing (see chat history for the full report). Overall verdict from that review: **yes, with caveats** -- the golden path is confirmed working end-to-end on current `main`, none of the findings below are recent regressions (all pre-date the last several weeks of merges), but several represent real gaps worth closing before calling the path fully release-ready. - **Item 35: systematically promote non-gating CI lanes/checks and inert NDJSON rows to real gating status -- EXTREME CAUTION, one lane/row per slice, diagnostics-site publish must never be blocked.** Goal: for every lane/row that is genuinely real (not a placeholder) and demonstrably non-flaky, make it capable of turning CI red for real, so "CI is green" stops being a partial truth for any lane or row this repo ships. **Known inventory to work through (re-derive against current state at slice time)**: non-gating lanes at the PER-LANE level -- `cache`, `justme-test`, `uv`, `contract-uv`, `contract-uv-fail`, `uv-dl-fallback` (the last three are explicitly load-bearing as non-gating, since they simulate failure/contract scenarios that intentionally diverge from a normal run -- re-verify that reasoning still holds before excluding them, don't just carry the text forward) still have no required-status-check of their OWN -- but since the "Enforce aggregate self-test verdict" gate below aggregates all 8 lanes, a real failure in any of them (including a genuine one already observed in `cache`/`uv`, unrelated to any of this item's own changes) is DESIGNED to block a merge via the aggregate check even though the individual lane still shows non-required in the PR checks UI -- currently, that enforcement step itself carries `continue-on-error: true` (see the "Reverted to advisory" note below), so today it does NOT actually block; this is the intended end-state once cache/uv are fixed and `continue-on-error` is removed. Do not read "non-gating" below as "cannot ever block a merge" post-aggregate-gate; it means "has no required check of its own." `ndjson-registry-check` (a separate advisory doc/code/log sync job, needs several more clean-PASS runs before even considering gating); inert/never-fired NDJSON rows with zero real-CI observation confirmed via `docs/agent-ndjson.md` -- `self.dll_bundle.recover` (see Item 37), `self.failfast.probe`, `self.exe.smokerun` (audit for others, these three are only the confirmed set); the real interactive `set /p` prompts and `pause`/`HP_INTERACTIVE_RUN`'s natural un-forced state, neither ever exercised in any CI configuration at all (lower priority -- already mitigated via forced- branch tests, just not the ambient real-user condition). **Non-negotiable constraint: the diagnostics-site-publish job must never be blocked or skipped by this work.** `publish_diag`'s `if: ${{ always() }}` / `needs: [selftest, selftest-gate, model-quick-fix]` guard already covers this -- any change to the job graph as part of this item must re-verify that `needs:` list still covers the right jobs and `if: always()` still applies, empirically (not just by reading the YAML) the first time `selftest-gate` actually goes red in practice. **A DIFFERENT, more specific gap in this same guarantee was found and fixed 2026-08-30, prompted by a maintainer question about whether Pages publishing was truly guaranteed.** Job-level `if: always()` only makes the JOB start regardless of `needs:` outcomes -- it does NOT make every STEP inside the job run regardless of an earlier step's failure (GitHub Actions gives every step an implicit `if: success()` unless it declares its own condition, which a genuine, not just skipped, prior-step failure turns false). Two of `publish_diag`'s own early steps -- "Checkout repository" and "Prep site directories" (the step that actually creates the `_site`/`.nojekyll` skeleton) -- had no explicit `if:` at all. Had either genuinely failed, everything downstream would still have attempted to run (nearly every later step in this job already carries its own `if: always()`, and the deploy steps' own `event_name`/`outcome`-gated conditions already bypass the default `success()` chaining too), but `_site` itself would never have been created, so "Upload Pages artifact" would fail for real (path doesn't exist) rather than the deploy simply being skipped or attempted against a thinner site. Fixed by adding `if: always()` to those two steps plus three more found lacking it for consistency ("Record iterate artifact status", "Fetch batch-check artifacts", "Append job summary" -- lower-stakes, since everything after them already had `always()`, but leaving them inconsistent was pointless risk). **Clarifying the causal story, since it wasn't quite what was suspected**: the widespread `continue-on-error`/ `exit 0` patterns scattered across the OTHER jobs in this file (the non-gating matrix lanes, a handful of optional sub-steps) are NOT what protects Pages publishing -- `publish_diag`'s own `if: always()` at the job level already guarantees it starts regardless of whether `selftest`/ `selftest-gate`/`model-quick-fix` succeed OR fail, with no dependency on those jobs being lenient with themselves. Those other patterns exist for unrelated reasons already documented under this repo's own CI lane policy (see "CI Overview" and the "CI lane gating maturity" periodic check). One inherent, unfixable-via-YAML platform limit remains: `always()` does not run a job if the whole workflow run itself is cancelled or the runner dies outright -- accepted as an unavoidable GitHub Actions characteristic, not a gap in this repo's own control. **A WIDER, pre-existing version of this same class of gap remains open, out of scope for the 2026-08-30 fix above (CodeRabbit review, PR #471).** The fix above closed the reachability gap for the 5 specific steps this PR's diff touches. Several OTHER `publish_diag` steps this PR never touched -- `Package iterate logs archive`, `Mirror iterate logs into site bundle`, `Normalize iterate artifact layout`, `Publish diagnostics index` (all pre-existing `if: always()`, confirmed absent from this PR's own diff) -- consume `steps.prep.outputs.ARTIFACTS`/`DIAG`/`SITE` directly, unguarded, the same way the 2 fixed steps did before their fix; if `Prep site directories` itself fails before writing those outputs (as opposed to merely being skipped because an earlier step failed), these steps would resolve to a malformed path (e.g. bash's `"${EMPTY}/iterate"` becomes `/iterate`) rather than the graceful scratch-directory fallback the 2 fixed steps now have. Auditing and fixing every such consumer across the whole job is a real, separate, larger undertaking (a full pass over `publish_diag`'s ~20+ steps) -- deliberately not folded into this PR, which is scoped to the reachability gap alone. A future slice should generalize the `_site_prep_failed`-style fallback to a single shared early step (writing a fallback env var or step output once) rather than repeating the guard at every consumer site. **"Gating" is a GitHub branch-protection setting, not a YAML edit.** This repo's actual merge gate is GitHub's native required-status-checks list on the default branch's protection rule (`pr-automerge.yml` delegates entirely to it, no custom required-check logic lives in this repo's own workflow code). Removing a job's `continue-on-error: true` is a necessary prerequisite but NOT sufficient -- someone with repo-admin access must also add the check's exact name to that list. Each slice's real deliverable is prove-readiness-then-flag-for-the-branch-protection- change (or make it directly, if the acting agent has that access) -- a YAML edit alone is never "done." **Aggregate-check mechanism: implemented and now the primary path for closing the required- checks maintenance treadmill (most lanes drifted non-gating simply because they were added AFTER the original required-checks list was set and never retrofitted in, not by deliberate policy).** `selftest-gate` ("Aggregate self-test verdicts") already downloads and aggregates every one of the 8 matrix lanes' `lane_verdict.json` files unconditionally via `tools/aggregate_selftest_ verdicts.ps1`, comparing the SET of expected lane IDs against the SET of observed ones -- a missing, unexpected, or duplicate lane counts as `has_failures=true` on its own, independent of the overall job's own `success`/`failure` result (this fail-closed precondition was itself a real fix: the original mechanism only caught a TOTAL absence of verdict files, missing a single lane's silently-missing upload as long as the rest landed). Confirmed via `tests/test_aggregate_selftest_verdicts.ps1`'s fixtures (8 scenarios covering missing/ unexpected/duplicate lanes, malformed JSON, and a missing `has_failures` field, none of which depend on a real Windows host) and several consecutive green real `real`-lane runs. **Actual gating step implemented 2026-08-30 (maintainer added the matching branch-protection required check the same day): a new "Enforce aggregate self-test verdict" step `exit 1`s when `steps.aggregate.outputs.has_failures == 'true'`**, mirroring the identical, already-proven "Enforce NDJSON failures for gated lanes" per-lane pattern. Re-verified first that `contract-uv`/`contract-uv-fail`/`uv-dl-fallback`'s own simulated-failure scenarios report a clean (non-`has_failures`) verdict on every real run observed to date, so gating on the aggregate does not turn them into permanent false blockers. Added ALONGSIDE the existing per-lane `real`/`conda-full` required checks, not replacing them -- collapsing to one aggregate check would lose today's per-lane visibility in the PR checks UI, and a bug in `selftest-gate` itself would otherwise remove the only signal. Consolidate down to just the aggregate check later, once its own reliability is separately proven across more runs. **Reverted to advisory (`continue-on-error: true`) the same day, on PR #471, after its own first two real activations both hard-failed on a genuine, reproducible, pre-existing condition unrelated to that PR's diff.** The pre-gating re-verification above checked only that the three simulated-failure lanes (`contract-uv`/`contract-uv-fail`/`uv-dl-fallback`) don't false-positive -- it never checked whether `cache`/`uv` had a CURRENTLY-real failure of their own, which they did: two separate runs, hours apart, produced byte-identical failing rows -- `self.exe.smokerun` (`cache`, `exitCode:1`) and `self.cascade.exec` + `self.exe.warnfix.venv_repair` (`uv` -- cascade falls through to embed instead of stopping at conda; the warnfix repair-install precondition never fires). Identical detail payloads twice rules out flake. Neither PR touched `run_setup.bat`/any selfapps script, so this predates PR #471 and is a genuine, currently-open regression in the bootstrapper or its test scripts, not a CI-mechanism bug -- but since this gate is what first turned an already-non-gating lane's failure into a repo-wide merge blocker, leaving it hard-failing would have blocked every PR until someone separately root-causes and fixes it. `continue-on-error: true` keeps the step's own red result visible in the PR checks UI without failing the job. **Re-remove `continue-on-error` only after both root causes below are fixed and the mechanism has re-soaked per the process discipline already stated below** (it was not, in fact, fully met before this first gating attempt -- the sample only covered the 3 simulated-failure lanes, not all 8). **New, still-open sub-item spun out of this finding**: root-cause and fix (a) `self.cascade.exec` (`uv` lane) -- why the provider cascade currently falls through conda to embed (`condaToEmbed:1`) instead of stopping at conda, and (b) `self.exe.warnfix.venv_repair` (`uv` lane) -- why the forced-missing-xlrd precondition no longer triggers warnfix's repair-install path (`installedXlrd:false`) under venv mode. Both reproduced identically on 2026-08-30 across two separate `uv`-lane runs (workflow runs `33288809538` and `33293648911`); start there rather than re-deriving reproduction steps from scratch. **Process discipline for every future slice in this item**: (1) one lane/row at a time -- confirm the mechanism is genuinely real and non-flaky, let the full 8-lane matrix run to completion several consecutive times before treating it as proven, only then take the gating step; (2) no shortcuts on "let CI run to completion" -- a partial or cancelled run never counts. `docs/agent-ndjson.md`'s own notes on each currently-inert row already diagnose why it's inert; `docs/agent-closed-backlog.md`'s Item 19 entry is relevant prior art for the `cache` lane's own non-gating history. - **Item 37: `self.dll_bundle.recover`'s visibility fix is confirmed via real CI (PR #452); its promotion out of `continue-on-error` (or the whole `cache` lane's) remains open, tracked as an Item 35 slice, not implemented independently.** The DLL-bundling repair loop's own NDJSON row previously never fired in any real CI run at all -- `selfapps_layered_e2e.ps1`'s sub-bootstrap ran with `HP_NDJSON` unset. Fixed by setting `HP_NDJSON` there (pointed at the same shared `~test-results.ndjson` the test's own rows already use, restored in `finally`) and diffing the file before/after to record, as an informational `details` field on `self.layered_e2e.chain` (deliberately NOT folded into that test's own pass/fail, so a wiring hiccup can't turn an already-proven test red). Confirmed via a real published diagnostics artifact: the row fired TWICE with `state:"repaired"` in one run. One confirmed run is not yet the "several consecutive runs" Item 35's process requires before considering promotion -- that decision is a live next step for a future loop. - **Item 46: `:die`'s `exit /b` lets most call sites continue executing afterward, producing repeated `pause` prompts and further doomed work instead of a single clear stop -- batched, proven-shape-at-a-time remediation, NOT a small slice.** See `docs/agent-lessons-learned.md`'s `:die` entry for the core mechanism (execution continues past `call :die`; `HP_BOOTSTRAP_STATE` is set at the source so the status file stays honest regardless). **Maintainer decision (2026-08-21)**: continue the current approach -- goto-based stopgaps to the nearest safe sink, batched by proven shape across several PRs -- rather than a global `HP_FATAL` flag or converting `:die` itself to a genuinely process-halting `exit` (drop `/b`). The latter is a plausible SEPARATE future effort once the batch inventory shrinks further, not folded into this ongoing work -- see `docs/plan-die-fatal-remediation.md` for the full batch trace/risk classification and `docs/agent-lessons-learned.md`'s `:die` entry for the one existing test contract (`tests/selfapps_entrysmoke_no_interpreter.ps1:171`) that change would need to update deliberately. Two non-pausing treatments exist for different shapes of `:die` site: `:warn_build_incomplete` (a genuinely NOT-doomed case -- real verification work still follows) and a straight `goto` to the nearest safe re-entry point (a genuinely doomed chain, where nothing useful follows). Not every `:die` site qualifies for either -- classify doomed-vs-not-doomed before converting a new one. **Status**: Bucket B (3 PyInstaller-build-failure sites -> `:warn_build_incomplete`) and Bucket A Batches 1/2/3/5 plus one Batch-4 site are merged (PR #468 and a same-session follow-on). **Still open**: Batch 6 (`:tci_both_failed`'s two failure sites, inside `:try_conda_install` -- needs a new caller-side coordination flag rather than a drop-in `goto` since they sit inside a `call`ed subroutine with their own `goto :eof`; deliberately deferred, since Batch 1's own fix already shrunk its remaining value to "one fewer redundant pause in an already-rare scenario"); the rest of Batch 4 (6 sites, individually traced and reclassified as falling through into benign, silently-degraded continuations rather than a redundant-`:die` cascade -- not fixed); and candidate (c), converting `:die` to a genuine halt, deferred per the maintainer decision above. A known, unfixed, near-zero-exposure quirk surfaced along the way: `:after_env_skip` writes `state=ok` unconditionally regardless of an earlier `call :die` in the same run (`HP_CI_SKIP_ENV` is test-infrastructure-only). Full detail: `docs/plan-die-fatal-remediation.md`'s "Implementation Status" section. ## Cold Storage (promising ideas, deliberately shelved -- revisit only if a named trigger fires) Moved to `docs/agent-cold-storage.md` (2026-07-31, to reduce this file's per-session context footprint -- see that file's own header for why). Real, technically-sound ideas that are NOT being pursued right now, each gated on a specific, checkable trigger -- not "eventually," not "never," but "only if X happens." Check that file before proposing something that might already be a deliberately-shelved idea, and before assuming a gap is unnoticed rather than intentionally parked. ## Periodic Maintenance Checks (recurring, quarterly) This section is for checks that need to be **repeated on a schedule** because they track externally-moving state (GitHub's own ecosystem, CI lane maturity, upstream project health) -- not one-time backlog items. Each entry keeps only the *most recent* scan's date, findings, and going-forward notes; overwrite in place rather than appending a history. **Directive: re-run this checklist and update every entry below every 3 months** (calendar quarters -- Jan/Apr/Jul/Oct). Quarterly comfortably covers GitHub's own deprecation cadence (Actions runner/Node-version changes are typically announced 6-12 months ahead) without letting findings go stale for a full year. This is the standard mechanism for scheduled recurring maintenance in this repo -- prefer adding a new dated entry here over inventing a new backlog item for something that will need re-checking indefinitely. A quarterly Claude Code Remote trigger ("Python_vs_Windows quarterly maintenance scan", cron `0 9 1 1,4,7,10 *`, fires a fresh session each time) pokes a session to run this checklist and open a docs-only PR with the update; if that trigger is ever missing or misfiring, recreate it with the same name/cadence rather than relying on manual memory. ### GitHub Actions / CI health (action pins, deprecations, lint) - **Last scanned**: 2026-07-07. - **Findings**: all action pins across `.github/workflows/*.yml` are on their latest majors (`checkout@v5`, `cache@v5`, `upload-artifact@v6`, `download-artifact@v6`, `github-script@v8`, `codeql-action@v3`, `configure-pages@v6`, `upload-pages-artifact@v5`, `deploy-pages@v5`) -- nothing to bump. Two informational, non-actionable GitHub-runner-side/action-internal notices observed in CI logs, neither with any fix available on this repo's side: (1) "Node.js 20 is deprecated... forced to run on Node.js 24" against `download-artifact@v6` (already latest -- upstream runner behavior); (2) a Node `[DEP0040] DeprecationWarning: The punycode module is deprecated` emitted from inside `actions/deploy-pages@v5`'s own bundled JS during the "Publish diagnostics to Pages" job -- originates inside the action's own execution, not this repo's code, and resolves whenever the action's maintainers update their bundled dependencies. `actionlint` and `yamllint` both clean. `.github/dependabot.yml` now exists (`github-actions` ecosystem, weekly schedule) -- see Closed Backlog. - **Going forward**: confirm Dependabot is actually opening PRs against the `github-actions` ecosystem (not just present but inert) at the next scan. ### Persisted CI run-page warnings - **Last scanned**: 2026-07-06. - **Findings**: reviewed the latest completed runs (through run #1523); no warnings recur across multiple runs beyond the Node.js-20-deprecation notice already covered above. - **Going forward**: look for anything recurring across several runs, not one-off transient network blips (e.g. the `conda.anaconda.org` 403 already tracked separately as the conda-create transient-retry gap above). ### CI lane gating maturity - **Last scanned**: 2026-07-08 (added `ndjson-registry-check` to the watch list; see below). - **Findings**: only `real` and `conda-full` gate PR merges; `cache`, `justme-test`, `uv`, `contract-uv`, `contract-uv-fail`, `uv-dl-fallback` are deliberately non-gating (see AGENTS.md policy and this file's Closed-Backlog history for why each was made so) -- not a bug. The `ndjson-registry-check` job (a separate advisory job, not a matrix lane) is also `continue-on-error: true` -- as of run #1555 it shows a clean PASS in real CI for the first time after its Python-source-scanning gap closed (see Closed Backlog), but is being watched for soak time before any gating discussion, same as the two lanes below. - **Going forward**: `uv` and `justme-test` are the two most mature/stable of the six non-gating lanes -- re-assess each scan whether either has soaked long enough (no flakiness, no lane-specific caveats left) to graduate to gating. Leave `contract-uv`/`contract-uv-fail`/ `uv-dl-fallback` non-gating indefinitely (their non-gating status is explicitly load-bearing, not provisional). `ndjson-registry-check` needs several more real-CI runs at clean PASS before even considering gating -- one green run is not a trend. ### CI wall-clock duration (conda-full lane growth) This tracks a genuine periodic-reassessment concern, not a one-time backlog item -- moved here 2026-07-25 from Active Backlog (it was originally filed as a backlog item, but "worth periodic reassessment, no action planned" is exactly what this section exists for, and letting it grow stale as a single never-closed backlog entry was the wrong home for it). **What this tracks and why it matters**: `conda-full` (the lane that forces every dependency install through real conda, no fallbacks -- the slowest of the 8 matrix lanes, since conda's own dependency solver is materially slower than pip/uv) is the dominant single contributor to overall CI wall-clock time, which grew from ~20 min to ~1.5h historically. Since all 8 matrix lanes run in parallel, the WHOLE workflow's wall-clock is bounded by whichever lane is slowest -- almost always `conda-full` -- so this lane's own growth directly sets how long every contributor waits for a PR to go green. This matters for development velocity (a slower feedback loop costs real time on every single PR), not correctness -- nothing about the lane itself is broken. - **Last scanned**: 2026-07-25. Pulled created/updated timestamps for the 18 most recent successful full-workflow runs on this branch via the GitHub API (`workflow_runs` list) -- overall wall-clock ranged 85-110 minutes, averaging ~92 minutes, consistent with the original ~80 min observation and not showing runaway growth since. No single-lane breakdown gathered yet (would need per-job, not per-run, timestamps to isolate `conda-full` specifically from the other 7 lanes running alongside it). - **Findings**: likely accumulated feature/test-scenario growth over time (more selftest scripts each doing a real `conda create`/`conda install`), not a regression traceable to any single change -- no evidence of a single fixable root cause has surfaced across multiple scans. - **Going forward**: re-check the same 18-run wall-clock sample each scan; flag if the average climbs meaningfully above ~92 minutes (would suggest either unchecked growth or an actual regression, not just noise) rather than plateauing. If a real investigation is ever warranted, it needs per-job (not per-run) timestamps to isolate `conda-full`'s own duration from the other 7 lanes -- not yet gathered, since no action has been planned either way. ### pipreqs ecosystem status - **Last scanned**: 2026-07-04. - **Findings**: pipreqs (bndr/pipreqs) still stagnant/maintenance-only as of this scan; no new PyPI release since 0.5.0; still correctly pinned to 0.4.13 here (see "pipreqs pin rationale" above); `self.stub.pipreqs_version_fail` (Closed Backlog) confirms the warnfix safety net covers total pipreqs unavailability regardless of the exact failure mode. - **Going forward**: check whether pipreqs has a new maintainer/release that changes the `<3.13` Requires-Python situation, or whether it's been removed from PyPI (extremely unlikely) -- revisit the internalization decision above if either happens. - **Next-pin probe** (add to each scan going forward, see "Next-pin probe concept" below for the general idea this instantiates): under the CURRENT latest target Python (not an old ambient one -- this matters, see the concept note), run an unpinned `pip install pipreqs` (or `pip index versions pipreqs`) and check whether the resolved version is still 0.4.13. If a newer version resolves cleanly, pipreqs's own `Requires-Python` ceiling has moved -- open a dedicated loop to re-evaluate the pin (do not bump it inline during a routine scan; re-pinning needs its own verification pass, mirroring how the original 0.4.13 pin was chosen). ### Embed version table (REQ-009 Tier 5, `tools/embed_pyver_check.py`) - **Last scanned**: 2026-07-10 (entry added this scan; first run of this specific check). - **Findings**: `EMBED_PYTHON_TABLE` currently spans 3.10 through 3.14 (`LATEST_MINOR="3.14"`, `FLOOR_MINOR="3.10"`), pinned to patches 3.10.11 / 3.11.9 / 3.12.10 / 3.13.14 / 3.14.6 with embedded SHA256 checksums, last refreshed 2026-07-09 per the file's own header comment. The `"3.14"` entry's patch/sha256 must stay in sync with `HP_EMBED_LATEST_PATCH`/ `HP_EMBED_LATEST_SHA256` in `run_setup.bat`; `tests/test_embed_tier.py`'s `BatchPythonConsistency` test enforces this automatically on every CI run, so a stale cross-reference is caught in CI, not just at scan time. No CPython 3.15 has shipped yet as of this scan (3.14 remains current stable), so the table's ceiling is still accurate. - **Going forward**: each scan, check python.org for (a) a new CPython minor release -- add a table entry and bump `LATEST_MINOR`/`HP_EMBED_LATEST_PATCH`/`HP_EMBED_LATEST_SHA256` together (re-run `test_embed_tier.py` to confirm the sync); (b) any of the pinned patch versions reaching end-of-life within their minor line -- bump to the latest patch of that minor (embedded zips are patch-specific, not auto-updating); (c) whether `FLOOR_MINOR="3.10"` should advance -- python.org's own supported-versions page lists five actively-supported minors at any time, so the floor should track five minors behind latest, not stay fixed at 3.10 indefinitely. Re-verify each new/changed table entry's SHA256 against a fresh direct download before committing, per this tier's original design principle (embedded checksums are computed once at pin-time and independently verified, never trusted from a third-party checksum file fetched over the same network path as the download itself). - **Next-pin probe**: this table's own quarterly refresh (checking python.org for a new minor) already covers this on the "does a new version exist" axis. If REQ-AV's Tier B (shelved in Cold Storage, not dropped -- see `docs/agent-cold-storage.md`'s Tier B entry for why and what restarts it; PRD at `docs/prd-av-safe-build-path.md`) ever ships its own Python-3.12 pin for Nuitka/MinGW64 compatibility, add a matching probe here: periodically check whether Nuitka's MinGW64 backend has resumed Python 3.13+ support upstream, since that specific fact (not a general "try a newer version and see") is what the 3.12 pin depends on -- see that PRD's "Notes from Claude" section for why a pin justified by one specific, checkable fact should be revisited by re-checking that exact fact, not by a generic version-bump probe. ### Next-pin probe concept (general pattern, applies to any future pin) Both entries above now include a "next-pin probe": a periodic, mechanical check of whether the CURRENT constraint behind an existing pin still holds, run against the environment the pin would actually need to work in (not a stale or convenient stand-in). This section names the pattern explicitly so a future pin (anywhere in this repo) gets the same treatment by default rather than each one inventing its own ad hoc check. **The pattern**: for any pin `X` justified by "we can't use the newer version of `X` because of constraint `C`", the probe is: periodically attempt to use the newer version of `X` under the exact conditions `C` describes, and treat an unexpected *success* as the signal to open a re-pinning loop (this mirrors this repo's own `xfail`/XPASS vocabulary already used for the hidden-import-recovery tests -- an "expected failure that starts passing" is exactly the maintenance signal this pattern is built around). Two worked examples above: pipreqs 0.4.13 (constraint: `Requires-Python <3.13` on 0.5.0+) probes by trying an unpinned `pip install pipreqs` under the bootstrapper's actual current-latest target Python, not an arbitrarily older one -- probing under an old Python would trivially "succeed" without proving anything about whether the REAL constraint (0.5.0 vs. this bootstrapper's actual target) has resolved. **Why this is maintenance-checklist work, not a CI lane**: a probe that is *expected* to fail under normal conditions and treated as informational-only when it does doesn't fit this repo's gating model (`real`/`conda-full` block merges; even the non-gating lanes exist to catch real regressions, not to track upstream ecosystem state). It also doesn't need to run on every push -- upstream constraints like a `Requires-Python` ceiling or a compiler compatibility gap change on the timescale of months, not commits. It belongs exactly where the two entries above already put it: as a scripted or semi-scripted step inside the existing quarterly "Periodic Maintenance Checks" cadence (the same Claude Code Remote trigger already described at the top of this section), not a new lane in `batch-check.yml`. **Not implemented as a standalone script yet.** Both entries above describe the probe in words; neither has a dedicated `tools/check_next_pins.py`-style script yet. That's a reasonable, small, self-contained next loop if someone wants to reduce the quarterly scan from "a human runs `pip install pipreqs` by hand and reads the result" to "a script prints a one-line PASS/FLAG verdict for each tracked pin" -- worth doing, not urgent, and deliberately not built speculatively ahead of a second or third pin actually needing it. ## Known Findings (diagnosed, no action warranted) Moved to `docs/agent-closed-backlog.md` (2026-08-09, Active Backlog Item 34 Loop 2). Real investigations that concluded "no action needed" or "considered and rejected, with reasoning" -- this section's own scope is verbatim that file's stated purpose (read on demand, not every session), so it doesn't belong in the always-loaded file. Check that file's "Known Findings" section before re-investigating something that might already have a documented, closed answer. ## Closed Backlog Moved to `docs/agent-closed-backlog.md` (2026-07-31, to reduce this file's per-session context footprint -- that section alone was over 60% of this file's total length). It holds the full historical record of completed feature/fix work (what shipped, why, and the design reasoning behind it) plus any Active Backlog item that was later fully closed (each keeps its original item number for stable cross-reference -- this is why Active Backlog above does not start at 1). Read it on demand: when you need the resolution history behind a specific PR or item cited by number, or when investigating something that "feels like it was already done."