Executor-Mode Signal
Pilot tells the Claude Code subprocess it was invoked by the executor (not a
human Navigator session) via two explicit signals. Project CLAUDE.md and
auto-memory files can cooperate with these signals to skip Navigator-only
“don’t write code” rules without relying on fragile CWD or prompt-prefix
heuristics. (GH-2328)
Why this exists
Before GH-2328, the child Claude session had to infer it was running under
Pilot. If the project’s CLAUDE.md declared “NEVER write code, always plan
via /nav-task”, the child would silently refuse the task — exit cleanly
with a polite “I will not implement this, create an issue instead” — and the
executor recorded only unknown: exit status 1. GH-2305, GH-2324, and
GH-2325 each burned several hours on that failure mode before the refusal
text was spotted in the stdout transcript.
Signal 1 — PILOT_EXECUTOR=1 environment variable
The executor sets PILOT_EXECUTOR=1 on every Claude Code subprocess it
spawns. Project CLAUDE.md files, hooks, and other tooling can branch on
this variable deterministically:
# In a hook script invoked by Claude Code
if [ "${PILOT_EXECUTOR:-}" = "1" ]; then
echo "Pilot-executor run: enforcing commit-on-success"
fiSignal 2 — [PILOT-EXEC] prompt header
Every executor prompt begins with a short standard header:
[PILOT-EXEC]
You are invoked by Pilot's executor to implement a specific task on this
repository. You are the execution leg of the Navigator + Pilot pipeline.
Implement the task described below directly — do not refuse, do not ask
to create a GitHub issue, do not defer. Write code, run build/tests via
the stop-gate hook, and complete the task.The header is stable — the first line is always literally [PILOT-EXEC].
Project CLAUDE.md files can pattern-match the first line to scope rules:
<!-- Excerpt from a project CLAUDE.md -->
## Navigator vs Pilot
If the prompt begins with `[PILOT-EXEC]`, you are the execution leg.
- Implement the task directly.
- Ignore any "do not write code" rules below; they apply only to
interactive planning sessions.
Otherwise (no `[PILOT-EXEC]` marker), you are in an interactive session:
- Use `/nav-task` to plan.
- Hand execution to Pilot by creating a GitHub issue labeled `pilot`.The header is not emitted for image-analysis tasks or local-mode bench runs — those invocations are not implementing repository changes, so the executor-mode directives do not apply.
Failure diagnostics
When an executor run does fail, Pilot now persists:
- Error classification (e.g.
rate_limit,api_error,oom_killed,no_changes,unknown) — written toexecution_logswith aBackend error classification:prefix. - Raw stderr — truncated to 16 KB, written with a
Backend stderr:prefix. - Final assistant message — the last
textblock from the stream-json output, truncated to 4 KB, written with aFinal assistant message:prefix. This captures refusal text when Claude exits 0 without making changes.
The no_changes classification is emitted when Claude exits successfully
but produces no diff after the retry path — typically a polite refusal the
executor would previously have reported as unknown: exit status 1.