From a041bf01b4e7e062bd5d3264fb493a4f6812b1e9 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Aug 2026 23:29:02 +0000 Subject: [PATCH] Item 41: GUI-app-aware caveat hint for the silent-timeout case ~exe_smokerun.ps1's activity-aware kill only force-stops a verification run that produced zero stdout/stderr bytes by the ~30s deadline -- the same shape a correctly-behaving GUI app (tkinter/PyQt, a mainloop with no console output) produces. HP_EXE_EXIT=="-1" already implies this (the kill is gated on $sawOutput being false), so no new runtime signal was needed: record HP_EXE_TIMEDOUT_SILENT at the same point HP_EXE_VERIFY_FAILED is set, reset once per fresh build attempt alongside HP_DLL_HINT_STATE's own reset, and print a distinguishing note from the postflight caveat panel via a new :pfb_gui_hint subroutine, mirroring :pfb_dll_hint's existing dispatch shape. New tests/selfapps_gui_timeout_hint.ps1 (uv lane, non-gating) reproduces the signal directly: a real PyInstaller-built EXE that sleeps with zero output, verified with HP_SMOKERUN_KILL_MS shortened via its pre-existing test-only override. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_017SQ1rvJxDbE71pTXJ4QvLV --- .github/workflows/batch-check.yml | 12 +++ CLAUDE.md | 28 ------ docs/agent-closed-backlog.md | 45 +++++++++ docs/agent-ndjson.md | 39 ++++++++ run_setup.bat | 28 ++++++ tests/selfapps_gui_timeout_hint.ps1 | 140 ++++++++++++++++++++++++++++ 6 files changed, 264 insertions(+), 28 deletions(-) create mode 100644 tests/selfapps_gui_timeout_hint.ps1 diff --git a/.github/workflows/batch-check.yml b/.github/workflows/batch-check.yml index 9c6c9607..5f4fe6f6 100644 --- a/.github/workflows/batch-check.yml +++ b/.github/workflows/batch-check.yml @@ -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 diff --git a/CLAUDE.md b/CLAUDE.md index eddacfb8..444dfa76 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/docs/agent-closed-backlog.md b/docs/agent-closed-backlog.md index f48c35bb..727615cc 100644 --- a/docs/agent-closed-backlog.md +++ b/docs/agent-closed-backlog.md @@ -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.** diff --git a/docs/agent-ndjson.md b/docs/agent-ndjson.md index 5948e75c..2ee5bc8e 100644 --- a/docs/agent-ndjson.md +++ b/docs/agent-ndjson.md @@ -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 diff --git a/run_setup.bat b/run_setup.bat index 577d3b64..0273d906 100644 --- a/run_setup.bat +++ b/run_setup.bat @@ -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 @@ -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)" @@ -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. @@ -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 diff --git a/tests/selfapps_gui_timeout_hint.ps1 b/tests/selfapps_gui_timeout_hint.ps1 new file mode 100644 index 00000000..9aa71f73 --- /dev/null +++ b/tests/selfapps_gui_timeout_hint.ps1 @@ -0,0 +1,140 @@ +# ASCII only +# selfapps_gui_timeout_hint.ps1 - CLAUDE.md Active Backlog Item 41: a working GUI app is +# force-killed at the ~30s build-verification deadline (:run_exe_smokerun's activity-aware kill, +# ~exe_smokerun.ps1's own $sawOutput gate -- see docs/agent-interconnect.md "Activity-aware +# EXE-smoke kill") because it produces zero console output, exactly the same silence a genuinely +# hung program produces. HP_EXE_EXIT=="-1" (TIMED OUT) at that point in run_setup.bat already +# implies $sawOutput was false when the kill fired (the kill is gated on it), so no new runtime +# signal was needed -- run_setup.bat now records HP_EXE_TIMEDOUT_SILENT at that exact point and +# the post-flight caveat panel (:pfb_caveat) prints a GUI-app-aware note distinguishing this case +# from a genuine crash/hang, instead of leaving a beginner to read the generic caveat as evidence +# something is broken. +# +# This test does not launch a real GUI (no display/Xvfb available on a headless Windows CI +# runner, and building one would be its own, much larger and more fragile undertaking) -- it +# reproduces the exact SIGNAL the mechanism reacts to instead: a frozen EXE that produces zero +# stdout/stderr and blocks past the kill deadline. That is sufficient, since :pfb_caveat's new +# branch only ever inspects HP_EXE_TIMEDOUT_SILENT, never anything GUI-specific itself. +# +# HP_SMOKERUN_KILL_MS is a pre-existing test-only override (~exe_smokerun.ps1 reads +# $env:HP_SMOKERUN_KILL_MS directly; production run_setup.bat never sets it, so this is inherited +# from this test's own process environment with no run_setup.bat code change needed -- see +# tests/test_exe_smokerun.py for the identical override technique used at the Python-unit-test +# level). Set well 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 +# this test fast. +# +# Lane: uv only, non-gating for its first landing -- this exercises HP_SMOKERUN_KILL_MS from a +# full-bootstrap selfapps test for the first time (previously only exercised at the Python-unit +# level against a plain pwsh-launched script, not a real PyInstaller-frozen EXE's own cold-start +# behavior), so it could not be verified against a real Windows PyInstaller build locally -- +# matches this repo's established graduation pattern (see CLAUDE.md's "CI lane gating maturity" +# periodic check) for a new mechanism not yet proven stable across several real runs. +param() +$ErrorActionPreference = 'Continue' +$here = $PSScriptRoot +$repo = Split-Path -Path $here -Parent +$nd = Join-Path $here '~test-results.ndjson' +$ciNd = Join-Path $repo 'ci_test_results.ndjson' +if (-not (Test-Path $nd)) { New-Item -ItemType File -Path $nd -Force | Out-Null } +if (-not (Test-Path $ciNd)) { New-Item -ItemType File -Path $ciNd -Force | Out-Null } + +function Write-NdjsonRow { + param([hashtable]$Row) + $lane = [Environment]::GetEnvironmentVariable('HP_CI_LANE') + if ($lane -and -not $Row.ContainsKey('lane')) { $Row['lane'] = $lane } + $json = $Row | ConvertTo-Json -Compress -Depth 8 + Add-Content -LiteralPath $nd -Value $json -Encoding Ascii + Add-Content -LiteralPath $ciNd -Value $json -Encoding Ascii +} + +function Write-Row { + param( + [Parameter(Mandatory)]$Pass, + [Parameter(Mandatory)][string]$Desc, + [Parameter(Mandatory)][hashtable]$Details + ) + Write-NdjsonRow ([ordered]@{ id = 'self.exe.timeout_gui_hint'; req = 'CLAUDE.md-Item-41'; pass = $Pass; desc = $Desc; details = $Details }) +} + +# Non-Windows skip +$platform = [System.Environment]::OSVersion.Platform.ToString() +if ($platform -ne 'Win32NT') { + Write-Row -Pass $true -Desc 'GUI-timeout caveat hint (skipped on non-Windows)' -Details ([ordered]@{ skip = $true; platform = $platform; reason = 'non-windows-host' }) + exit 0 +} + +$batchPath = Join-Path $repo 'run_setup.bat' +if (-not (Test-Path $batchPath)) { + Write-Row -Pass $false -Desc 'GUI-timeout caveat hint: run_setup.bat not found' -Details ([ordered]@{ error = 'run_setup.bat not found at ' + $batchPath }) + exit 1 +} + +$workDir = Join-Path $here '~selftest_gui_timeout_hint' +if (Test-Path $workDir) { Remove-Item -Recurse -Force $workDir } +New-Item -ItemType Directory -Force -Path $workDir | Out-Null +Copy-Item -Path $batchPath -Destination $workDir -Force + +# derived requirement: zero print()/output of any kind, then block well past the shortened kill +# window -- reproduces the exact "silent + force-stopped" shape a correctly-behaving GUI +# mainloop() produces, without needing a real display. +Set-Content -Path (Join-Path $workDir 'app.py') -Value @' +import time +time.sleep(600) +'@ -Encoding ASCII + +$bootstrapLog = '~gui_timeout_hint_bootstrap.log' + +$prevSkipPipreqs = if (Test-Path Env:HP_SKIP_PIPREQS) { $env:HP_SKIP_PIPREQS } else { $null } +$prevKillMs = if (Test-Path Env:HP_SMOKERUN_KILL_MS) { $env:HP_SMOKERUN_KILL_MS } else { $null } +$env:HP_SKIP_PIPREQS = '1' +$env:HP_SMOKERUN_KILL_MS = '12000' + +try { + Push-Location $workDir + try { + cmd /c "call run_setup.bat > $bootstrapLog 2>&1 < nul" + $runExit = $LASTEXITCODE + } finally { + Pop-Location + } + + $logPath = Join-Path $workDir $bootstrapLog + $logLines = if (Test-Path $logPath) { Get-Content -LiteralPath $logPath -Encoding ASCII } else { @() } + $combined = $logLines -join "`n" + + $timedOutFound = $combined -match [regex]::Escape('Run Status: TIMED OUT') + $caveatFound = $combined -match [regex]::Escape('SETUP COMPLETE -- WITH A CAVEAT') + $guiHintFound = $combined -match [regex]::Escape('this can happen for a GUI app, e.g. tkinter or PyQt') + $exeExists = Test-Path (Join-Path $workDir 'dist') + + $statusPath = Join-Path $workDir '~bootstrap.status.json' + $statusText = if (Test-Path -LiteralPath $statusPath) { Get-Content -LiteralPath $statusPath -Raw } else { $null } + $statusState = $null + if ($statusText) { + try { $statusState = ($statusText | ConvertFrom-Json).state } catch { $statusState = $null } + } + + # derived requirement: the EXE must have genuinely been BUILT (a real PyInstaller success) -- + # otherwise this would be exercising :print_no_exe_briefing's own, unrelated caveat path + # instead of :print_postflight_briefing's :pfb_gui_hint branch this test targets. + $pass = $timedOutFound -and $caveatFound -and $guiHintFound -and $exeExists + + Write-Row -Pass $pass -Desc 'GUI-timeout caveat hint: a silent, force-killed EXE gets the GUI-aware caveat note' -Details ([ordered]@{ + bootstrapExit = $runExit + timedOutFound = [bool]$timedOutFound + caveatFound = [bool]$caveatFound + guiHintFound = [bool]$guiHintFound + exeExists = [bool]$exeExists + statusState = $statusState + log = $bootstrapLog + }) +} finally { + if ($null -eq $prevSkipPipreqs) { Remove-Item Env:HP_SKIP_PIPREQS -ErrorAction SilentlyContinue } else { $env:HP_SKIP_PIPREQS = $prevSkipPipreqs } + if ($null -eq $prevKillMs) { Remove-Item Env:HP_SMOKERUN_KILL_MS -ErrorAction SilentlyContinue } else { $env:HP_SMOKERUN_KILL_MS = $prevKillMs } +} + +if (-not $pass) { exit 1 } +exit 0