Add real-time progress feedback to multi-run simulations
Balance Check / balance-simulation (push) Successful in 11m24s
Balance Check / multi-run-balance (push) Successful in 26m35s
CI / build-and-push (push) Successful in 34s

Switch from exec() to spawn() for streaming stderr, add onProgress
callback to runner, and emit per-run progress lines from workers.
CI now shows live percentage, tick count, and era during long runs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-26 17:42:37 -04:00
parent 04d8a4e883
commit db034687d6
3 changed files with 36 additions and 11 deletions
+18 -8
View File
@@ -1,4 +1,4 @@
import { exec } from 'node:child_process';
import { spawn } from 'node:child_process';
import { writeFileSync } from 'node:fs';
import { resolve as pathResolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -74,20 +74,30 @@ interface WorkerResult {
function spawnWorker(runId: number, seed: number): Promise<WorkerResult> {
return new Promise((resolve, reject) => {
const workerPath = new URL('./worker.ts', import.meta.url).pathname.replace(/^\/([A-Z]:)/, '$1');
const cmd = `npx tsx "${workerPath}" --strategy ${strategyName} --ticks ${totalTicks} --seed ${seed} --run-id ${runId}`;
exec(cmd, { maxBuffer: 200 * 1024 * 1024 }, (error, stdout, stderr) => {
if (stderr) process.stderr.write(stderr);
if (error) {
reject(new Error(`Run #${runId} (seed ${seed}) failed: ${error.message}`));
const tsxBin = pathResolve(__dirname, '..', 'node_modules', '.bin', 'tsx');
const child = spawn(tsxBin, [workerPath, '--strategy', strategyName, '--ticks', String(totalTicks), '--seed', String(seed), '--run-id', String(runId)], {
stdio: ['ignore', 'pipe', 'pipe'],
});
let stdout = '';
child.stdout.on('data', (chunk: Buffer) => { stdout += chunk; });
child.stderr.on('data', (chunk: Buffer) => { process.stderr.write(chunk); });
child.on('close', (code) => {
if (code !== 0) {
reject(new Error(`Run #${runId} (seed ${seed}) exited with code ${code}`));
return;
}
try {
const result = JSON.parse(stdout) as WorkerResult;
resolve(result);
resolve(JSON.parse(stdout) as WorkerResult);
} catch (e) {
reject(new Error(`Run #${runId} (seed ${seed}) produced invalid JSON: ${(e as Error).message}`));
}
});
child.on('error', (err) => {
reject(new Error(`Run #${runId} (seed ${seed}) failed to spawn: ${err.message}`));
});
});
}