---
name: claude-code
description: Delegate autonomous coding, review, research, or verification work to headless Claude Code through the bundled Pi extension tools.
version: 3.2.0-pi-extension
---

# Claude Code via Pi Extension

Use this skill when a task should be delegated to Claude Code from Pi.

## Required interface

Do **not** run `claude -p` or a standalone `claude_cli_runner.sh` through `bash`. The extension owns the runner and lifecycle. Use these Pi tools:

- `spawn_claude_code` — start a headless Claude worker.
- `get_claude_code_run` — inspect liveness (elapsed, timeout budget remaining, ms since last output) and the result artifact.
- `list_claude_code_runs` — enumerate all workers and spot a stuck one (`activeOnly: true` for running only).
- `stop_claude_code_run` — terminate an owned worker (SIGTERM→SIGKILL).

## This extension is not pi-subagents — never cross the tools

`spawn_claude_code` run IDs belong **only** to this extension. They are not subagent run IDs.

- **Never** pass a `spawn_claude_code` run ID to `subagent_wait` (or any other `pi-subagents` tool). `pi-subagents` is a completely separate extension; it has no visibility into Claude Code runs and cannot wait on or observe them. Doing so is an invalid cross-plugin tool call and will simply fail or hang on the wrong thing.
- Symmetrically, never pass a `pi-subagents` run ID to `get_claude_code_run` / `list_claude_code_runs` / `stop_claude_code_run`.
- If a run was started with `spawn_claude_code`, the only valid way to check on or stop it is `get_claude_code_run`, `list_claude_code_runs`, or `stop_claude_code_run` from this same extension.

## Completion is guaranteed — read `terminationReason`

Every `spawn_claude_code` run injects exactly one `claude-code-complete` hook within a bounded time; a worker can never hang silently. The hook's `details.terminationReason` is authoritative:

- `completed` — proceed to verification (below). Success is still a claim, not proof.
- `claude_error` / `timeout` / `stall` / `spawn_error` / `stopped` — the delegated task is **unfinished**. Do NOT report it done. Inspect the artifact/stderr for evidence, then retry with an adjusted prompt or escalate to the user. Do not launch blind retries.

**Do not poll for completion.** `get_claude_code_run` and `list_claude_code_runs` are one-shot diagnostic/status inspections, not a wait or polling mechanism — calling `get_claude_code_run` in a loop (e.g. every few seconds) while a run is active is busy-polling and will not make the run finish any faster. After spawning:

1. Continue other useful, independent work in the same turn if there is any, or yield/end the turn.
2. Wait for the `claude-code-complete` steering hook — it fires automatically and wakes Pi back up; no waiting loop is required.
3. Mid-flight `get_claude_code_run` / `list_claude_code_runs` calls are appropriate only when one of these applies:
   - a `claude-code-inactivity` alert has fired for that run,
   - the user explicitly asks for the run's status, or
   - there is concrete evidence (not a fixed interval "just checking") that the worker may be stuck.
4. Once the `claude-code-complete` hook fires, call `get_claude_code_run` **once** to inspect the finalized artifact/status before verification — you do not need to call it again after that.

## Inactivity alerts (`claude-code-inactivity`)

While a worker runs, a **non-blocking** `claude-code-inactivity` hook fires if it produces no output for `inactivityAlertMs` (default 120s), and repeats every interval that silence continues (`details.inactivityAlertCount` escalates). This is an alert, **not** a failure and **not** a stop:

- The worker is still running. A long test run, slow build, or big edit can legitimately be quiet.
- On the first alert, call `get_claude_code_run(<runId>)` and look at `msSinceLastOutput` and `status`. If it is doing expected long work, keep waiting.
- Call `stop_claude_code_run(<runId>)` only if you have concrete reason to believe it is genuinely stuck (e.g. repeated alerts with no plausible long-running step, or it is waiting on input it will never get).
- Do not re-spawn a duplicate worker in response to an inactivity alert — that creates two workers doing the same task.

Leave `inactivityAlertMs` at the default unless a task is expected to be quiet for long stretches (raise it) or you want faster nudges (lower it). Set `stallKillMs` only when you explicitly want silence to auto-kill the worker rather than just alert.

## Critical runner configuration

**NEVER set `bare: true` on `spawn_claude_code`.** Always omit the `bare` parameter so Claude Code loads its normal authentication, hooks, plugins, MCP configuration, and `CLAUDE.md` instructions. There are no exceptions, including read-only reviews, isolated diagnostics, or attempts to reduce context.

If a proposed tool call contains `bare: true`, stop and remove that parameter before spawning the worker. The extension also rejects `bare: true` at runtime with an explicit error before the worker starts — treat that error as confirmation to remove the parameter, not as something to retry as-is.

## Non-disableable limits

Two limits are hard-coded in the extension and cannot be raised or disabled from a tool call:

- **6-hour absolute timeout ceiling.** `timeoutMs` is clamped to `[30s, 6h]` no matter what is requested, including `0`. A task that genuinely needs longer than 6 hours must be split into multiple delegated runs.
- **Max 3 active runs.** `spawn_claude_code` refuses to start a 4th worker while 3 spawned by this session are still unfinalized. If you need to run more, `stop_claude_code_run` or wait for one to finish first — do not treat the rejection as a transient error to blindly retry.

## Workflow

1. Give `spawn_claude_code` a complete prompt containing:
   - the exact task;
   - explicit working directory;
   - success criteria;
   - validation commands;
   - files or paths that must not be changed.
2. Set `allowedTools` to the narrowest useful Claude permission set. Use `tools` to restrict the actual tool set exposed to Claude Code. Use `allowedTools` only to pre-approve permissions for tools that remain available. For least-privilege delegations, set both: narrow `tools`, then use `allowedTools` for the subset you are willing to auto-approve.
3. Assume Claude CLI authentication is available. Do not run preflight auth checks, infer auth state from prior runs, or warn the user before attempting `spawn_claude_code`.
4. Omit the `bare` parameter. Never pass `bare: true`.
5. Leave `maxTurns` unset for the runner's unlimited-turn default. Set it only as a deliberate runaway guard for a narrowly-bounded task. Tune `timeoutMs` only when a task legitimately needs longer than the 30 min wall-clock default; tune `inactivityAlertMs` (default 120s) per the inactivity-alerts section below.
6. Wait for the `claude-code-complete` message injected by the extension. Check `details.terminationReason` first — only `completed` may proceed.
7. Call `get_claude_code_run` with the returned run ID.
8. Require runner exit code `0`, Claude result subtype `success`, `terminal_reason: completed`, and no permission/auth/tool failure.
9. Independently verify important files, diffs, tests, commands, PR state, or external results from Pi. Claude's final answer is a claim, not proof.

## Permission examples

Read-only review:

```text
Read,Bash(git status *),Bash(git diff *),Bash(git log *)
```

Focused implementation:

```text
Read,Edit,Write,Bash(git diff *),Bash(npm test *),Bash(pytest *)
```

A broad `Bash` permission is not read-only. Do not grant it unless the task genuinely requires unrestricted shell access.

## Failure handling

- Always attempt `spawn_claude_code` before discussing authentication. Assume authentication works until that specific run proves otherwise.
- Only tell the user Claude is not authenticated when `get_claude_code_run` or its result artifact shows a concrete Anthropic API HTTP 4xx/5xx response that explicitly identifies missing or invalid authentication. Include the run ID, HTTP status, and bounded error text as proof.
- A runner exit code, missing status envelope, `api_error` terminal reason, empty `api_error_status`, prior failed run, or generic “Not logged in” text without an Anthropic HTTP 4xx/5xx authentication response is not sufficient proof. Report only the observed runner failure in those cases; do not diagnose missing authentication.
- Do not run OAuth/login/setup flows.
- If the worker loops, produces no artifact, or repeatedly fails validation, stop it and report evidence rather than launching speculative retries. A `timeout` or `stall` terminationReason already means the extension killed it for you — surface that, don't re-spawn blindly.
- If Pi shuts down or reloads, the extension SIGTERM→SIGKILLs the process group of every worker owned by that session; the runner's own `--timeout` backstop also self-terminates any child orphaned by an abrupt exit.

## Artifacts

Results are stored under:

```text
/tmp/pi-claude-code/<run-id>/result.json
```

Large outputs remain in the artifact; `get_claude_code_run` returns only a bounded excerpt.
