baphuongna/pi-crew/child-pi-spawning

Claude Code and agent skill by baphuongna/pi-crew

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Child Pi worker spawning, lifecycle callbacks, and failure modes. When NOT to use: non-Pi subprocess execution (use safe-bash); for general async reliability (use async-worker-recovery).

Install

npx vskill@latest install baphuongna/pi-crew/child-pi-spawning

SKILL.md

child-pi-spawning

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.

Spawn Flow

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)
Key components
  • ChildPiLineObserver: Parses JSON events and stdout lines from child Pi's output stream
  • Response timeout: 5-minute timer resets on every stdout/stderr chunk; on timeout → SIGTERM
  • Final drain: After last assistant event, waits finalDrainMs (default 2s) then SIGTERM
  • Hard kill: After hardKillMs (default 2s) from SIGTERM, SIGKILL
  • Active process tracking: activeChildProcesses Map for global cleanup

Lifecycle Events

ChildPiLifecycleEvent interface — emitted via onLifecycleEvent callback:

typescript
interface ChildPiLifecycleEvent {
type: "spawned" | "spawn_error" | "response_timeout" | "final_drain" | "hard_kill" | "exit" | "close";
pid?: number;
exitCode?: number | null;
error?: string;
ts: string;
}
Event sequence for normal completion:
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
Event sequence for crash:
1. spawned pid=12345
2. spawn_error error="..." ← OR →
3. exit exitCode=1
4. close exitCode=1
Event sequence for timeout:
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

onLifecycleEvent Callback Pattern

The callback bridges child-pi events → events.jsonl:

typescript
// 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.

Scaffold Mode

When: executeWorkers = false or runtime.kind === 'scaffold'

Behavior: No child process spawned. runChildPi is never called. The task:

  1. Writes the prompt to disk as an artifact
  2. Immediately completes with a scaffold result artifact
  3. No worker.spawned event — the agent appears and completes instantly

Display 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.

Child Args and Environment

Args built by 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>
Environment variables
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()
GetPiSpawnCommand

Resolves the pi binary path and builds the final command/args. On Windows, uses pi.cmd or pi.exe.

Common Spawn Failures

SymptomRoot causeFix
spawn_error: spawn returned no pidchild.pid is undefined — spawn call failed silentlyCheck binary path via getPiSpawnCommand()
spawn_error: not a valid Win32 applicationWrong binary (32-bit vs 64-bit)Reinstall pi binary
spawn_error: Access is deniedBinary not executable, or antivirus blockingCheck file permissions, run as admin
spawn_error: ENOENT: no such file or directorypi not in PATHAdd pi to PATH, or use full path
Worker crashes with exitCode=1, no outputAPI key missing or wrongCheck PI_API_KEY / ANTHROPIC_API_KEY
Worker crashes with exitCode=1, "Model not available"Wrong model nameCheck model name in config
Worker spawns, logs in, then crashesModel rate limit / quota exceededCheck provider limits
response_timeout: No output for 300000msChild process hung (network issue, model timeout)Increase responseTimeoutMs, check network
Worker completes but output not capturedstdout/stderr stream issueCheck ChildPiLineObserver parsing

Exit Code Mapping

Exit codeMeaning
0Success — worker produced output and completed
1Error — worker encountered a non-fatal error (API error, validation failure)
nullKilled — worker was SIGTERM'd or SIGKILL'd (timeout, cancel, drain)
130SIGINT — 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.

PID Tracking

  • PID recorded in manifest.async.pid at spawn (via checkpointTask)
  • PID checked by hasStaleAsyncProcess() (process-status.ts) to detect dead processes
  • PID used by killProcessPid() (child-pi.ts) for termination
  • PID in childHardKillTimers Map for timer cleanup on exit

Enforcement — Child Pi Spawning Gate

Before debugging worker spawn issues, verify:

  • [ ] Spawn error type identified (binary not found, permission denied, API key missing)
  • [ ] PID tracking status confirmed (recorded in manifest, process alive/dead)
  • [ ] Lifecycle event sequence matches expected pattern (spawned → output → exit)
  • [ ] Timeout source determined (5-min response timeout, hard kill, final drain)
  • [ ] Scaffold mode correctly identified (no worker.spawned event expected)

If ANY answer is NO → Stop. Re-examine events.jsonl and manifest before proceeding.

Anti-patterns

  • Blocking on spawn: spawn() is async — never await it synchronously. Use the Promise-based API.
  • Not handling exit: Always handle child.on("exit") and child.on("close"). Without handlers, zombie processes accumulate.
  • Ignoring lifecycle events: Without onLifecycleEvent handling, worker crashes leave no traceable evidence.
  • Not cleaning up timers: Hard-kill timers, response-timeout timers, and final-drain timers must be cleared on all exit paths.
  • Passing secrets in args: Child args are visible in process list. Use env vars (with redaction) instead.
  • Not handling `spawn_error`: Errors on spawn (binary not found, permission denied) must be caught and logged.

Source patterns

  • src/runtime/child-pi.ts — runChildPi, ChildPiLifecycleEvent, activeChildProcesses, killProcessPid
  • src/runtime/task-runner.ts — executeTask loop, onLifecycleEvent callback, runtimeKind
  • src/runtime/pi-args.ts — buildPiWorkerArgs, applyThinkingSuffix
  • src/runtime/runtime-resolver.ts — resolveCrewRuntime, isLiveSessionRuntimeAvailable, scaffold detection
  • src/runtime/model-resolver.ts — model fallback chain
  • src/utils/env-filter.ts — sanitizeEnvSecrets
  • src/config/defaults.ts — responseTimeoutMs, finalDrainMs, hardKillMs

Verification

bash
cd 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

Security

  • Security scanPassed, 90 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
58
Repository forks
22
7-day trend score
0

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/baphuongna/pi-crew/child-pi-spawning/badge)](https://verified-skill.com/skills/baphuongna/pi-crew/child-pi-spawning)

Something wrong with this skill or its page? Tell us.