pbakaus/impeccable · error · Error
No live copy-edit AI runner is available.
Error message
No live copy-edit AI runner is available.
What it means
runCopyEditBatchAgent() (skill/scripts/live-copy-edit-agent.mjs) applies staged browser copy edits by waking a local AI runner; the provider comes from opts.provider or chooseCopyEditAgent({ env }), which reads IMPECCABLE_LIVE_COPY_AGENT (default 'auto'). When resolution yields null — no authed codex or claude CLI, no chat session polling, a disabling value ('0'/'false'/'off'/'none'), or an explicit 'codex'/'claude' whose CLI is not on PATH — it throws the multi-line diagnostic built by describeNoProviderError(), listing per-provider install/auth state and ending with 'Fix one of the above, or set IMPECCABLE_LIVE_COPY_AGENT=mock for tests.'
Solutions
- Authenticate a runner: `codex login`, or `claude setup-token` once then export CLAUDE_CODE_OAUTH_TOKEN (sk-ant-oat01-...) — alternatively export ANTHROPIC_API_KEY if you have console credits
- Restart live-server.mjs after exporting the token so the spawned runner inherits the environment
- Keep an Impeccable live session polling in chat so the 'chat' provider routes Apply through the chat agent
- For tests, set IMPECCABLE_LIVE_COPY_AGENT=mock (optionally IMPECCABLE_LIVE_COPY_AGENT_MOCK_DELAY_MS/..._MOCK_RESULT to script outcomes)
Example fix
# before: live server started without auth env live-server.mjs # Apply later fails with 'No live copy-edit AI runner is available.' # after: export headless token first, then boot claude setup-token # prints an sk-ant-oat01-... token once export CLAUDE_CODE_OAUTH_TOKEN=<the printed token> node skill/scripts/live-server.mjs
Defensive patterns
Strategy: validation
Validate before calling
import { chooseCopyEditAgent } from './live-copy-edit-agent.mjs';
const provider = chooseCopyEditAgent({ env: process.env });
if (!provider) {
// Fail fast BEFORE staging edits: install/auth codex or claude,
// start a polling chat session, or set IMPECCABLE_LIVE_COPY_AGENT=mock.
throw new Error('Copy-edit Apply unavailable: configure a runner first');
} Try / catch
try {
const result = await runCopyEditBatchAgent(batch, { cwd });
} catch (err) {
if (err?.message?.startsWith('No live copy-edit AI runner is available.')) {
// Provider-availability problem, not a batch problem:
// surface the per-provider hints from describeNoProviderError()
// and do NOT retry the same batch unchanged.
showRunnerSetupHelp(err.message);
return;
}
throw err;
} Prevention
- Run `claude setup-token` once and export CLAUDE_CODE_OAUTH_TOKEN (or codex login) before booting live-server.mjs — env is captured at server start
- On macOS never rely on `claude /login` Keychain creds reaching the non-TTY subprocess
- Keep an Impeccable live chat session polling so the 'chat' provider is a fallback
- Use IMPECCABLE_LIVE_COPY_AGENT=mock in CI and tests so availability never blocks them
When it happens
Trigger: Clicking Apply on staged copy edits when neither codex nor claude is installed-and-authenticated and no Impeccable live chat session is polling; IMPECCABLE_LIVE_COPY_AGENT=off/none; IMPECCABLE_LIVE_COPY_AGENT=codex with codex missing from PATH; on macOS, the non-TTY subprocess cannot read `claude /login` Keychain credentials; CLAUDE_CODE_OAUTH_TOKEN set but expired; live-server.mjs started before the token was exported so the child never inherits it.
Common situations: Headless/CI machines without an interactive CLI login; tokens expiring between sessions; exporting auth env in a different shell than the one running live-server.mjs (env is captured at server boot); fresh machines where no AI CLI was ever installed.
Related errors
- AI copy-edit batch did not return a valid completion…
- Unsupported live copy-edit AI runner
- Authentication failed. The server token may have changed.
- {}
- preflight returned no scaffold metadata
AI-assisted analysis of pbakaus/impeccable@f88b2837a7 (2026-08-18).
Data as JSON: /api/errors/ea00b26b93f2010c.
Report an issue: GitHub.
Appendix: source
Thrown at skill/scripts/live-copy-edit-agent.mjs:114
export async function runCopyEditBatchAgent(batch, opts = {}) {
const cwd = opts.cwd || process.cwd();
const env = opts.env || process.env;
const provider = opts.provider || chooseCopyEditAgent({ env, chatAvailable: opts.chatAvailable });
if (provider === 'mock') {
const delayMs = Number(env.IMPECCABLE_LIVE_COPY_AGENT_MOCK_DELAY_MS || 0);
if (delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
return mockBatchResult(batch, env, cwd);
}
if (provider === 'chat') {
if (typeof opts.applyBatchToSource !== 'function') {
throw new Error('chat provider requires applyBatchToSource callback');
}
const raw = await opts.applyBatchToSource(batch, { repair: batch?.repair || null });
return normalizeBatchResult(raw || {});
}
if (!provider) {
throw new Error(describeNoProviderError({ env }));
}
const prompt = buildCopyEditBatchPrompt(batch, { cwd });
const outDir = opts.outDir || fs.mkdtempSync(path.join(os.tmpdir(), 'impeccable-copy-batch-'));
fs.mkdirSync(outDir, { recursive: true });
const resultPath = path.join(outDir, 'result.json');
const logPath = path.join(outDir, 'agent.log');
if (provider === 'codex') {
await runCodex(prompt, { cwd, env, resultPath, logPath, timeoutMs: opts.timeoutMs });
} else if (provider === 'claude') {
await runClaude(prompt, { cwd, env, resultPath, logPath, timeoutMs: opts.timeoutMs });
} else {
throw new Error(`Unsupported live copy-edit AI runner: ${provider}`);
}
const output = fs.existsSync(resultPath) ? fs.readFileSync(resultPath, 'utf-8') : '';
const parsed = parseCopyEditBatchResult(output);View on GitHub (pinned to f88b2837a7)