diff --git a/skill/scripts/live-copy-edit-agent.mjs b/skill/scripts/live-copy-edit-agent.mjs index 313ed7f10..f70f26ff4 100644 --- a/skill/scripts/live-copy-edit-agent.mjs +++ b/skill/scripts/live-copy-edit-agent.mjs @@ -470,12 +470,11 @@ function runClaude(prompt, { cwd, env, resultPath, logPath, timeoutMs = DEFAULT_ if (env.IMPECCABLE_LIVE_COPY_AGENT_MODEL) { args.push('--model', env.IMPECCABLE_LIVE_COPY_AGENT_MODEL); } - args.push(prompt); // Forward env as-is so CLAUDE_CODE_OAUTH_TOKEN and ANTHROPIC_API_KEY flow // through. On macOS, `claude /login` stores creds in the Keychain, which a // non-TTY subprocess cannot read; setting CLAUDE_CODE_OAUTH_TOKEN (via // `claude setup-token`) is the supported headless auth path. - return runAgentProcess('claude', args, '', { cwd, env, logPath, timeoutMs, mirrorOutputPath: resultPath }); + return runAgentProcess('claude', args, prompt, { cwd, env, logPath, timeoutMs, mirrorOutputPath: resultPath }); } function runAgentProcess(command, args, stdin, { cwd, env, logPath, timeoutMs, mirrorOutputPath }) { diff --git a/tests/live-copy-edit-agent.test.mjs b/tests/live-copy-edit-agent.test.mjs index 6993e5028..98d9c0c1b 100644 --- a/tests/live-copy-edit-agent.test.mjs +++ b/tests/live-copy-edit-agent.test.mjs @@ -305,6 +305,48 @@ describe('live-copy-edit-agent', () => { ); }); + it('passes large Claude prompts on stdin instead of argv', async () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'copy-agent-claude-stdin-')); + try { + const fakeClaude = path.join(tmp, 'claude'); + fs.writeFileSync(fakeClaude, [ + '#!/usr/bin/env node', + "let input = '';", + "process.stdin.setEncoding('utf8');", + "process.stdin.on('data', (chunk) => { input += chunk; });", + "process.stdin.on('end', () => {", + " const promptLeakedToArgv = process.argv.slice(2).some((arg) => arg.includes('large-prompt-sentinel'));", + " if (!input.includes('large-prompt-sentinel') || promptLeakedToArgv) process.exit(2);", + " process.stdout.write(JSON.stringify({ status: 'done', appliedEntryIds: ['large'], files: [], notes: [] }));", + '});', + '', + ].join('\n')); + fs.chmodSync(fakeClaude, 0o755); + + const result = await runCopyEditBatchAgent({ + pageUrl: '/', + entries: [{ + id: 'large', + pageUrl: '/', + ops: [{ originalText: 'Old', newText: `large-prompt-sentinel${'x'.repeat(1_100_000)}` }], + }], + }, { + provider: 'claude', + outDir: path.join(tmp, 'out'), + timeoutMs: 5_000, + env: { + ...process.env, + PATH: `${tmp}${path.delimiter}${process.env.PATH || ''}`, + }, + }); + + assert.equal(result.status, 'done'); + assert.deepEqual(result.appliedEntryIds, ['large']); + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } + }); + it('describeNoProviderError mentions starting impeccable live when chat is the missing piece', () => { const noChatPolling = describeNoProviderError({ exists: () => false,