con un clic
child-pi-spawning
Child Pi worker spawning, lifecycle callbacks, and failure modes.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Menú
Child Pi worker spawning, lifecycle callbacks, and failure modes.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Basado en la clasificación ocupacional SOC
Iterative multi-round codebase audit with diminishing-returns detection. Run 5-20+ rounds, each focusing on one specific area. Built from 19 rounds of dogfooding pi-crew on itself.
Pi TUI crew widget data sources, display priority, and rendering performance.
Multi-phase orchestration for planners and executors.
Spawn 3 adversarial subagents (Skeptic, Pragmatist, Critic) to evaluate a decision, architecture choice, or plan. Anti-anchoring: each role receives ONLY the question, not conversation history. Aggregates votes into consensus recommendation with dissent tracking. Use when facing critical decisions, architecture choices, security tradeoffs, or plan reviews where single-perspective analysis is insufficient.
Background worker, heartbeat, stale-run, crash-recovery, and deadletter workflow. Use when debugging stuck/dead workers or changing async run reliability.
Use when constructing worker prompts, reading artifacts/logs, summarizing runs, compacting context, or handing work between agents.
| name | child-pi-spawning |
| description | Child Pi worker spawning, lifecycle callbacks, and failure modes. |
| origin | pi-crew |
| triggers | ["worker crashed","worker blink","spawn failed","pid not found","child process error"] |
Child Pi workers are subprocesses spawned by task-runner.ts via runChildPi() in child-pi.ts. Understanding the spawn flow, lifecycle events, and failure modes is essential for debugging worker crashes and "worker blinks" issues.
task-runner.ts (runTeamTask)
→ runChildPi({ cwd, task, agent, model, skillPaths, signal, onLifecycleEvent })
→ child-pi.ts (runChildPi main function)
→ buildPiWorkerArgs() → getPiSpawnCommand() → spawn(command, args, options)
→ ChildProcess spawned
→ activeChildProcesses.set(pid, child)
→ input.onLifecycleEvent({ type: "spawned", pid, ts })
→ stdout.on("data") → ChildPiLineObserver
→ stderr.on("data")
→ child.on("error") → onLifecycleEvent("spawn_error")
→ child.on("exit") → onLifecycleEvent("exit")
→ child.on("close") → onLifecycleEvent("close"), settle(result)
finalDrainMs (default 2s) then SIGTERMhardKillMs (default 2s) from SIGTERM, SIGKILLactiveChildProcesses Map for global cleanupChildPiLifecycleEvent interface — emitted via onLifecycleEvent callback:
interface ChildPiLifecycleEvent {
type: "spawned" | "spawn_error" | "response_timeout" | "final_drain" | "hard_kill" | "exit" | "close";
pid?: number;
exitCode?: number | null;
error?: string;
ts: string;
}
1. spawned pid=12345 ← child.pid assigned
2. [stdout events: message, tool_execution_start, tool_execution_end, message_end...]
3. final_drain pid=12345 ← last assistant event received, SIGTERM sent
4. exit exitCode=0 ← process exited
5. close exitCode=0 ← stdio fully closed
1. spawned pid=12345
2. spawn_error error="..." ← OR →
3. exit exitCode=1
4. close exitCode=1
1. spawned pid=12345
2. [no stdout for 5 min]
3. response_timeout error="No output for 300000ms"
4. final_drain pid=12345
5. hard_kill pid=12345 ← SIGKILL after hardKillMs
6. exit exitCode=null
7. close exitCode=null
The callback bridges child-pi events → events.jsonl:
// task-runner.ts
onLifecycleEvent: (event: ChildPiLifecycleEvent) => {
appendEvent(manifest.eventsPath, {
type: `worker.${event.type}`,
runId: manifest.runId,
taskId: task.id,
message: event.error ?? `Worker ${event.type}`,
data: { pid: event.pid, exitCode: event.exitCode, error: event.error },
});
}
Why a callback instead of direct logging: child-pi.ts has no access to manifest/eventsPath. The callback lets the caller (task-runner) decide how to log.
When: executeWorkers = false or runtime.kind === 'scaffold'
Behavior: No child process spawned. runChildPi is never called. The task:
worker.spawned event — the agent appears and completes instantlyDisplay implication: In widget, scaffold agents appear and complete within 1 frame. This is normal behavior, not a bug.
Detection: runtimeKind === "child-process" triggers child spawning; "scaffold" or "live-session" skip it.
buildPiWorkerArgs() (pi-args.ts)pi
--role <role>
--task-id <taskId>
--run-id <runId>
--cwd <cwd>
[--session]
[--model <model>]
[--thinking <level>] # off/minimal/low/medium/high/xhigh
[--max-depth <n>] # from limits.maxTaskDepth (default 2)
[--skill-dir <path>] # one per skill directory
[--transcript <path>] # output transcript
--task
<task-prompt-text>
PI_EXECUTION_MODE=child # marks child process context
PI_TEAMS_WORKER=1 # enables team-worker features
PI_CREW_PARENT_PID=<pid> # parent process PID (added by child-pi.ts)
<redacted secrets> # API keys filtered by sanitizeEnvSecrets()
Resolves the pi binary path and builds the final command/args. On Windows, uses pi.cmd or pi.exe.
| Symptom | Root cause | Fix |
|---|---|---|
spawn_error: spawn returned no pid | child.pid is undefined — spawn call failed silently | Check binary path via getPiSpawnCommand() |
spawn_error: not a valid Win32 application | Wrong binary (32-bit vs 64-bit) | Reinstall pi binary |
spawn_error: Access is denied | Binary not executable, or antivirus blocking | Check file permissions, run as admin |
spawn_error: ENOENT: no such file or directory | pi not in PATH | Add pi to PATH, or use full path |
| Worker crashes with exitCode=1, no output | API key missing or wrong | Check PI_API_KEY / ANTHROPIC_API_KEY |
| Worker crashes with exitCode=1, "Model not available" | Wrong model name | Check model name in config |
| Worker spawns, logs in, then crashes | Model rate limit / quota exceeded | Check provider limits |
response_timeout: No output for 300000ms | Child process hung (network issue, model timeout) | Increase responseTimeoutMs, check network |
| Worker completes but output not captured | stdout/stderr stream issue | Check ChildPiLineObserver parsing |
| Exit code | Meaning |
|---|---|
0 | Success — worker produced output and completed |
1 | Error — worker encountered a non-fatal error (API error, validation failure) |
null | Killed — worker was SIGTERM'd or SIGKILL'd (timeout, cancel, drain) |
130 | SIGINT — interrupted by user cancel |
Note: final_drain followed by exitCode=0 means the worker completed its output before being killed. The 0 exit code preserves the result.
manifest.async.pid at spawn (via checkpointTask)hasStaleAsyncProcess() (process-status.ts) to detect dead processeskillProcessPid() (child-pi.ts) for terminationchildHardKillTimers Map for timer cleanup on exitBefore debugging worker spawn issues, verify:
If ANY answer is NO → Stop. Re-examine events.jsonl and manifest before proceeding.
spawn() is async — never await it synchronously. Use the Promise-based API.child.on("exit") and child.on("close"). Without handlers, zombie processes accumulate.onLifecycleEvent handling, worker crashes leave no traceable evidence.spawn_error: Errors on spawn (binary not found, permission denied) must be caught and logged.src/runtime/child-pi.ts — runChildPi, ChildPiLifecycleEvent, activeChildProcesses, killProcessPidsrc/runtime/task-runner.ts — executeTask loop, onLifecycleEvent callback, runtimeKindsrc/runtime/pi-args.ts — buildPiWorkerArgs, applyThinkingSuffixsrc/runtime/runtime-resolver.ts — resolveCrewRuntime, isLiveSessionRuntimeAvailable, scaffold detectionsrc/runtime/model-resolver.ts — model fallback chainsrc/utils/env-filter.ts — sanitizeEnvSecretssrc/config/defaults.ts — responseTimeoutMs, finalDrainMs, hardKillMscd pi-crew
# Test scaffold mode (no worker spawn)
PI_TEAMS_MOCK_CHILD_PI=json-success node --experimental-strip-types -e "
import { runChildPi } from './src/runtime/child-pi.ts';
const r = await runChildPi({ cwd: '.', task: 'test', agent: {name:'test'}, mock: 'success' });
console.log('exitCode:', r.exitCode);
"
npx tsc --noEmit
node --experimental-strip-types --test test/unit/task-runner.test.ts test/unit/child-pi.test.ts 2>/dev/null || echo "Tests may need specific files"
npm test