{"record":{"id":"ea00b26b93f2010c","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":"Error","httpStatus":null,"severity":"error","filePath":"skill/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/f88b2837a7d7c3182e46307bbbb091a1ed547571/skill/scripts/live-copy-edit-agent.mjs#L96-L132","documentation":"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.'","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":"# before: live server started without auth env\nlive-server.mjs  # Apply later fails with 'No live copy-edit AI runner is available.'\n\n# after: export headless token first, then boot\nclaude setup-token   # prints an sk-ant-oat01-... token once\nexport CLAUDE_CODE_OAUTH_TOKEN=<the printed token>\nnode skill/scripts/live-server.mjs","handlingStrategy":"validation","validationCode":"import { chooseCopyEditAgent } from './live-copy-edit-agent.mjs';\n\nconst provider = chooseCopyEditAgent({ env: process.env });\nif (!provider) {\n  // Fail fast BEFORE staging edits: install/auth codex or claude,\n  // start a polling chat session, or set IMPECCABLE_LIVE_COPY_AGENT=mock.\n  throw new Error('Copy-edit Apply unavailable: configure a runner first');\n}","typeGuard":null,"tryCatchPattern":"try {\n  const result = await runCopyEditBatchAgent(batch, { cwd });\n} catch (err) {\n  if (err?.message?.startsWith('No live copy-edit AI runner is available.')) {\n    // Provider-availability problem, not a batch problem:\n    // surface the per-provider hints from describeNoProviderError()\n    // and do NOT retry the same batch unchanged.\n    showRunnerSetupHelp(err.message);\n    return;\n  }\n  throw err;\n}","preventionTips":["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"],"tags":["env","ai-runner","authentication","copy-edit","live"],"backgroundTag":"ai-cli-not-authenticated","analyzedSha":"f88b2837a7d7c3182e46307bbbb091a1ed547571","analyzedAt":"2026-08-18T04:58:36.608Z","contentChangedAt":"2026-08-18T04:58:36.608Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}