{"record":{"id":"b792eec46181ac44","repo":"pbakaus/impeccable","slug":"no-live-copy-edit-ai-runner-is-available","errorCode":null,"errorMessage":"No live copy-edit AI runner is available.","messagePattern":"No live copy-edit AI runner is available\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/skills/impeccable/scripts/live-copy-edit-agent.mjs","lineNumber":114,"sourceCode":"\nexport async function runCopyEditBatchAgent(batch, opts = {}) {\n  const cwd = opts.cwd || process.cwd();\n  const env = opts.env || process.env;\n  const provider = opts.provider || chooseCopyEditAgent({ env, chatAvailable: opts.chatAvailable });\n  if (provider === 'mock') {\n    const delayMs = Number(env.IMPECCABLE_LIVE_COPY_AGENT_MOCK_DELAY_MS || 0);\n    if (delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));\n    return mockBatchResult(batch, env, cwd);\n  }\n  if (provider === 'chat') {\n    if (typeof opts.applyBatchToSource !== 'function') {\n      throw new Error('chat provider requires applyBatchToSource callback');\n    }\n    const raw = await opts.applyBatchToSource(batch, { repair: batch?.repair || null });\n    return normalizeBatchResult(raw || {});\n  }\n  if (!provider) {\n    throw new Error(describeNoProviderError({ env }));\n  }\n\n  const prompt = buildCopyEditBatchPrompt(batch, { cwd });\n  const outDir = opts.outDir || fs.mkdtempSync(path.join(os.tmpdir(), 'impeccable-copy-batch-'));\n  fs.mkdirSync(outDir, { recursive: true });\n  const resultPath = path.join(outDir, 'result.json');\n  const logPath = path.join(outDir, 'agent.log');\n\n  if (provider === 'codex') {\n    await runCodex(prompt, { cwd, env, resultPath, logPath, timeoutMs: opts.timeoutMs });\n  } else if (provider === 'claude') {\n    await runClaude(prompt, { cwd, env, resultPath, logPath, timeoutMs: opts.timeoutMs });\n  } else {\n    throw new Error(`Unsupported live copy-edit AI runner: ${provider}`);\n  }\n\n  const output = fs.existsSync(resultPath) ? fs.readFileSync(resultPath, 'utf-8') : '';\n  const parsed = parseCopyEditBatchResult(output);","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/pbakaus/impeccable/blob/d14711ae3d1a1dd62dee61a358d27f107c51ccd0/plugin/skills/impeccable/scripts/live-copy-edit-agent.mjs#L96-L132","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["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.","Start an Impeccable live chat session so chatAvailable() returns true and route Apply through the chat agent.","Set IMPECCABLE_LIVE_COPY_AGENT=mock for local/test runs that don't need a real model.","Read the full describeNoProviderError() output — it itemises which provider is missing/unauthed, so fix the first applicable bullet."],"exampleFix":"// before (no runner available)\nawait runCopyEditBatchAgent(batch, { cwd });\n\n// after\n// shell: export CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-...\n//   or:  codex login\n//   or:  IMPECCABLE_LIVE_COPY_AGENT=mock for tests\n\n// programmatic guard:\nimport { chooseCopyEditAgent } from './live-copy-edit-agent.mjs';\nif (!chooseCopyEditAgent({ env: process.env })) {\n  throw new Error('Enable a copy-edit runner before Apply.');\n}\nawait runCopyEditBatchAgent(batch, { cwd });","handlingStrategy":"validation","validationCode":"import { chooseCopyEditAgent } from './live-copy-edit-agent.mjs';\nconst provider = chooseCopyEditAgent({ env: process.env });\nif (!provider) {\n  // describeNoProviderError() itemises what's missing;\n  // either install/auth a runner, start a chat session, or set mock.\n  throw new Error('No copy-edit runner. Set IMPECCABLE_LIVE_COPY_AGENT=mock for tests.');\n}\nawait runCopyEditBatchAgent(batch, { cwd });","typeGuard":null,"tryCatchPattern":"try {\n  await runCopyEditBatchAgent(batch, { cwd });\n} catch (err) {\n  if (/No live copy-edit AI runner/.test(err.message)) {\n    // err.message is the full describeNoProviderError() breakdown — surface it\n    console.error(err.message);\n    process.exit(1);\n  }\n  throw err;\n}","preventionTips":["Start live-server.mjs from a shell that has CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY exported — headless subprocesses can't read the macOS Keychain.","For CI/tests, set IMPECCABLE_LIVE_COPY_AGENT=mock so the provider is always resolvable.","Run `codex login` / `claude setup-token` once per environment; re-run after token rotation."],"tags":["live-copy-edit","agent-runner","configuration","env"],"backgroundTag":null,"analyzedSha":"d14711ae3d1a1dd62dee61a358d27f107c51ccd0","analyzedAt":"2026-08-13T00:52:25.771Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}