Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 0 additions & 21 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -544,27 +544,6 @@ is not.
that can collapse without losing accuracy or completeness, and cut it. Do not sacrifice
correctness or completeness for terseness -- the goal is removing WORDS, not INFORMATION.

- **Item 33: classify PyInstaller/Nuitka build-tool failures with a `reason=`-style token, not
just a plain `[ERROR]` message.** CodeRabbit review finding on PR #423 (`docs/demo-
bootstrapper-output.md` lines 2977-2980, citing AGENTS.md's "external/environmental failures ...
make them legible in logs/NDJSON ... emit a `reason=` token" guideline). Verified: `run_setup.bat`
already uses `reason=` tokens for several failure classes (`UV_FALLBACK reason=dep_install_
failed`, Miniconda AllUsers install `reason=timeout`/`reason=installer_failed`, `pipreqs.install`'s
NDJSON `reason` field), but the PyInstaller/Nuitka Tier A build-failure call sites
(`:die "[ERROR] PyInstaller execution failed."` / `"[ERROR] PyInstaller did not produce
dist\%ENVNAME%.exe"`, inside `:run_entry_after_smoke`) and `self.exe.smokerun`'s own NDJSON row
(emitted from `:smokerun_ndjson`, `exitCode` only) do not carry an equivalent classification --
a real user hitting a genuine
PyInstaller crash, an AV lock, disk-full, or a plain compile error all currently produce the
identical generic message. NOT implemented in PR #423 (docs-only housekeeping PR; this needs a
design pass over which failure signatures are actually distinguishable and worth a separate
`reason=` value, plus updating `docs/agent-ndjson.md`'s row registry and probably
`tests/harness.ps1`'s static guards -- a real feature slice, not a docs fix). Note the AGENTS.md
guideline this cites is scoped to CI-lane self-test legibility (avoiding a flaky external
download being mistaken for a repo/test regression); whether the same treatment is warranted for
`run_setup.bat`'s own real-user-facing build-failure messages is part of what this item needs to
resolve, not something to assume.

## 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
Expand Down
39 changes: 39 additions & 0 deletions docs/agent-closed-backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -1815,6 +1815,45 @@ this belongs to).
no_python_files`, `pyFiles == 0`, `exitCode == 0` (identical to the plain empty-folder case)
AND the hint text appears in the bootstrap log. Registered in `docs/agent-ndjson.md`.

### Item 33 (closed 2026-08-09)

- **`reason=` tokens for the PyInstaller/Nuitka Tier A build-exhaustion `:die` call sites**
(`:run_entry_after_smoke`), a CodeRabbit finding on PR #423 not implemented there since it
needed its own design pass. Design finding: the three call sites correspond to three genuinely
distinguishable, non-overlapping trigger conditions (no probing of PyInstaller's own stderr
needed -- distinguishable purely from which code branch reached `:die`), so each got its own
token, mirroring the existing `UV_FALLBACK reason=<token>` convention exactly (which already
has a `reason=test_forced_fail` precedent for the identical "this is a CI test hook, not a real
failure" distinction):
- `reason=test_forced_fail` -- `HP_TEST_FORCE_PYINSTALLER_FAIL` simulated the failure; no real
PyInstaller invocation ever ran.
- `reason=build_error` -- a genuine PyInstaller invocation returned a nonzero exit code. Real
code, but not exercised by any deterministic CI hook (same "extrapolated, not tested" status
as a real Nuitka compiler failure elsewhere in this repo -- see the "Reactive-only failure
hint" section, Part VIII).
- `reason=missing_output` -- the build reported success (exit 0) but `dist\<env>.exe` never
appeared (`HP_TEST_FORCE_OUTPUT_VANISH` in CI; a real AV/indexer deletion in production --
same underlying condition either way, so the same token covers both).
- `self.exe.smokerun`'s own NDJSON row turned out not to need a matching change: `:run_exe_
smokerun` exits immediately (`exit /b 0`, no row emitted) whenever `dist\<env>.exe` is
missing, so that row is never emitted at all for a total build failure -- there was no row to
add a `reason` field to. The two `:die` messages' own `reason=` tokens (now in `%LOG%`) are
the actual legibility improvement.
- No `tests/harness.ps1` static guard referenced either message string (confirmed via grep), so
no harness change was needed, contrary to Item 33's own speculation.
- Updated `tests/selfapps_pyinstaller_fail.ps1`'s `self.exe.build.xfail` scenario to assert the
expected `reason=` token per sub-scenario (`execfail`/`execfail_runtimefail` ->
`test_forced_fail`, `output_vanish` -> `missing_output`) in addition to its existing message
check.
- Updated `docs/demo-bootstrapper-output.md`'s Scenario 38a/38b real-CI-capture quotes to
include the new token (spliced from current `run_setup.bat` source, labeled honestly as a
post-capture splice rather than a fresh capture) so they stay accurate to current source.
- AGENTS.md's own `reason=` guideline is scoped to CI-lane self-test legibility (avoiding a
flaky external failure being mistaken for a repo/test regression) -- this change extends the
same pattern to `run_setup.bat`'s real-user-facing build-failure messages too, since the two
genuinely differ in actionability (`missing_output` suggests an AV/indexer conflict a user
could investigate; `build_error`/`test_forced_fail` do not) and the token costs nothing to add.

## Closed Backlog

- **Cascade-vs-postexec fix (Active Backlog item 9), 2026-07-25, owner-directed follow-up to a
Expand Down
25 changes: 18 additions & 7 deletions docs/demo-bootstrapper-output.md
Original file line number Diff line number Diff line change
Expand Up @@ -3107,15 +3107,18 @@ one's console text, since it's really a REQ-027 demo). The first two additionall

#### 38a. `execfail` -- the PyInstaller build command itself fails

Real CI capture, run `29788624195`, job `88506013028` ("real" lane). **This scenario is triggered
by two CI test hooks (forcing both the PyInstaller build and the Nuitka fallback to fail), but a
real user hitting the same genuine failure sees nothing at either trigger point** -- both
PyInstaller's and Nuitka's own error output go only to the log file, never the console:
Real CI capture, run `29788624195`, job `88506013028` ("real" lane), with the `reason=`
token (CLAUDE.md Item 33, added after that capture) spliced in verbatim from `run_setup.bat`'s
current literal source -- the rest of the block is unmodified real console output. **This
scenario is triggered by two CI test hooks (forcing both the PyInstaller build and the Nuitka
fallback to fail), but a real user hitting the same genuine failure sees nothing at either
trigger point** -- both PyInstaller's and Nuitka's own error output go only to the log file,
never the console:

```
[INFO] Building standalone executable -- this may take a minute or two...
[INFO] Standard build did not complete; attempting a fallback build (this may take a minute or two).
[ERROR] PyInstaller execution failed.
[ERROR] PyInstaller execution failed. reason=test_forced_fail
[DEBUG] warnfix: warn file not found
[INFO] PyInstaller build artifacts cleaned up.
[WARN] EXE smokerun: dist\<env>.exe not found; skipping
Expand All @@ -3126,6 +3129,13 @@ PyInstaller's and Nuitka's own error output go only to the log file, never the c
[INFO] REQ-018: post-execution checkpoint (interpreter): declined (run footprint stays at one execution).
```

The `reason=test_forced_fail` token (CLAUDE.md Item 33) reflects the actual trigger here -- this
scenario reaches `:die` via the `HP_TEST_FORCE_PYINSTALLER_FAIL` test hook, never a genuine
PyInstaller error. A real, unforced build failure would instead read `reason=build_error` --
`[Extrapolated Branch]`, since no deterministic CI hook forces a genuine nonzero PyInstaller exit
(the same "real trigger, no CI hook" situation as Tier A's own Nuitka compiler-failure hint, Part
VIII above).

When BOTH the PyInstaller build and the Nuitka fallback fail outright but the interpreter
fallback's own run exits 0 (this trivial stub script does), the final line is
`[STATUS] Run Status: SUCCESS (Exit Code: 0)` and the postflight panel is the plain
Expand All @@ -3135,7 +3145,8 @@ interpreter run ALSO fails.

#### 38b. `output_vanish` -- PyInstaller succeeds, then the EXE disappears immediately

Real CI capture, same run/job as 38a. **Unlike the test hooks elsewhere in this file, the
Real CI capture, same run/job as 38a, with the same post-capture `reason=` splice described
there. **Unlike the test hooks elsewhere in this file, the
underlying trigger here stands in for a real external event, not a purely internal simulation** --
a real user could hit this exact same gap if antivirus software or a file indexer deletes the
freshly-built EXE in the instant right after PyInstaller creates it. Neither the deletion itself
Expand All @@ -3146,7 +3157,7 @@ code path that announces either one, so both are omitted below (see 38a's note f
```
[INFO] Building standalone executable -- this may take a minute or two...
[INFO] Standard build did not complete; attempting a fallback build (this may take a minute or two).
[ERROR] PyInstaller did not produce dist\<env>.exe
[ERROR] PyInstaller did not produce dist\<env>.exe reason=missing_output
[DEBUG] warnfix: warn file found
[INFO] warnfix: some modules could not be automatically bundled (full list in ~warnfile.txt / ~setup.log); modules such as posix, fcntl, grp, pwd, resource, _scproxy, _posixsubprocess, collections.abc, and _frozen_importlib_external are expected on Windows and are filtered out automatically.
[INFO] PyInstaller build artifacts cleaned up.
Expand Down
10 changes: 7 additions & 3 deletions run_setup.bat
Original file line number Diff line number Diff line change
Expand Up @@ -3457,7 +3457,7 @@ if not defined HP_BUILD_OK (
call :log "[TEST] HP_TEST_FORCE_PYINSTALLER_FAIL: simulating PyInstaller build failure."
call :try_nuitka_tier_a
if errorlevel 1 (
call :die "[ERROR] PyInstaller execution failed."
call :die "[ERROR] PyInstaller execution failed. reason=test_forced_fail"
set "HP_BOOTSTRAP_STATE=error"
) else (
set "HP_NUITKA_FALLBACK_USED=1"
Expand All @@ -3467,7 +3467,11 @@ if not defined HP_BUILD_OK (
if errorlevel 1 (
call :try_nuitka_tier_a
if errorlevel 1 (
call :die "[ERROR] PyInstaller execution failed."
rem CLAUDE.md Item 33: reason=build_error means PyInstaller's own process exited
rem nonzero -- distinct from reason=missing_output below (exit 0 but no EXE) and
rem reason=test_forced_fail above (no real build ever ran). Mirrors the existing
rem UV_FALLBACK reason= token convention; see docs/agent-lessons-learned.md.
call :die "[ERROR] PyInstaller execution failed. reason=build_error"
set "HP_BOOTSTRAP_STATE=error"
) else (
set "HP_NUITKA_FALLBACK_USED=1"
Expand All @@ -3480,7 +3484,7 @@ if not defined HP_BUILD_OK (
if not exist "dist\%ENVNAME%.exe" (
call :try_nuitka_tier_a
if errorlevel 1 (
call :die "[ERROR] PyInstaller did not produce dist\%ENVNAME%.exe"
call :die "[ERROR] PyInstaller did not produce dist\%ENVNAME%.exe reason=missing_output"
set "HP_BOOTSTRAP_STATE=error"
) else (
set "HP_NUITKA_FALLBACK_USED=1"
Expand Down
30 changes: 21 additions & 9 deletions tests/selfapps_pyinstaller_fail.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,17 @@ try {
$expectedMsg = if ($scenario -eq 'execfail' -or $scenario -eq 'execfail_runtimefail') { 'PyInstaller execution failed' } else { 'PyInstaller did not produce dist' }
$expectedMsgFound = $combined -match [regex]::Escape($expectedMsg)
$testHookFired = $combined -match [regex]::Escape('HP_TEST_FORCE')
# CLAUDE.md Item 33: both scenarios here reach the :die call via HP_TEST_FORCE_PYINSTALLER_FAIL
# or HP_TEST_FORCE_OUTPUT_VANISH, never a genuine nonzero PyInstaller exit (reason=build_error
# is real code but not exercised by any deterministic CI hook -- same "extrapolated, not tested"
# status as a real Nuitka compiler failure elsewhere in this repo).
$expectedReason = if ($scenario -eq 'execfail' -or $scenario -eq 'execfail_runtimefail') { 'reason=test_forced_fail' } else { 'reason=missing_output' }
# derived requirement: match the reason token on the SAME log line as $expectedMsg, not
# anywhere in the combined log -- this scenario also forces the Nuitka fallback to fail, so a
# whole-log match could in principle be satisfied by an unrelated line rather than genuinely
# proving the PyInstaller [ERROR] line itself carries the right token.
$expectedFailureLines = @($logLines | Where-Object { $_ -match [regex]::Escape($expectedMsg) })
$expectedReasonFound = [bool]($expectedFailureLines | Where-Object { $_ -match [regex]::Escape($expectedReason) } | Select-Object -First 1)
# docs/open-questions.md item 1: when packaging fails outright but the interpreter fallback
# still runs cleanly, :print_no_exe_briefing (run_setup.bat) prints a dedicated panel instead
# of leaving the bare "[STATUS] Run Status: SUCCESS" line as the only thing the user sees.
Expand Down Expand Up @@ -165,17 +176,18 @@ try {
# "graceful stop" contract for this class of failure -- see selfapps_preflight.ps1's own
# $pass condition, the sibling test for the pre-existing HP_BOOTSTRAP_STATE=error precedent,
# which likewise never checks the process exit code).
$xfailPass = $testHookFired -and $expectedMsgFound -and ($statusState -eq 'error') -and $noExeBriefingFound
$xfailPass = $testHookFired -and $expectedMsgFound -and $expectedReasonFound -and ($statusState -eq 'error') -and $noExeBriefingFound

Write-PyiFailRow -Pass $xfailPass -Desc "PyInstaller build XFAIL ($scenario): build failure correctly reported, not masked as success" -Details ([ordered]@{
scenario = $scenario
bootstrapExit = $runExit
testHookFired = [bool]$testHookFired
expectedMsgFound = [bool]$expectedMsgFound
statusState = $statusState
noExeBriefingFound = [bool]$noExeBriefingFound
xfailPass = $xfailPass
log = $bootstrapLog
scenario = $scenario
bootstrapExit = $runExit
testHookFired = [bool]$testHookFired
expectedMsgFound = [bool]$expectedMsgFound
expectedReasonFound = [bool]$expectedReasonFound
statusState = $statusState
noExeBriefingFound = [bool]$noExeBriefingFound
xfailPass = $xfailPass
log = $bootstrapLog
})
} finally {
if ($null -eq $prevSkipPipreqs) { Remove-Item Env:HP_SKIP_PIPREQS -ErrorAction SilentlyContinue } else { $env:HP_SKIP_PIPREQS = $prevSkipPipreqs }
Expand Down
Loading