From 7e8a65a9c07358bb9a7f261c05b82b430d961c83 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 27 Jul 2026 23:31:24 +0000 Subject: [PATCH 1/2] ci: wire conda_avail gate to 27 conda-full self-tests + loud tripwire Gates the conda-full half of 22 real/conda-full self-test steps plus 5 conda-full-only steps on steps.conda_avail.outputs.available == 'true' (skip fast instead of each independently retrying a doomed sub-bootstrap). Ships paired with a new "Enforce Miniconda availability" tripwire step that unconditionally fails the job when that reads anything but 'true' in the conda-full lane -- deliberately without distinguishing a genuine Miniconda install failure from a bug in the gate mechanism itself, per explicit owner direction to default to a loud failure either way. This is what makes the gate safe to ship: shipping it alone would reintroduce the exact PR #390 failure class (a wrong condition silently skips real tests while the job stays green). Owner-directed after a full risk/benefit assessment discussed in chat, including exploring (and declining, with documented reasoning) an additional CI-level retry mechanism on top of run_setup.bat's own already-comprehensive Miniconda recovery (AllUsers->JustMe, REQ-022 retry, REQ-013 connectivity retry, download-URL fallback, bounded installer timeout). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_015xbWLPbiaKVsobB9FZy8kS --- .github/workflows/batch-check.yml | 143 ++++++++++++++++++++++-------- CLAUDE.md | 61 +++++++++++++ docs/agent-ndjson.md | 30 ++++--- 3 files changed, 185 insertions(+), 49 deletions(-) diff --git a/.github/workflows/batch-check.yml b/.github/workflows/batch-check.yml index e0234ce4..9a6ee695 100644 --- a/.github/workflows/batch-check.yml +++ b/.github/workflows/batch-check.yml @@ -322,7 +322,7 @@ jobs: run: | & tests\selfapps_envsmoke.ps1 - - name: "Check Miniconda availability (diagnostic only -- not yet wired to any if: condition)" + - name: "Check Miniconda availability (diagnostic; gates 27 conda-full self-tests + a loud tripwire)" # derived requirement (moved here 2026-07-27, correcting item 7's original placement): this # check previously ran right after "Bootstrap environment (run_setup.bat)" -- but that step # runs against THIS repo's own root (no loose .py files, the empty-repo/no_python_files @@ -345,13 +345,17 @@ jobs: # own script would otherwise allow, so a nonzero exit there can only mean the conda install # itself failed -- making this the correct point to sample "is conda now really available." # - # Deliberately NOT yet wired to any if: condition in this same commit. This mechanism has - # already produced two real, independently-discovered bugs in quick succession (the - # premature-gate bug this comment describes, and a distinct CodeRabbit-caught wording slip - # on the PR that reverted it) -- landing the corrected POSITION on its own first, with zero - # steps depending on it yet, lets the very first conda-full run after this change prove - # (via the job summary / step logs) that `available` now correctly flips to 'true' once - # envsmoke's real install succeeds, before any gating logic is re-added in a follow-up PR. + # Re-wired 2026-07-27 (owner sign-off, full risk/benefit assessment in chat -- see CLAUDE.md + # Active Backlog item 7's own closing entry for the summary) after the corrected POSITION + # above was empirically confirmed working across two real conda-full runs (PR #395, #396: + # `available` correctly read `true` both times). The 27 downstream conda-full-only self-test + # steps below now gate on `steps.conda_avail.outputs.available == 'true'` -- BUT this alone + # would reintroduce exactly the PR #390 risk (a wrong condition silently skips real tests + # while the job stays green), so it never ships without its paired tripwire: the very next + # step unconditionally FAILS THE JOB (not skip-silently) whenever this reads anything other + # than 'true' in the conda-full lane, regardless of whether the root cause is a genuine + # Miniconda install failure or a bug in this gating mechanism itself -- owner's explicit + # direction was to default to a loud failure in either case, not a graceful/silent one. if: ${{ !cancelled() && env.HP_CACHE_CORRUPTED != '1' }} id: conda_avail shell: pwsh @@ -362,18 +366,79 @@ jobs: "available=$($avail.ToString().ToLowerInvariant())" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding ascii -Append Write-Host "Miniconda available at shared path: $avail" # derived requirement: this diagnostic's own output was previously observable - # (Write-Host + step output) but had no NDJSON row -- CodeRabbit flagged the gap on - # PR #394. Non-gating (pass is always true; this step never fails the job), so it is - # safe to record even before any downstream if: condition depends on it. + # (Write-Host + step output) had no NDJSON row -- CodeRabbit flagged the gap on + # PR #394. This step itself never fails the job (that judgment now lives in the + # paired tripwire step immediately below) -- it only ever reports the observed fact. $row = [ordered]@{ id = 'diag.conda.available' pass = $true - desc = 'Miniconda availability diagnostic (non-gating, unwired)' + desc = 'Miniconda availability diagnostic (raw fact only; see diag.conda.available.gate for the enforced judgment)' details = [ordered]@{ available = $avail } } | ConvertTo-Json -Compress -Depth 8 $row | Add-Content 'tests\~test-results.ndjson' -Encoding Ascii $row | Add-Content 'ci_test_results.ndjson' -Encoding Ascii + - name: "Enforce Miniconda availability (conda-full only; fails loud, never skips silently)" + # derived requirement: this is the tripwire that makes the conda_avail gate above safe to + # use. It intentionally does NOT try to distinguish "Miniconda genuinely failed to install" + # (which already independently fails the "Self-test: real env smoke (CI-only)" step's own + # self.env.smoke.conda NDJSON row today, via $bootstrapPass requiring a clean interpreter + + # entry-run + no [ERROR] lines, not just a zero exit code -- see selfapps_envsmoke.ps1) from + # "a bug in this gating mechanism itself" (the PR #390 failure class: a wrong lane/step/ + # output reference that silently skips real tests while the job stays green). Per the + # owner's explicit direction, BOTH cases fail this step loudly and unconditionally, even + # though the first case is technically "not our fault" (a transient external Miniconda/ + # network issue) -- a loud, attributable failure on a rare true negative is preferred over + # any risk of a silent false negative recurring. Runs in every lane (matching conda_avail's + # own if:) so its own NDJSON row is always present in every artifact, uniformly skip=true + # outside conda-full per this repo's established skip-pattern convention (see + # docs/agent-interconnect.md "Skip pattern template"). + if: ${{ !cancelled() && env.HP_CACHE_CORRUPTED != '1' }} + id: conda_avail_gate + shell: pwsh + run: | + $avail = '${{ steps.conda_avail.outputs.available }}' + if ($env:HP_CI_LANE -ne 'conda-full') { + $row = [ordered]@{ + id = 'diag.conda.available.gate' + pass = $true + desc = 'Miniconda availability gate (enforced judgment; conda-full only)' + details = [ordered]@{ skip = $true; reason = 'not-conda-full'; lane = $env:HP_CI_LANE } + } | ConvertTo-Json -Compress -Depth 8 + $row | Add-Content 'tests\~test-results.ndjson' -Encoding Ascii + $row | Add-Content 'ci_test_results.ndjson' -Encoding Ascii + exit 0 + } + if ($avail -eq 'true') { + $row = [ordered]@{ + id = 'diag.conda.available.gate' + pass = $true + desc = 'Miniconda availability gate (enforced judgment; conda-full only)' + details = [ordered]@{ available = $avail } + } | ConvertTo-Json -Compress -Depth 8 + $row | Add-Content 'tests\~test-results.ndjson' -Encoding Ascii + $row | Add-Content 'ci_test_results.ndjson' -Encoding Ascii + exit 0 + } + $row = [ordered]@{ + id = 'diag.conda.available.gate' + pass = $false + desc = 'Miniconda availability gate (enforced judgment; conda-full only)' + details = [ordered]@{ available = $avail } + } | ConvertTo-Json -Compress -Depth 8 + $row | Add-Content 'tests\~test-results.ndjson' -Encoding Ascii + $row | Add-Content 'ci_test_results.ndjson' -Encoding Ascii + Write-Host '::error::Miniconda was NOT detected at the shared install path after "Self-test: real env smoke (CI-only)" completed. 27 downstream conda-full self-tests are gated on this and will have been skipped.' + Write-Host '::error::Two possible root causes, both must be checked: (1) a genuine Miniconda install failure during real env smoke -- this already independently fails that step''s own self.env.smoke.conda NDJSON row, so if that row also failed, this is likely just secondary confirmation; check tests\~envsmoke\~envsmoke_bootstrap.log and tests\~envsmoke\~setup.log below and in the job artifacts. (2) a bug in this gating mechanism itself (wrong lane/step/output reference) -- if the env-smoke step passed cleanly, treat this as case (2) and investigate the conda_avail / conda_avail_gate steps and the 27 if: conditions before assuming this is safe to ignore. See CLAUDE.md Active Backlog item 7 for the documented history of exactly this failure class (PR #390).' + $envsmokeLog = 'tests\~envsmoke\~envsmoke_bootstrap.log' + if (Test-Path -LiteralPath $envsmokeLog) { + Write-Host '--- tail of tests\~envsmoke\~envsmoke_bootstrap.log ---' + Get-Content -LiteralPath $envsmokeLog -Tail 40 -ErrorAction SilentlyContinue + } else { + Write-Host '(tests\~envsmoke\~envsmoke_bootstrap.log not found -- real env smoke may not have run at all)' + } + exit 1 + - name: "Self-test: uv contract assertions (contract-uv* only)" if: ${{ matrix.mode == 'contract-uv' || matrix.mode == 'contract-uv-fail' }} continue-on-error: true @@ -402,13 +467,13 @@ jobs: & tests\selfapps_reqspec.ps1 - name: "Self-test: dep-check skip scenario (real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} shell: pwsh run: | & tests\selfapps_depcheck.ps1 - name: "Self-test: warnfix PASS scenario (real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} env: WARNFIX_SCENARIO: pass shell: pwsh @@ -416,7 +481,7 @@ jobs: & tests\selfapps_warnfix.ps1 - name: "Self-test: warnfix XFAIL scenario (real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} env: WARNFIX_SCENARIO: xfail shell: pwsh @@ -424,7 +489,7 @@ jobs: & tests\selfapps_warnfix.ps1 - name: "Self-test: warnfix REAL scenario - heuristic prevents warnfix (real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} env: WARNFIX_SCENARIO: real shell: pwsh @@ -432,7 +497,7 @@ jobs: & tests\selfapps_warnfix.ps1 - name: "Self-test: warnfix REAL_WARNFIX scenario - warnfix fixes non-heuristic dep (real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} env: WARNFIX_SCENARIO: real_warnfix shell: pwsh @@ -440,7 +505,7 @@ jobs: & tests\selfapps_warnfix.ps1 - name: "Self-test: warnfix REAL_WARNFIX_DELAYED scenario - warnfix processes delayed imports (real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} env: WARNFIX_SCENARIO: real_warnfix_delayed shell: pwsh @@ -575,13 +640,13 @@ jobs: & tests\selfapps_pvw_idempotent.ps1 - name: "Self-test: pre-build collect-submodules double-gate (real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} shell: pwsh run: | & tests\selfapps_collect.ps1 - name: "Self-test: strict --hidden-import auto-recovery (real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} shell: pwsh run: | & tests\selfapps_hidden_import.ps1 @@ -590,7 +655,7 @@ jobs: # auto-recovery loop to its 3-attempt cap without ever succeeding (its sibling above only # covers the one-shot-recoverable success path). - name: "Self-test: strict --hidden-import auto-recovery exhaustion (real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} shell: pwsh run: | & tests\selfapps_hidden_import_exhaust.ps1 @@ -701,13 +766,13 @@ jobs: & tests\selfapps_interactive_stdin.ps1 - name: "Self-test: EXE smokerun XFAIL bad import (real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} shell: pwsh run: | & tests\selfapps_exefail.ps1 - name: "Self-test: PyInstaller build failure correctly reported, not masked (execfail, real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} shell: pwsh env: PYI_FAIL_SCENARIO: execfail @@ -715,7 +780,7 @@ jobs: & tests\selfapps_pyinstaller_fail.ps1 - name: "Self-test: PyInstaller build failure correctly reported, not masked (output_vanish, real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} shell: pwsh env: PYI_FAIL_SCENARIO: output_vanish @@ -723,7 +788,7 @@ jobs: & tests\selfapps_pyinstaller_fail.ps1 - name: "Self-test: no-EXE briefing is honest when the interpreter fallback also fails (execfail_runtimefail, real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} shell: pwsh env: PYI_FAIL_SCENARIO: execfail_runtimefail @@ -731,67 +796,67 @@ jobs: & tests\selfapps_pyinstaller_fail.ps1 - name: "Self-test: REQ-021 py_compile pre-flight syntax error (real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} shell: pwsh run: | & tests\selfapps_preflight.ps1 - name: "Self-test: EXE smokerun XFAIL missing data file (real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} shell: pwsh run: | & tests\selfapps_exedata_fail.ps1 - name: "Self-test: EXE smokerun XFAIL dynamic import not bundled (real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} shell: pwsh run: | & tests\selfapps_exedyn_fail.ps1 - name: "Self-test: fast-path broken-EXE graceful fallback (real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} shell: pwsh run: | & tests\selfapps_exefastpath.ps1 - name: "Self-test: fail-fast probe -- fast-failure discard and alive-past-probe no-discard (real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} shell: pwsh run: | & tests\selfapps_failfast_probe.ps1 - name: "Self-test: post-execution checkpoint -- accept and decline (real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} shell: pwsh run: | & tests\selfapps_postexec_checkpoint.ps1 - name: "Self-test: super-user skip hooks (conda-full only)" - if: ${{ !cancelled() && matrix.mode == 'conda-full' }} + if: ${{ !cancelled() && matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true' }} shell: pwsh run: | & tests\selfapps_skiphooks.ps1 - name: "Self-test: REQ-002 timed entry picker (conda-full only)" - if: ${{ !cancelled() && matrix.mode == 'conda-full' }} + if: ${{ !cancelled() && matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true' }} shell: pwsh run: | & tests\selfapps_entry_picker.ps1 - name: "Self-test: REQ-009 cascade consent timed prompt (conda-full only)" - if: ${{ !cancelled() && matrix.mode == 'conda-full' }} + if: ${{ !cancelled() && matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true' }} shell: pwsh run: | & tests\selfapps_cascade_timed.ps1 - name: "Test pandas/openpyxl heuristic (conda-full only)" - if: ${{ !cancelled() && matrix.mode == 'conda-full' }} + if: ${{ !cancelled() && matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true' }} shell: pwsh run: | & tests\selfapps_pandas_excel.ps1 - name: "Test pip gap-fill safety net (conda-full only)" - if: ${{ !cancelled() && matrix.mode == 'conda-full' }} + if: ${{ !cancelled() && matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true' }} shell: pwsh run: | & tests\selfapps_pipgap.ps1 @@ -925,19 +990,19 @@ jobs: if (-not $pass) { Write-Host $outStr; exit 1 } - name: "Self-test: runtime.txt write-back (real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} shell: pwsh run: | & tests\selfapps_runtime_writeback.ps1 - name: "Self-test: pyvisa NI-VISA detection (real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} shell: pwsh run: | & tests\selfapps_pyvisa.ps1 - name: "Self-test: pyproject.toml Python version precedence (real/conda-full only)" - if: ${{ !cancelled() && (matrix.mode == 'real' || matrix.mode == 'conda-full') }} + if: ${{ !cancelled() && (matrix.mode == 'real' || (matrix.mode == 'conda-full' && steps.conda_avail.outputs.available == 'true')) }} shell: pwsh run: | & tests\selfapps_pyproject_precedence.ps1 diff --git a/CLAUDE.md b/CLAUDE.md index f41e6a98..a64d5a09 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -845,6 +845,67 @@ further.)* two independent bugs from this exact mechanism is reason enough to keep that a deliberate, owner-confirmed step rather than something a single green run alone unlocks. + **Step 2 shipped, 2026-07-27, owner-directed with full rigor after a detailed risk-vs-benefit + assessment posted in chat.** The owner's own framing: "yes default to fail CI if any problem + for loud notification even if it isn't necessarily the fault of things we control." Two pieces + shipped TOGETHER, deliberately never one without the other -- shipping the gate alone would + reintroduce exactly the PR #390 risk (a wrong condition silently skips real tests while the job + stays green): + - The 27 `real/conda-full`/`conda-full`-only steps now gate their conda-full half on + `steps.conda_avail.outputs.available == 'true'` (skip fast instead of each independently + retrying a doomed sub-bootstrap). + - A new paired step, "Enforce Miniconda availability" (`id: conda_avail_gate`), runs + immediately after `conda_avail` in EVERY lane (skip=true outside conda-full) and + unconditionally FAILS the job (`Write-Host '::error::...'` + `exit 1`, no + `continue-on-error`) whenever `available` is anything other than `'true'` in conda-full -- + deliberately without trying to distinguish "genuine Miniconda install failure" from "a bug in + this gating mechanism itself." On failure it also prints the tail of + `tests\~envsmoke\~envsmoke_bootstrap.log` inline for immediate root-cause visibility. + - **A key fact that shaped the final design, found by re-reading `tests/selfapps_envsmoke.ps1` + before implementing rather than assuming**: a GENUINE Miniconda install failure already + independently fails the "Self-test: real env smoke (CI-only)" step's own `self.env.smoke.conda` + NDJSON row TODAY, with no changes needed -- `$bootstrapPass` requires a clean interpreter, + successful entry-run, PyInstaller output, AND the absence of any `[ERROR]` line in the + bootstrap log, not just a zero process exit code (which the file's own `:die`-exit-code + lessons-learned entry already warns is not by itself reliable evidence of success). This means + the tripwire's genuinely NEW protection is narrower and more precise than "catch Miniconda + failing" -- it specifically catches a bug in the gate/tripwire mechanism ITSELF (wrong lane, + wrong step id, wrong output name, a GH Actions expression typo) that could cause the 27 steps + to skip even when conda_avail's own check is working correctly. The genuine-failure case still + gets caught too (belt-and-suspenders, via an independent signal -- `Test-Path` on disk vs. + parsing bootstrap log text -- rather than relying on the log-parsing signal alone), exactly + per the owner's "default to fail CI if any problem" direction. + - **Recovery-mechanism exploration, per the owner's own ask ("if there is any more proactive + recovery mechanism... to make the benefit more worth it").** Traced what already exists + entirely inside `run_setup.bat`'s own single invocation (i.e. within ONE call to + `selfapps_envsmoke.ps1`): AllUsers -> JustMe Miniconda install fallback (`:tci_justme`), + REQ-022's transient-network detect-and-retry for `conda create`/bulk install, REQ-013's + connectivity-aware retry (recently hardened, see this doc's own Closed Backlog entry), a + secondary Miniconda download URL, and Active Backlog item 14's own bounded (60-minute) + installer timeout so a hung installer can't hang forever. **Decision: did NOT add a + CI-orchestration-level retry (e.g. wrapping the whole envsmoke step in a re-run-on-failure + action).** Two reasons: (1) no genuine Miniconda install failure has ever been observed in + this repo's real CI history despite extensive documented usage (the one related incident -- + a `conda.anaconda.org` 403 -- was already a transient network blip, already covered by REQ-022's + own retry, not an install failure) -- the marginal benefit of a SECOND retry layer on top of an + already-comprehensive one is small against a problem that has not actually materialized; (2) a + CI-level auto-retry that silently succeeds on its second attempt would REDUCE signal quality by + smoothing over a real transient issue instead of surfacing it, which cuts directly against the + owner's own stated preference for loud, attributable failure even when "not our fault." The + lower-risk, genuinely additive piece of "more proactive" WAS shipped instead: the tripwire's + own diagnostic-log-tail printing (above), which makes whichever failure DOES occur maximally + actionable without extra digging, at zero new control-flow risk (pure read-only enrichment). + - **No genuinely blocking open question was found while implementing this** -- confirmed via a + full manual trace of all 27 target `if:` conditions (diff-scope-verified: exactly 27 lines + changed, matching the enumerated step names one-for-one) plus `yamllint`/`actionlint`, both + clean. Nothing added to `docs/open-questions.md`. + - **STATUS: item 7 is now fully closed, gating-lane half included.** Real-CI confirmation that + the gate correctly reads `true` in a normal conda-full run, and correctly fails loud in a + genuinely-broken one, is the one thing that could not be verified locally -- watch the next + conda-full run closely, and specifically confirm the OLD-style circular-self-skip failure mode + (PR #390) cannot recur even if some future edit accidentally reintroduces it, since it would + now hard-fail instead of silently passing. + ## Cold Storage (promising ideas, deliberately shelved -- revisit only if a named trigger fires) **Scope, and how this differs from Active Backlog and Known Findings**: an Active Backlog item is diff --git a/docs/agent-ndjson.md b/docs/agent-ndjson.md index 8b4e1459..c04a706c 100644 --- a/docs/agent-ndjson.md +++ b/docs/agent-ndjson.md @@ -207,7 +207,7 @@ self.corrupt.conda.heal.decline, self.corrupt.conda.heal.accept, self.corrupt.conda.override_exit, self.corrupt.uv.detect, -diag.conda.available +diag.conda.available, diag.conda.available.gate ``` `self.corrupt.conda.override_exit` (CLAUDE.md Active Backlog item 12) covers the @@ -530,15 +530,25 @@ self.interactive.stdin.roundtrip `~hidden_import_scan.py`'s own tried-list exclusion and stop the loop early via "no next hidden import found", never reaching the iteration cap this test needs to exercise. - `diag.conda.available` (inline `.github/workflows/batch-check.yml`, the "Check Miniconda - availability (diagnostic only -- not yet wired to any if: condition)" step -- see CLAUDE.md - Active Backlog item 7's `conda_avail` history) is always `pass: true` (non-gating; the step - itself never fails the job) and carries `details.available` (`true`/`false`) reflecting whether - Miniconda was found at the shared `%PUBLIC%\Documents\Miniconda3` path at that point in the job. - Added on PR #394 per a CodeRabbit finding: the step's own `Write-Host`/output had been observable - since PR #390 with no NDJSON row. Present in every non-`HP_CACHE_CORRUPTED` lane run regardless - of `matrix.mode`, since the step itself has no lane restriction (only its future `if:` wiring, - deferred pending owner sign-off per the same backlog item, will restrict its meaning to - `real`/`conda-full`). + availability" step -- see CLAUDE.md Active Backlog item 7's `conda_avail` history) is always + `pass: true` (this step itself never fails the job, by design -- it only reports the observed + fact) and carries `details.available` (`true`/`false`) reflecting whether Miniconda was found at + the shared `%PUBLIC%\Documents\Miniconda3` path at that point in the job. Added on PR #394 per a + CodeRabbit finding: the step's own `Write-Host`/output had been observable since PR #390 with no + NDJSON row. Present in every non-`HP_CACHE_CORRUPTED` lane run regardless of `matrix.mode`, since + the step itself has no lane restriction. +- `diag.conda.available.gate` (inline `.github/workflows/batch-check.yml`, the "Enforce Miniconda + availability" step immediately after `diag.conda.available`'s own step) is the ENFORCED judgment + that gates 27 `conda-full`-only self-test steps and, unlike its sibling above, DOES fail the job + when it reads `pass: false` (`Write-Host '::error::...'` + `exit 1`, no `continue-on-error`). + `skip: true, reason: 'not-conda-full'` in every lane except `conda-full`, where it re-reads the + same `steps.conda_avail.outputs.available` value and requires exactly `'true'` -- anything else + (including a never-set/empty value) fails loud, deliberately without distinguishing "Miniconda + genuinely failed to install" from "a bug in this gating mechanism itself" (both are surfaced the + same way, per an explicit owner decision to prefer a loud failure over any risk of a silent one + recurring -- see CLAUDE.md's Active Backlog item 7 for the full PR #390 incident this protects + against and the risk/benefit assessment behind this design). On failure it also prints the tail + of `tests\~envsmoke\~envsmoke_bootstrap.log` inline for immediate root-cause visibility. - A row absent from the diag site means the test script either was not reached, threw before the `Write-NdjsonRow` call, or the lane skipped that selfapps file. - Rows gated by `pyFileCount` (e.g. `entry.single.direct`) will be absent whenever the From f7c55f76d036d157fb2b18b8247faa2c97004518 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 27 Jul 2026 23:36:12 +0000 Subject: [PATCH 2/2] ci: fix CodeRabbit findings on PR #397 (every-lane wording, template-injection hardening) - The conda_avail/conda_avail_gate steps' own if: already excludes the HP_CACHE_CORRUPTED=1 path, so "runs in every lane" was inaccurate -- corrected to "every non-cache-corrupted lane" in both the workflow comment and CLAUDE.md's mirrored description. - Route conda_avail's output through an env: mapping instead of interpolating it directly into the PowerShell script body, per a zizmor template-injection info-level finding. The value was never attacker-controlled (an enum string from our own prior Test-Path check), but the env: form is the established safe idiom and costs nothing to use for newly-authored code. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_015xbWLPbiaKVsobB9FZy8kS --- .github/workflows/batch-check.yml | 15 ++++++++++----- CLAUDE.md | 6 ++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/batch-check.yml b/.github/workflows/batch-check.yml index 9a6ee695..e189e02b 100644 --- a/.github/workflows/batch-check.yml +++ b/.github/workflows/batch-check.yml @@ -389,15 +389,20 @@ jobs: # owner's explicit direction, BOTH cases fail this step loudly and unconditionally, even # though the first case is technically "not our fault" (a transient external Miniconda/ # network issue) -- a loud, attributable failure on a rare true negative is preferred over - # any risk of a silent false negative recurring. Runs in every lane (matching conda_avail's - # own if:) so its own NDJSON row is always present in every artifact, uniformly skip=true - # outside conda-full per this repo's established skip-pattern convention (see - # docs/agent-interconnect.md "Skip pattern template"). + # any risk of a silent false negative recurring. Runs in every non-cache-corrupted lane + # (matching conda_avail's own if:) so its own NDJSON row is always present in every such + # artifact, uniformly skip=true outside conda-full per this repo's established skip-pattern + # convention (see docs/agent-interconnect.md "Skip pattern template"). Both this step and + # conda_avail above are themselves skipped, like everything else in this job, on the rare + # HP_CACHE_CORRUPTED=1 path (cache-lane restore failure -- see this file's own "cache lane + # Miniconda-corruption handling" lessons-learned entry). if: ${{ !cancelled() && env.HP_CACHE_CORRUPTED != '1' }} id: conda_avail_gate + env: + HP_CONDA_AVAIL: ${{ steps.conda_avail.outputs.available }} shell: pwsh run: | - $avail = '${{ steps.conda_avail.outputs.available }}' + $avail = $env:HP_CONDA_AVAIL if ($env:HP_CI_LANE -ne 'conda-full') { $row = [ordered]@{ id = 'diag.conda.available.gate' diff --git a/CLAUDE.md b/CLAUDE.md index a64d5a09..20969199 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -855,8 +855,10 @@ further.)* `steps.conda_avail.outputs.available == 'true'` (skip fast instead of each independently retrying a doomed sub-bootstrap). - A new paired step, "Enforce Miniconda availability" (`id: conda_avail_gate`), runs - immediately after `conda_avail` in EVERY lane (skip=true outside conda-full) and - unconditionally FAILS the job (`Write-Host '::error::...'` + `exit 1`, no + immediately after `conda_avail` in every non-cache-corrupted lane (skip=true outside + conda-full; both steps are skipped, like everything else in the job, on the rare + `HP_CACHE_CORRUPTED=1` path) and unconditionally FAILS the job (`Write-Host '::error::...'` + + `exit 1`, no `continue-on-error`) whenever `available` is anything other than `'true'` in conda-full -- deliberately without trying to distinguish "genuine Miniconda install failure" from "a bug in this gating mechanism itself." On failure it also prints the tail of