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
12 changes: 12 additions & 0 deletions .github/workflows/batch-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,18 @@ jobs:
run: |
& tests\selfapps_interactive_stdin.ps1

# CLAUDE.md Active Backlog Item 41: a real, silent, force-killed EXE gets the new
# GUI-app-aware caveat hint instead of the generic caveat text alone. Non-gating for its
# first landing -- first time HP_SMOKERUN_KILL_MS is exercised from a full-bootstrap
# selfapps test against a real PyInstaller-frozen EXE's own cold-start behavior, so it
# could not be verified against real Windows locally.
- name: "Self-test: GUI-timeout caveat hint (uv lane, non-gating)"
if: ${{ matrix.mode == 'uv' }}
continue-on-error: true
shell: pwsh
run: |
& tests\selfapps_gui_timeout_hint.ps1

- name: "Self-test: EXE smokerun XFAIL bad import (real/conda-full only)"
if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }}
shell: pwsh
Expand Down
28 changes: 0 additions & 28 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -811,34 +811,6 @@ but several represent real gaps worth closing before calling the path fully rele
**Coverage gap to close in the same slice**: no scenario backdates a source file's mtime below
the EXE's to test this. Add one.

- **Item 41: a working GUI app is force-killed at the 30s build-verification deadline and
reported with a caveat, with no messaging calibrated for that specific, correctly-behaving
case.** Confirmed reasoned-from-source, with the kill-on-silence RULE confirmed by real CI
capture of `:warn_user_code_launch`'s own message text.

**Mechanism**: the activity-aware kill (`~exe_smokerun.ps1`, `$sawOutput` gate -- see
`docs/agent-interconnect.md`'s "Activity-aware EXE-smoke kill" section for the full, deliberate
design history) only kills a process that has produced ZERO bytes of stdout/stderr within 30
seconds -- exactly the intended behavior for a genuinely hung process. A tkinter/PyQt app with a
`mainloop()` and no console output is, however, ALSO exactly that shape while behaving perfectly
correctly. It gets killed, `HP_EXE_EXIT=-1`, `[STATUS] Run Status: TIMED OUT`, and the postflight
panel reads "SETUP COMPLETE -- WITH A CAVEAT."

**Realistic scenario**: a student is handed a tkinter grade calculator. Setup runs, a window
appears, they start typing into it -- 30 seconds later Windows kills it mid-entry. The caveat
panel then says "we couldn't fully verify it runs as a standalone program," with nothing
anywhere explaining "GUI apps produce no console output, so we can't auto-verify them -- this is
normal and does not mean something is wrong." `:warn_user_code_launch` DOES warn beforehand that
a silent process will be force-stopped after ~30s, so this is disclosed, not hidden -- but the
disclosure and the caveat panel are both worded for a console-program mental model, and GUI-first
beginners are a real slice of the target audience.

**High-level fix**: detect (or simply always emit, cheaply) a GUI-app-aware variant of the
caveat message specifically for the `TIMED OUT` + zero-stdout-observed case -- something like
"your program may be a GUI app with its own window; that's expected to produce no console
output, and this caveat does not necessarily mean anything is wrong" -- distinguishing this case
from a genuine crash/hang in the panel text itself, not just in the pre-launch warning.

- **Item 42: console output is verbose across all log levels, and every fresh build ends with two
unexplained Y/N prompts -- both plausibly overwhelming for the actual target audience
(beginners with no setup experience).** Confirmed reasoned-from-source and CI capture. New; not
Expand Down
45 changes: 45 additions & 0 deletions docs/agent-closed-backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2639,6 +2639,51 @@ run of the same regex logic before landing, not just reasoned about).
new per-process uniqueness makes a genuine collision with an earlier run's leftover file
extremely unlikely on its own.

### Item 41 (closed 2026-08-18)

- **A working GUI app is force-killed at the 30s build-verification deadline and reported with a
caveat, with no messaging calibrated for that specific, correctly-behaving case.** The
activity-aware kill (`~exe_smokerun.ps1`, its own `$sawOutput` gate -- see
`docs/agent-interconnect.md`'s "Activity-aware EXE-smoke kill" section for the full design
history) only force-stops a process that has produced ZERO bytes of stdout/stderr within ~30
seconds -- exactly the intended behavior for a genuinely hung process, but ALSO exactly the
shape a correctly-behaving tkinter/PyQt app with a `mainloop()` and no console output produces.
It gets killed, `HP_EXE_EXIT=-1`, `[STATUS] Run Status: TIMED OUT`, and the postflight panel
reads "SETUP COMPLETE -- WITH A CAVEAT" with nothing anywhere explaining that this specific
shape is expected and does not mean something is broken.

**Fix shipped**: no new runtime signal was needed -- `~exe_smokerun.ps1`'s own `$killed` only
ever becomes true when `$sawOutput` was still false at the kill decision, so
`HP_EXE_EXIT=="-1"` at the point `run_setup.bat` already checks it ALREADY implies zero output
was observed before the kill. A new `HP_EXE_TIMEDOUT_SILENT` flag is set at that exact point
(right alongside the existing `HP_EXE_VERIFY_FAILED=1`), reset once per fresh build attempt
(alongside `HP_DLL_HINT_STATE`'s own reset, for the identical cascaded-away-provider-must-not-
leak-a-stale-hint reason) so a REQ-009 cascade re-entry to a different provider tier does not
inherit a stale hint from an earlier tier's own timed-out verification. `:print_postflight_
briefing`'s `:pfb_caveat` branch now calls a new `:pfb_gui_hint` subroutine when the flag is
defined, mirroring `:pfb_dll_hint`'s existing dispatch shape -- prints a note distinguishing
"this can happen for a GUI app that opens its own window and prints nothing to the console" from
a genuine crash/hang, right in the caveat panel text itself, not just the pre-launch warning
`:warn_user_code_launch` already gives (which discloses the ~30s kill beforehand but is worded
for a console-program mental model, same as the caveat panel was before this fix).

**Regression coverage**: `tests/selfapps_gui_timeout_hint.ps1` (new file, `uv` lane,
non-gating) does not launch a real GUI (no display on a headless Windows CI runner) -- it
reproduces the exact SIGNAL the mechanism reacts to instead: a real PyInstaller-built EXE
(`import time; time.sleep(600)`, zero output of any kind) verified with `HP_SMOKERUN_KILL_MS`
shortened to 12000ms. This is a PRE-EXISTING test-only override -- `~exe_smokerun.ps1` already
reads `$env:HP_SMOKERUN_KILL_MS` directly from its own inherited process environment (see
`tests/test_exe_smokerun.py` for the identical technique at the Python-unit-test level) -- so no
`run_setup.bat` code change was needed to support overriding it from a full-bootstrap selfapps
test for the first time. 12000ms was chosen with margin above typical PyInstaller onefile
cold-start extraction time (documented as commonly 1-3+ seconds even on an idle machine, see
`docs/agent-lessons-learned.md`'s "widened to 10000ms" entry for the sibling fail-fast-probe
window) so a slow-but-genuinely-silent extraction cannot be misclassified, while staying far
below the real 30000ms production default to keep the test fast. Non-gating for its first
landing, matching this repo's established graduation pattern -- this exercises
`HP_SMOKERUN_KILL_MS` against a real PyInstaller-frozen EXE's own cold-start behavior for the
first time, which could not be verified against real Windows locally.

## Known Findings (diagnosed, no action warranted)

- **Backlog item numbering: renumber-on-collision convention dropped, 2026-07-31 owner decision.**
Expand Down
39 changes: 39 additions & 0 deletions docs/agent-ndjson.md
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,45 @@ substitute for a human's own interactive session).
self.interactive.stdin.roundtrip
```

## selfapps-gui-timeout-hint NDJSON rows (selfapps_gui_timeout_hint.ps1, uv lane only, non-gating)

CLAUDE.md Active Backlog Item 41: `:run_exe_smokerun`'s activity-aware kill (`~exe_smokerun.ps1`,
its own `$sawOutput` gate -- see `docs/agent-interconnect.md`'s "Activity-aware EXE-smoke kill"
section) only force-stops a verification run that has produced ZERO stdout/stderr bytes by the
~30s deadline -- exactly the shape a correctly-behaving GUI app (tkinter/PyQt, a `mainloop()`
with no console output) also produces, indistinguishable at that point from a genuinely hung
program. `run_setup.bat` now records `HP_EXE_TIMEDOUT_SILENT` at the same point
`HP_EXE_VERIFY_FAILED` is set (`HP_EXE_EXIT=="-1"` already implies `$sawOutput` was false, since
the kill is gated on it -- no new runtime signal needed), and `:print_postflight_briefing`'s
`:pfb_caveat` branch calls a new `:pfb_gui_hint` subroutine when it is defined, printing a note
that distinguishes this case from a genuine crash/hang instead of leaving the generic caveat text
to imply something is broken.

This test does not launch a real GUI (no display on a headless Windows CI runner) -- it
reproduces the exact SIGNAL the mechanism reacts to instead: a real, PyInstaller-built EXE
(`import time; time.sleep(600)`, zero output of any kind) verified with `HP_SMOKERUN_KILL_MS`
shortened to 12000ms (a pre-existing test-only override `~exe_smokerun.ps1` already reads from
its own inherited process environment -- see `tests/test_exe_smokerun.py` for the identical
technique at the Python-unit-test level; no `run_setup.bat` code change was needed to support
this). 12000ms was chosen with margin above typical PyInstaller onefile cold-start extraction
time (documented as commonly 1-3+ seconds even on an idle machine, see
`docs/agent-lessons-learned.md`'s "widened to 10000ms" entry for the sibling fail-fast-probe
window) so a slow-but-genuinely-silent extraction cannot be misclassified, while staying far
below the real 30000ms production default to keep the test fast.

Asserts: `[STATUS] Run Status: TIMED OUT` appears in the log, the caveat panel's header
(`SETUP COMPLETE -- WITH A CAVEAT`) appears, the new GUI-hint text appears, and `dist\` genuinely
exists (proving the EXE was really built -- otherwise this would exercise the unrelated
`:print_no_exe_briefing` path instead of `:pfb_caveat`). Non-gating for its first landing --
first time `HP_SMOKERUN_KILL_MS` is exercised from a full-bootstrap selfapps test against a real
PyInstaller-frozen EXE's own cold-start behavior, so it could not be verified against real
Windows locally -- matches this repo's established graduation pattern (see CLAUDE.md's "CI lane
gating maturity" periodic check).

```
self.exe.timeout_gui_hint
```

## selfapps-cache-selfheal NDJSON rows (test_ci_cache_selfheal.ps1, `real` lane only, GATING)

Item 19 follow-on (docs/agent-closed-backlog.md): the cache-lane self-heal logic
Expand Down
28 changes: 28 additions & 0 deletions run_setup.bat
Original file line number Diff line number Diff line change
Expand Up @@ -3742,6 +3742,11 @@ if not defined HP_BUILD_OK (
rem :dll_bundle_recover never even calls (e.g. this attempt is not conda, or no DLL warning
rem appears at all) must not inherit a stale hint left by an earlier, cascaded-away provider.
set "HP_DLL_HINT_STATE="
rem CLAUDE.md Active Backlog Item 41: the post-flight caveat panel's GUI-app-aware hint reads
rem HP_EXE_TIMEDOUT_SILENT, set below where HP_EXE_VERIFY_FAILED is set. Reset here for the
rem identical reason as HP_DLL_HINT_STATE above -- a cascaded-away provider's own timed-out
rem verification must not leak a stale hint into a later provider tier's own caveat panel.
set "HP_EXE_TIMEDOUT_SILENT="
if defined HP_TEST_FORCE_PYINSTALLER_FAIL (
call :log "[TEST] HP_TEST_FORCE_PYINSTALLER_FAIL: simulating PyInstaller build failure."
call :try_nuitka_tier_a
Expand Down Expand Up @@ -4823,6 +4828,12 @@ set "HP_EXE_VERIFY_FAILED=1"
rem a -1 is a timeout/hang: no parseable error to hint on, and re-running the EXE in
rem :exe_smokerun_hints would hang too -- skip straight to the post-flight caveat.
if not "%HP_EXE_EXIT%"=="-1" call :exe_smokerun_hints
rem CLAUDE.md Active Backlog Item 41: ~exe_smokerun.ps1's own kill only fires when ZERO
rem stdout/stderr bytes were observed before the 30s deadline (its $sawOutput gate) -- exactly
rem the shape of a correctly-behaving GUI app (tkinter/PyQt, a mainloop with no console output),
rem not just a genuinely hung program. HP_EXE_EXIT=="-1" already implies this at this point in
rem the file, so no new runtime signal is needed -- just record it for the caveat panel below.
if "%HP_EXE_EXIT%"=="-1" set "HP_EXE_TIMEDOUT_SILENT=1"
goto :smokerun_ndjson
:smokerun_ok
call :log "[INFO] EXE smokerun: exited 0 (ok)"
Expand Down Expand Up @@ -5606,6 +5617,7 @@ echo ============================================================
echo We packaged your app, but couldn't fully verify it runs as a
echo standalone program. Your Python environment was set up and the
echo packaging step completed without a fatal error.
if defined HP_EXE_TIMEDOUT_SILENT call :pfb_gui_hint
if defined HP_DLL_HINT_STATE call :pfb_dll_hint
:pfb_runapp
echo.
Expand Down Expand Up @@ -5706,6 +5718,22 @@ echo cause of the caveat above.
:pfb_dll_hint_done
exit /b 0

:pfb_gui_hint
rem CLAUDE.md Active Backlog Item 41: the ~30s verification kill only fires when ZERO
rem stdout/stderr bytes were observed before the deadline (~exe_smokerun.ps1's own $sawOutput
rem gate, see docs/agent-interconnect.md "Activity-aware EXE-smoke kill") -- exactly the shape
rem of a correctly-behaving GUI app (tkinter, PyQt) with a mainloop and no console output, not
rem just a genuinely hung program. Distinguish the two in the panel text itself, not just the
rem pre-launch warning (:warn_user_code_launch already discloses the kill beforehand, but that
rem disclosure and this panel are both worded for a console-program mental model otherwise).
echo.
echo NOTE: this can happen for a GUI app, e.g. tkinter or PyQt, that opens its
echo own window and prints nothing to the console -- that produces the exact
echo same silence as a genuinely hung program, so we cannot tell them apart
echo automatically. If a window appeared and worked normally, this caveat
echo does not necessarily mean anything is wrong.
exit /b 0

:print_no_exe_briefing
rem docs/open-questions.md item 1: when PyInstaller AND the Nuitka fallback both fail outright
rem (no dist\%ENVNAME%.exe at all), the run still succeeds via the interpreter fallback and the
Expand Down
Loading
Loading