#!/usr/bin/env bash # The second-engine class (C35, 5 October 2026, PC 1 22:31 UTC): a playbook started a second Igneum engine beside the # installed app through a redirected PIPE (PowerShell's Process.Start with RedirectStandardOutput). A pipe's write end is # inherited by every process the engine starts (its miners and workers); when the job was aborted, the installed app's # jobs runner waited for an EOF the orphaned grandchildren never sent and its quit hung for 24 minutes, and the second # engine's miners mined on against the relaunched app. Rule for every playbook that starts an engine: (1) the engine's # output goes to a FILE (Start-Process -RedirectStandardOutput ), never a pipe into the script; (2) the engine's # whole process tree is ended at the end and on the budget (taskkill /T /F), so nothing is orphaned; the installed # app's miners come back only after that (the job runner restarts them when the script ends). This check fails CI when # a playbook starts an engine without both, or without IGNEUM_APP_NO_OTA = '1' (the third line, same night: the second # engine's updater found itself under min_supported_version and ran the installer, which quit the installed app). set -euo pipefail cd "$(dirname "$0")/../.." fail=0 while IFS= read -r f; do grep -qE "igneum-app(\.exe)?['\"]? *(--sweep|-ArgumentList '--sweep'|--no-open)|ArgumentList '--sweep'|\.Arguments = '--sweep'" "$f" || continue if grep -qE 'RedirectStandardOutput *= *\$true|UseShellExecute *= *\$false|Register-ObjectEvent|BeginOutputReadLine' "$f"; then echo "second-engine: $f starts an engine through a pipe (RedirectStandardOutput/BeginOutputReadLine); use Start-Process -RedirectStandardOutput "; fail=1 fi if ! grep -qE 'taskkill /T /F' "$f"; then echo "second-engine: $f starts an engine without ending its process tree (taskkill /T /F) at the end"; fail=1 fi if grep -qE 'settings\.json|\.json' "$f" && grep -qE 'Set-Content[^\n]*-Encoding +utf8' <<<"$(grep -vE '^\s*#' "$f")"; then echo "second-engine: $f writes JSON with Set-Content -Encoding utf8 (a BOM the engine refuses: the copy read as defaults, no payout address, nothing mined); use [IO.File]::WriteAllText with UTF8Encoding(\$false)"; fail=1 fi if grep -qE 'settings\.json|Set-Content|WriteAllText|Out-File' <<<"$(grep -vE '^\s*#' "$f" | grep -E 'ConvertTo-Json' || true)"; then echo "second-engine: $f rewrites settings.json through ConvertTo-Json (a lossy round trip: big integers become doubles and the engine reads the whole file as defaults; run 4, 6 October 2026); copy the file verbatim, the engine applies Settings::for_measurement under --sweep"; fail=1 fi if ! grep -qE "IGNEUM_APP_NO_OTA *= *'1'" "$f"; then echo "second-engine: $f starts an engine without IGNEUM_APP_NO_OTA = '1' (its updater would run the installer, which quits the installed app: PC 1, 5 October 2026, 22:31 UTC)"; fail=1 fi done < <(git ls-files 'relay/playbooks/**' 'tools/windows/**' 'packaging/**' | grep -E '\.ps1$') [ "$fail" = 0 ] && echo "second-engine: every playbook that starts an engine logs to a file and ends its tree" exit $fail