Parallel MSBuild fan-out (e.g. ns7zip x86/x64/x86-ansi) over pypsrp
Linux→Win fails with MSBuild MSB6003 / Win32 1816 'Not enough quota'
because two default guest limits are too tight for concurrent native
processes via WinRM:
- Plugin Microsoft.PowerShell MaxMemoryPerShellMB = 150 MB
(pypsrp uses the plugin endpoint, not the Shell endpoint that
Deploy was previously bumping)
- Non-interactive desktop heap SharedSection 3rd field = 768 KB
(Session 0 csrss; CreateProcess fails when 3+ cl.exe spawn)
Pre-migration Win→Win went through Invoke-Command on PSSession with
quotas inherited from the local process, so the limits never bit.
Changes:
- Deploy-WinBuild2025.ps1 / Deploy-WinBuild2022.ps1: also set the
plugin-level MaxMemoryPerShellMB=2048, bump MaxProcessesPerShell
25→100, patch SharedSection 3rd field 768→4096 (reboot needed for
csrss to pick up, applied by post-install shutdown).
- Install-CIToolchain-WinBuild2025/2022.ps1: add Assert-Step for
plugin quota, MaxProcessesPerShell≥100, SharedSection≥4096; fail
hard if any are under threshold.
- docs/WINDOWS-TEMPLATE-SETUP.md: new "WinRM quotas e desktop heap"
section explaining the diagnosis + one-shot patch for legacy
templates; updated Step 3 validation row + troubleshooting entry.
- AGENTS.md: error #13 with symptom, root cause, and pointer to docs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
If ci-backup-template.service is killed mid-run (SIGTERM), the Python
finally block that restarts act-runner.service is skipped. A subsequent
backup then finds the runner already inactive, _stop_runner returns
False, and the finally restart is gated off — leaving the runner down
indefinitely (incident: 21 mag 2026, runner stayed off ~80 min).
Add ExecStopPost=-/bin/systemctl start act-runner.service so systemd
guarantees the runner is restarted regardless of how the unit exits
(success, failure, signal). The `-` prefix preserves the unit's own
exit status.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add plans/idea-3-powershell-removal.md: Phase C goal is to eliminate
the pwsh dependency from the Linux host by porting the remaining PS
scripts (bench run, validate host, smoke run) to Python sub-commands.
- Update idea-3-esxi-support.md header: Fase C → Fase D.
- Update ideas-overview.md: new four-phase table (A/B/C/D) with
prerequisiti and criteri for Phase C.
- Rename all "Hook futuri Fase C" (ESXi) → "Hook futuri Fase D" in
implementation-plan-A-B.md; add Phase C/D entries to summary and
references section.
- Update PhaseB-user-checklist.md end note and CLAUDE.md to point
Phase C → pwsh removal, Phase D → ESXi.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Add `retention run` command (ports Invoke-RetentionPolicy.ps1): purges
old artifact/log dirs with aggressive mode when disk space is low.
- Add `template backup` command (ports Backup-CITemplate.ps1): 7z -mx=1
compressed archives in /var/lib/ci/backups/, prunes to keep-count=3,
stops/restarts act-runner.service around the copy.
- Update ci-retention-policy.service and ci-backup-template.service to use
Python; pwsh is no longer required on the Linux host.
- Fix ci-watch-runner-health.service: pass --service-name act-runner
(Linux service name, not Windows act_runner default).
- Fix _list_orphans in vm.py: wrap is_dir() inside the OSError try block
so a stat() failure on an entry is silently skipped rather than raised.
- Mark B5 complete in PhaseB-user-checklist.md.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
User requirement (non-negotiable): the project must stay
first-class compatible with a Windows host permanently — Phase B
adds Linux *alongside* Windows, never replacing/degrading it.
- implementation-plan-A-B §2: categorical-constraint banner; B8
decommissioning cancelled (kept only as historical record);
master checklist B8 row updated.
- PhaseB-user-checklist: Passo 9 (B8) cancelled -> dual-host kept;
Passo 8 no longer a decommission gate; tracking table + closing
note updated.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
build-ns7zip builds an EXTERNAL repo (nsis-plugin-ns7zip). The
`push: tags v*` trigger (inherited from when this workflow lived in
the plugin repo) made every CI-system tag — including the Phase A
marker v2.0.0-phaseA — auto-build and publish an ns7zip release on
the CI-system repo. Remove the tag trigger; manual dispatch only.
The release job's tag-gate `if` now never fires (skipped) — release
flow already validated with a throwaway tag.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Medium update: exec-summary status (A COMPLETATA, B ready), master
checklist A items [x], Phase A gating satisfied, §1 COMPLETATA banner
with real validation deviations (LocalSystem venv/keyring, github-free
artifacts, transport, shim common-params, deferred §8.1/§8.2), §7
A-side definition-of-done [x]. Fase B (§2/§3/§4) + risk matrix +
cronoprogramma left intact as the Phase B design reference alongside
PhaseB-user-checklist.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
feature merged to main (2aa12bb), tag v2.0.0-phaseA pushed
(triggers build-ns7zip as final end-to-end validation of main).
All Passi 1-9 [x]. Residual non-blocking cleanup: delete the test
Gitea releases via UI (tags kept).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
feature merged to main (2aa12bb, tree == feature). Passi 6/7/8 [x],
Passo 9 in progress (release tag pending).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The blind -X -> --x shims forwarded PowerShell common parameters
(-ErrorAction, -Verbose, ...) to the Python CLI, e.g.
Measure-CIBenchmark.ps1 -> Remove-BuildVM.ps1 -ErrorAction
SilentlyContinue produced `ci_orchestrator vm remove --error-action`
-> "No such option" -> destroy failed (orphan VM left). Skip
common switches and consume value-taking ones. Applied to all 7
blind-loop shims; param-style shims (New-BuildVM, Invoke-RemoteBuild,
Get-BuildArtifacts) are not affected. PSSA clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Start-BurnInTest.ps1 (lab wrapper: burnin-dummy + build.ps1) — 4
concurrent x 3 rounds all PASS, clean VM/lock teardown each round.
Note the gotcha: raw Test-CapacityBurnIn.ps1 without -BuildCommand
defaults to dotnet and fails on the non-.NET dummy repo.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
All workflows passed; the "if one fails" item never triggered. Replace
the dangling [ ] with a historical note so Passo 6 has no false-open
task.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
lint, self-test, build-ns7zip (Win+Linux) and the release job
(Gitea release + asset upload via REST API, auto token) all green.
Phase A Gitea end-to-end validated.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
build-nsInnoUnp.yml shipped without the closing brace of the
asset-upload foreach (and no EOF newline) since its origin; the
release job had never run (tag-only) so the PS parse error
"MissingEndCurlyBrace" was latent until the test tag triggered it.
Close the loop, add per-asset + final Write-Host. PS parse verified.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Remove-Securely: suppress PSUseShouldProcessForStateChangingFunctions
(internal best-effort shred; ShouldProcess prompts are undesirable)
+ add [CmdletBinding()].
- Replace the two empty catch blocks (Set-CIGuestCredential.ps1,
Test-CIGuestWinRM.ps1) with Write-Verbose so PSAvoidUsingEmptyCatchBlock
is satisfied while keeping best-effort behavior.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
WSL on the host builds the 3 configs in parallel in ~103s (= one
config) — the parallel design is correct. The 4-vCPU CI VM ran 3x
make -j on 4 cores -> oversubscription thrash -> ~3x slower. Not
structural / not RAM. Bump guest-cpu to 8 (job.py patches the clone
VMX numvcpus); host is 10c/20t, this matrix is 2 VMs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
job.py already patches the clone VMX (numvcpus/memsize) via
--guest-cpu/--guest-memory-mb; expose them as action inputs
(default '0' = keep template). build-ns7zip.yml sets
guest-memory-mb: 8192 — the parallel MinGW cross-compile thrashes at
the 4 GB Linux template default (Windows template is 6 GB).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
After a memory-heavy parallel build the guest can momentarily fail to
launch wsmprovhost (WSManFault 1018), which broke artifact collect
(now does Compress-Archive + fetch over WinRM). run() retries up to 4x
with exponential backoff on transient WSMan markers, dropping the
cached shell so a fresh one is opened once the guest recovers.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A bare `if: github.ref_type == 'tag'` on a needs-dependent job can
leave `release` stuck in "blocked" instead of skipped for
workflow_dispatch runs on Gitea. Use the explicit
${{ github.event_name == 'push' && github.ref_type == 'tag' }} form.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- SshTransport.run_streaming: incremental channel recv, forwards
stdout/stderr live (no more one-block-at-end). _linux_build uses it
with PYTHONUNBUFFERED so the guest python flushes promptly.
- _tail_lines split out for failure messages (no double print).
- Windows live streaming deferred (TODO 8.2) — pypsrp execute_ps
returns only at completion.
- build-ns7zip.yml: use-git-clone:'true' (in-guest), repo-url ->
https://gitea.emulab.it/... (guest-reachable; host SSH alias was
host-only). Requires GiteaPAT in the LocalSystem keyring or a public
nsis-plugin-ns7zip.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ns7zip now supports --dist (commit 57133b7 on nsis-plugin-ns7zip):
DLLs land in <repo>/dist/<config>. Build with `python build_plugin.py
--dist` and collect artifact-source 'dist'.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Linux template provides `python`, so the OS-aware split is
unnecessary. Use `python build_plugin.py` for both matrix legs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
build_plugin.py is a dispatcher: it auto-detects host/toolset and
forwards extra args to the per-version script, which rejected
--final/--dist-dir. Both the Windows (legacy 2601_vs2026) and Linux
build scripts copy nsis7z.dll into <repo>/plugins/{x86-ansi,
x86-unicode,amd64-unicode}, not dist.
- build-command: just `python build_plugin.py` (python3 on Linux —
Ubuntu 24.04 has no `python`).
- artifact-source: 'plugins' (fixes the "dist missing" failure too).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Workflow now targets Simone/nsis-plugin-ns7zip. Renamed the file and
the workflow `name:`, and updated all references in active docs
(plans/archived and docs/archived left as frozen history).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Switch the cross-repo build target to Simone/nsis-plugin-ns7zip
(repo-url, artifact-name, release asset name).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
On a non-zero build the guest stdout/stderr was written unflushed
then a ClickException raised, so the cause was lost (only
"build command failed (exit 1)" surfaced). _report_build_output now
emits the output with markers, flushes both streams, and returns the
last lines which are appended to the failure message — diagnosable
even if act_runner buffering drops the body. Applied to Linux + Windows.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PowerShell 5.1 drops a zero-length string when splatting to a native
exe, so an empty $repoCommit shifted flag/value pairing and click saw
the build command as an unexpected positional. Append --commit only
when a commit is set.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Runner fetches the composite action from @main. Propagates the
cross-repo branch/commit inputs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
github.ref_name/github.sha refer to the workflow's own repo. When
repo-url points elsewhere (build-nsInnoUnp builds
nsis-plugin-nsinnounp) the workflow's branch does not exist there
("Remote branch ... not found in upstream origin"). New inputs:
- repo-branch: target branch (default: github.ref_name)
- repo-commit: target commit (default: github.sha only if repo-url
not overridden; cross-repo => empty = branch tip, no pin)
build-nsInnoUnp.yml sets repo-branch: main.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
_host_clone hid all stderr ("exit 128" with no cause). Surface the
last stderr lines with scheme://user:pass@ credentials masked, so
failures (e.g. unresolved SSH alias under LocalSystem) are diagnosable.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
repo-url is ssh://gitea-ci/... — an SSH alias defined only in the
host ~/.ssh/config. With the new in-guest-clone default the guest
tried to resolve `gitea-ci` and failed (name resolution). Set
use-git-clone:'false' so the host (which has the alias + SSH key)
performs the clone.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Short-form `Simone/local-ci-cd-system/...@main` resolved to
github.com (DEFAULT_ACTIONS_URL=github) -> "authentication required:
Repository not found". Use the full gitea.emulab.it URL like
self-test.yml.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Passo 6: self-test.yml validated — in-guest clone (default) and
host-side clone+zip, Windows + Linux, all SUCCESS. Artifact handling
github-free.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Runner fetches the composite action from @main. Propagates the
default-on transport/submodules inputs and explicit flag forwarding.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The job command previously discarded --use-git-clone and --submodules
(`del`) and always in-guest cloned with submodules off — making both
action inputs dead no-ops (submodule repos silently broken).
- job.py: --use-git-clone/--host-clone and --submodules/--no-submodules
are real booleans defaulting to ON. Default = in-guest clone with
submodules. --host-clone clones on the host into a temp dir
(_host_clone) and transfers a zip; temp dir cleaned in finally.
clone_submodules now honoured (was hardcoded False).
- action.yml: use-git-clone / submodules default 'true'; forward the
explicit positive/negative flag so the Python default is overridden
deterministically.
- self-test.yml: add smoke-{windows,linux}-hostclone jobs covering the
host-side transport (default jobs already cover in-guest).
- tests: default-transport, host-clone, and --no-submodules coverage.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Artifact github.com dependency removed (fix 2 applied). Remaining:
actions/checkout@v4 in lint.yml. Recorded as deferred §8.1 with
revisit conditions (air-gapped host / total github.com independence).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Runner fetches the composite action from @main. Propagates the
github.com-free artifact handling (host-side collect dir).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
actions/upload-artifact and download-artifact run code fetched from
github.com (and v4 is GHES-incompatible, v3 deprecated). The runner
host is single with a shared filesystem, so the orchestrator's
host-side collect dir is the artifact handoff:
- action.yml: replace the two upload-artifact steps with
host-path report steps; artifacts stay under F:\CI\Artifacts and
are aged by Invoke-RetentionPolicy.ps1.
- build-nsInnoUnp.yml: release job reads
F:\CI\Artifacts\{run_id}-{run_attempt}-{target} directly instead of
download-artifact; existing Gitea REST release-asset upload unchanged.
Tradeoff (accepted): artifacts are no longer downloadable from the
Gitea Actions run page for non-tag builds — only on host disk, and as
release assets for tagged builds.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>