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

  1. 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
  2. Restart live-server.mjs after exporting the token so the spawned runner inherits the environment
  3. Keep an Impeccable live session polling in chat so the 'chat' provider routes Apply through the chat agent
  4. 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

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-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)