pbakaus/impeccable · error

No live copy-edit AI runner is available.

Error message

No live copy-edit AI runner is available.

What it means

Thrown by runCopyEditBatchAgent() when chooseCopyEditAgent() returns null — no mock/chat/codex/claude provider resolved. The provider is chosen from env.IMPECCABLE_LIVE_COPY_AGENT (default 'auto') combined with auth/availability checks (commandAuthed for codex/claude, chatAvailable() for chat). The message body comes from describeNoProviderError(), which lists per-provider status. This fires before any prompt is built.

Source

Thrown at plugin/skills/impeccable/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 d14711ae3d)

Solutions

  1. Install and authenticate a runner: `codex login` or `claude setup-token` then export CLAUDE_CODE_OAUTH_TOKEN (or ANTHROPIC_API_KEY) in the shell that starts live-server.mjs.
  2. Start an Impeccable live chat session so chatAvailable() returns true and route Apply through the chat agent.
  3. Set IMPECCABLE_LIVE_COPY_AGENT=mock for local/test runs that don't need a real model.
  4. Read the full describeNoProviderError() output — it itemises which provider is missing/unauthed, so fix the first applicable bullet.

Example fix

// before (no runner available)
await runCopyEditBatchAgent(batch, { cwd });

// after
// shell: export CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-...
//   or:  codex login
//   or:  IMPECCABLE_LIVE_COPY_AGENT=mock for tests

// programmatic guard:
import { chooseCopyEditAgent } from './live-copy-edit-agent.mjs';
if (!chooseCopyEditAgent({ env: process.env })) {
  throw new Error('Enable a copy-edit runner before Apply.');
}
await runCopyEditBatchAgent(batch, { cwd });
Defensive patterns

Strategy: validation

Validate before calling

import { chooseCopyEditAgent } from './live-copy-edit-agent.mjs';
const provider = chooseCopyEditAgent({ env: process.env });
if (!provider) {
  // describeNoProviderError() itemises what's missing;
  // either install/auth a runner, start a chat session, or set mock.
  throw new Error('No copy-edit runner. Set IMPECCABLE_LIVE_COPY_AGENT=mock for tests.');
}
await runCopyEditBatchAgent(batch, { cwd });

Try / catch

try {
  await runCopyEditBatchAgent(batch, { cwd });
} catch (err) {
  if (/No live copy-edit AI runner/.test(err.message)) {
    // err.message is the full describeNoProviderError() breakdown — surface it
    console.error(err.message);
    process.exit(1);
  }
  throw err;
}

Prevention

When it happens

Trigger: IMPECCABLE_LIVE_COPY_AGENT unset/auto AND neither codex nor claude passes commandAuthed AND no live chat session is polling; explicitly set to 'off'/'none'/'false'/'0'; set to 'chat' but chatAvailable() is false; set to 'codex'/'claude' but the CLI binary is not on PATH. Also when an unrecognised mode string is set (auto-falls through to null at line 559).

Common situations: Fresh machine without codex/claude CLI installed; CLAUDE_CODE_OAUTH_TOKEN expired so claude is no longer authed; claude installed but macOS Keychain unreachable from a no-TTY subprocess (the comment at line 591 explains this); dev server started in a shell that lacks the API key env vars; user disabled the agent with IMPECCABLE_LIVE_COPY_AGENT=none.

Related errors


AI-assisted analysis of pbakaus/impeccable@d14711ae3d (2026-08-13). Data as JSON: /api/errors/b792eec46181ac44. Report an issue: GitHub.