From 8c2a75a8d8bf377f54e774d93e6c202158d9b8bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:00:54 +0000 Subject: [PATCH 1/6] Initial plan From b8c973af0b8861b16f0ee012cbf9cb78187814d9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:07:15 +0000 Subject: [PATCH 2/6] fix: rewrite inline prompt cat expansion to avoid E2BIG argv failures Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/4052214b-06e7-4406-aeb0-e3958af8a77e --- src/docker-manager.test.ts | 26 ++++++++++++++++++++++++++ src/docker-manager.ts | 32 ++++++++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/docker-manager.test.ts b/src/docker-manager.test.ts index 3b00279f..966025f6 100644 --- a/src/docker-manager.test.ts +++ b/src/docker-manager.test.ts @@ -1295,6 +1295,32 @@ describe('docker-manager', () => { expect(agent.command).toEqual(['/bin/bash', '-c', 'echo $$HOME && echo $${USER}']); }); + it('should rewrite inline --prompt $(cat ...) to stdin pipe to avoid ARG_MAX expansion', () => { + const configWithInlinePrompt = { + ...mockConfig, + agentCommand: 'node /tmp/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" --allow-all-tools', + }; + const result = generateDockerCompose(configWithInlinePrompt, mockNetworkConfig); + const agent = result.services.agent; + + expect(agent.command).toEqual([ + '/bin/bash', + '-c', + 'cat \'/tmp/gh-aw/aw-prompts/prompt.txt\' | node /tmp/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --prompt - --allow-all-tools', + ]); + }); + + it('should not rewrite regular literal --prompt values', () => { + const configWithLiteralPrompt = { + ...mockConfig, + agentCommand: 'copilot --prompt "hello world"', + }; + const result = generateDockerCompose(configWithLiteralPrompt, mockNetworkConfig); + const agent = result.services.agent; + + expect(agent.command).toEqual(['/bin/bash', '-c', 'copilot --prompt "hello world"']); + }); + it('should pass through GITHUB_TOKEN when present in environment', () => { const originalEnv = process.env.GITHUB_TOKEN; process.env.GITHUB_TOKEN = 'ghp_testtoken123'; diff --git a/src/docker-manager.ts b/src/docker-manager.ts index ff00583b..21453ae2 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -36,6 +36,7 @@ const MAX_ENV_VALUE_SIZE = 64 * 1024; // 64 KB * Linux ARG_MAX is ~2 MB for argv + envp combined; warn well before that. */ const ENV_SIZE_WARNING_THRESHOLD = 1_500_000; // ~1.5 MB +const INLINE_PROMPT_CAT_REGEX = /--prompt\s+"?\$\(cat\s+([^)]+?)\s*\)"?/; /** * Flag set by fastKillAgentContainer() to signal runAgentCommand() that @@ -51,6 +52,24 @@ let agentExternallyKilled = false; */ let awfDockerHostOverride: string | undefined; +function shellQuote(value: string): string { + return `'${value.replace(/'/g, '\'\\\'\'')}'`; +} + +function rewriteInlinePromptCatToStdin(agentCommand: string): string { + const match = agentCommand.match(INLINE_PROMPT_CAT_REGEX); + if (!match) return agentCommand; + + const promptPath = match[1]?.trim().replace(/^['"]|['"]$/g, ''); + if (!promptPath) return agentCommand; + + const rewrittenCommand = agentCommand.replace(INLINE_PROMPT_CAT_REGEX, '--prompt -'); + if (rewrittenCommand === agentCommand) return agentCommand; + + logger.warn(`Rewriting inline prompt expansion to stdin pipe to avoid ARG_MAX/E2BIG: ${promptPath}`); + return `cat ${shellQuote(promptPath)} | ${rewrittenCommand}`; +} + /** * Sets the Docker host to use for AWF's own container operations. * @@ -602,6 +621,7 @@ export function generateDockerCompose( squidConfigContent?: string ): DockerComposeConfig { const projectRoot = path.join(__dirname, '..'); + const rewrittenAgentCommand = rewriteInlinePromptCatToStdin(config.agentCommand); // Guard: --build-local requires full repo checkout (not available in standalone bundle) if (config.buildLocal) { @@ -1024,10 +1044,14 @@ export function generateDockerCompose( if (config.envAll) { const totalEnvBytes = Object.entries(environment) .reduce((sum, [k, v]) => sum + k.length + (v?.length ?? 0) + 2, 0); // +2 for '=' and null - if (totalEnvBytes > ENV_SIZE_WARNING_THRESHOLD) { + const argvBytes = ['/bin/bash', '-c', rewrittenAgentCommand] + .reduce((sum, arg) => sum + arg.length + 1, 1); // +1 null terminator + const totalArgvEnvBytes = totalEnvBytes + argvBytes; + if (totalArgvEnvBytes > ENV_SIZE_WARNING_THRESHOLD) { logger.warn( - `⚠️ Total container environment size is ${(totalEnvBytes / 1024).toFixed(0)} KB — ` + - 'may cause E2BIG (Argument list too long) errors when combined with large command arguments' + `⚠️ Estimated argv+env size is ${(totalArgvEnvBytes / 1024).toFixed(0)} KB ` + + `(env ${(totalEnvBytes / 1024).toFixed(0)} KB, argv ${(argvBytes / 1024).toFixed(0)} KB) — ` + + 'may cause E2BIG (Argument list too long) errors' ); logger.warn(' Consider using --exclude-env to remove unnecessary variables'); } @@ -1565,7 +1589,7 @@ export function generateDockerCompose( start_period: '1s', }, // Escape $ with $$ for Docker Compose variable interpolation - command: ['/bin/bash', '-c', config.agentCommand.replace(/\$/g, '$$$$')], + command: ['/bin/bash', '-c', rewrittenAgentCommand.replace(/\$/g, '$$$$')], }; // Set working directory if specified (overrides Dockerfile WORKDIR) From 7d6d23c42a4ffd3ee38d96d6bf3945ef92961f06 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:08:44 +0000 Subject: [PATCH 3/6] docs: clarify prompt rewrite ARG_MAX mitigation internals Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/4052214b-06e7-4406-aeb0-e3958af8a77e --- src/docker-manager.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/docker-manager.ts b/src/docker-manager.ts index 21453ae2..b207d0b5 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -36,6 +36,9 @@ const MAX_ENV_VALUE_SIZE = 64 * 1024; // 64 KB * Linux ARG_MAX is ~2 MB for argv + envp combined; warn well before that. */ const ENV_SIZE_WARNING_THRESHOLD = 1_500_000; // ~1.5 MB +// Matches inline prompt expansion patterns emitted by gh-aw style commands, e.g.: +// --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" +// --prompt $(cat /tmp/gh-aw/aw-prompts/prompt.txt) const INLINE_PROMPT_CAT_REGEX = /--prompt\s+"?\$\(cat\s+([^)]+?)\s*\)"?/; /** @@ -53,9 +56,20 @@ let agentExternallyKilled = false; let awfDockerHostOverride: string | undefined; function shellQuote(value: string): string { + // POSIX single-quote escaping: close quote, emit escaped single quote, reopen. + // Example: a'b -> 'a'\''b' return `'${value.replace(/'/g, '\'\\\'\'')}'`; } +/** + * Rewrites inline prompt expansions of the form: + * --prompt "$(cat /path/to/prompt.txt)" + * to: + * cat '/path/to/prompt.txt' | ... --prompt - + * + * This keeps large prompts out of argv at execve time, reducing ARG_MAX/E2BIG + * failures when workflows inline big prompt files. + */ function rewriteInlinePromptCatToStdin(agentCommand: string): string { const match = agentCommand.match(INLINE_PROMPT_CAT_REGEX); if (!match) return agentCommand; From 52d5be9e1edea3d1d6f5796da2fe4a0e5f2941ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:10:42 +0000 Subject: [PATCH 4/6] fix: harden inline prompt rewrite parsing for quoted paths Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/4052214b-06e7-4406-aeb0-e3958af8a77e --- src/docker-manager.test.ts | 15 +++++++++++++++ src/docker-manager.ts | 19 ++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/docker-manager.test.ts b/src/docker-manager.test.ts index 966025f6..08f776a2 100644 --- a/src/docker-manager.test.ts +++ b/src/docker-manager.test.ts @@ -1310,6 +1310,21 @@ describe('docker-manager', () => { ]); }); + it('should rewrite inline --prompt $(cat ...) when cat path is single-quoted', () => { + const configWithQuotedPath = { + ...mockConfig, + agentCommand: 'node /tmp/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --prompt "$(cat \'/tmp/gh-aw/aw-prompts/prompt with spaces.txt\')" --allow-all-tools', + }; + const result = generateDockerCompose(configWithQuotedPath, mockNetworkConfig); + const agent = result.services.agent; + + expect(agent.command).toEqual([ + '/bin/bash', + '-c', + 'cat \'/tmp/gh-aw/aw-prompts/prompt with spaces.txt\' | node /tmp/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --prompt - --allow-all-tools', + ]); + }); + it('should not rewrite regular literal --prompt values', () => { const configWithLiteralPrompt = { ...mockConfig, diff --git a/src/docker-manager.ts b/src/docker-manager.ts index b207d0b5..5c79c1e8 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -39,7 +39,8 @@ const ENV_SIZE_WARNING_THRESHOLD = 1_500_000; // ~1.5 MB // Matches inline prompt expansion patterns emitted by gh-aw style commands, e.g.: // --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" // --prompt $(cat /tmp/gh-aw/aw-prompts/prompt.txt) -const INLINE_PROMPT_CAT_REGEX = /--prompt\s+"?\$\(cat\s+([^)]+?)\s*\)"?/; +// Supports quoted and unquoted cat paths. +const INLINE_PROMPT_CAT_REGEX = /--prompt\s+"?\$\(cat\s+((?:"[^"]+"|'[^']+'|[^)])+)\s*\)"?/; /** * Flag set by fastKillAgentContainer() to signal runAgentCommand() that @@ -58,9 +59,21 @@ let awfDockerHostOverride: string | undefined; function shellQuote(value: string): string { // POSIX single-quote escaping: close quote, emit escaped single quote, reopen. // Example: a'b -> 'a'\''b' + // Replacement token '\'' means: end quote + escaped single quote + reopen quote. return `'${value.replace(/'/g, '\'\\\'\'')}'`; } +function trimMatchingOuterQuotes(value: string): string { + const trimmed = value.trim(); + if ( + (trimmed.startsWith('\'') && trimmed.endsWith('\'')) || + (trimmed.startsWith('"') && trimmed.endsWith('"')) + ) { + return trimmed.slice(1, -1); + } + return trimmed; +} + /** * Rewrites inline prompt expansions of the form: * --prompt "$(cat /path/to/prompt.txt)" @@ -74,7 +87,7 @@ function rewriteInlinePromptCatToStdin(agentCommand: string): string { const match = agentCommand.match(INLINE_PROMPT_CAT_REGEX); if (!match) return agentCommand; - const promptPath = match[1]?.trim().replace(/^['"]|['"]$/g, ''); + const promptPath = trimMatchingOuterQuotes(match[1] ?? ''); if (!promptPath) return agentCommand; const rewrittenCommand = agentCommand.replace(INLINE_PROMPT_CAT_REGEX, '--prompt -'); @@ -1059,7 +1072,7 @@ export function generateDockerCompose( const totalEnvBytes = Object.entries(environment) .reduce((sum, [k, v]) => sum + k.length + (v?.length ?? 0) + 2, 0); // +2 for '=' and null const argvBytes = ['/bin/bash', '-c', rewrittenAgentCommand] - .reduce((sum, arg) => sum + arg.length + 1, 1); // +1 null terminator + .reduce((sum, arg) => sum + arg.length + 1, 0); const totalArgvEnvBytes = totalEnvBytes + argvBytes; if (totalArgvEnvBytes > ENV_SIZE_WARNING_THRESHOLD) { logger.warn( From 6b0f9c656e3ea097a2562671c7b3de5e0e7d91af Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:12:49 +0000 Subject: [PATCH 5/6] fix: avoid regex redos in inline prompt rewrite logic Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/4052214b-06e7-4406-aeb0-e3958af8a77e --- src/docker-manager.test.ts | 15 ++++++++++++ src/docker-manager.ts | 49 ++++++++++++++++++++++++++++++-------- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/src/docker-manager.test.ts b/src/docker-manager.test.ts index 08f776a2..8fc56a7b 100644 --- a/src/docker-manager.test.ts +++ b/src/docker-manager.test.ts @@ -1325,6 +1325,21 @@ describe('docker-manager', () => { ]); }); + it('should rewrite unquoted --prompt $(cat ...) form', () => { + const configWithUnquotedInlinePrompt = { + ...mockConfig, + agentCommand: 'node /tmp/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --prompt $(cat /tmp/gh-aw/aw-prompts/prompt.txt) --allow-all-tools', + }; + const result = generateDockerCompose(configWithUnquotedInlinePrompt, mockNetworkConfig); + const agent = result.services.agent; + + expect(agent.command).toEqual([ + '/bin/bash', + '-c', + 'cat \'/tmp/gh-aw/aw-prompts/prompt.txt\' | node /tmp/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --prompt - --allow-all-tools', + ]); + }); + it('should not rewrite regular literal --prompt values', () => { const configWithLiteralPrompt = { ...mockConfig, diff --git a/src/docker-manager.ts b/src/docker-manager.ts index 5c79c1e8..aca685ce 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -36,11 +36,6 @@ const MAX_ENV_VALUE_SIZE = 64 * 1024; // 64 KB * Linux ARG_MAX is ~2 MB for argv + envp combined; warn well before that. */ const ENV_SIZE_WARNING_THRESHOLD = 1_500_000; // ~1.5 MB -// Matches inline prompt expansion patterns emitted by gh-aw style commands, e.g.: -// --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" -// --prompt $(cat /tmp/gh-aw/aw-prompts/prompt.txt) -// Supports quoted and unquoted cat paths. -const INLINE_PROMPT_CAT_REGEX = /--prompt\s+"?\$\(cat\s+((?:"[^"]+"|'[^']+'|[^)])+)\s*\)"?/; /** * Flag set by fastKillAgentContainer() to signal runAgentCommand() that @@ -74,6 +69,40 @@ function trimMatchingOuterQuotes(value: string): string { return trimmed; } +function findInlinePromptCatRange(command: string): { start: number; end: number; promptPath: string } | null { + // Variant 1: --prompt "$(cat /path/to/prompt.txt)" + const quotedPrefix = '--prompt "$(cat '; + const quotedStart = command.indexOf(quotedPrefix); + if (quotedStart !== -1) { + const pathStart = quotedStart + quotedPrefix.length; + const pathEnd = command.indexOf(')"', pathStart); + if (pathEnd !== -1) { + return { + start: quotedStart, + end: pathEnd + 2, + promptPath: trimMatchingOuterQuotes(command.slice(pathStart, pathEnd)), + }; + } + } + + // Variant 2: --prompt $(cat /path/to/prompt.txt) + const unquotedPrefix = '--prompt $(cat '; + const unquotedStart = command.indexOf(unquotedPrefix); + if (unquotedStart !== -1) { + const pathStart = unquotedStart + unquotedPrefix.length; + const pathEnd = command.indexOf(')', pathStart); + if (pathEnd !== -1) { + return { + start: unquotedStart, + end: pathEnd + 1, + promptPath: trimMatchingOuterQuotes(command.slice(pathStart, pathEnd)), + }; + } + } + + return null; +} + /** * Rewrites inline prompt expansions of the form: * --prompt "$(cat /path/to/prompt.txt)" @@ -84,13 +113,13 @@ function trimMatchingOuterQuotes(value: string): string { * failures when workflows inline big prompt files. */ function rewriteInlinePromptCatToStdin(agentCommand: string): string { - const match = agentCommand.match(INLINE_PROMPT_CAT_REGEX); - if (!match) return agentCommand; + const inlinePrompt = findInlinePromptCatRange(agentCommand); + if (!inlinePrompt) return agentCommand; - const promptPath = trimMatchingOuterQuotes(match[1] ?? ''); + const promptPath = inlinePrompt.promptPath; if (!promptPath) return agentCommand; - const rewrittenCommand = agentCommand.replace(INLINE_PROMPT_CAT_REGEX, '--prompt -'); + const rewrittenCommand = `${agentCommand.slice(0, inlinePrompt.start)}--prompt -${agentCommand.slice(inlinePrompt.end)}`; if (rewrittenCommand === agentCommand) return agentCommand; logger.warn(`Rewriting inline prompt expansion to stdin pipe to avoid ARG_MAX/E2BIG: ${promptPath}`); @@ -1072,7 +1101,7 @@ export function generateDockerCompose( const totalEnvBytes = Object.entries(environment) .reduce((sum, [k, v]) => sum + k.length + (v?.length ?? 0) + 2, 0); // +2 for '=' and null const argvBytes = ['/bin/bash', '-c', rewrittenAgentCommand] - .reduce((sum, arg) => sum + arg.length + 1, 0); + .reduce((sum, arg) => sum + arg.length + 1, 0); // +1 for each argv null terminator const totalArgvEnvBytes = totalEnvBytes + argvBytes; if (totalArgvEnvBytes > ENV_SIZE_WARNING_THRESHOLD) { logger.warn( From 8d41f147ab956a5ff6aa4191eb726d3e81c49650 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Wed, 15 Apr 2026 17:31:39 -0700 Subject: [PATCH 6/6] refactor: use in-place stdin redirect for prompt rewrite Replace 'cat file | command --prompt -' with 'command --prompt - < file' to preserve shell semantics for compound commands (pipes, &&, ;). Preserve original path quoting so env-var paths like $RUNNER_TEMP expand correctly at runtime instead of being force single-quoted. Add test coverage for env-var prompt paths and compound commands. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/docker-manager.test.ts | 40 ++++++++++++++++++++++++++++++++++---- src/docker-manager.ts | 29 +++++++++++++-------------- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/docker-manager.test.ts b/src/docker-manager.test.ts index 8fc56a7b..210d3236 100644 --- a/src/docker-manager.test.ts +++ b/src/docker-manager.test.ts @@ -1295,7 +1295,7 @@ describe('docker-manager', () => { expect(agent.command).toEqual(['/bin/bash', '-c', 'echo $$HOME && echo $${USER}']); }); - it('should rewrite inline --prompt $(cat ...) to stdin pipe to avoid ARG_MAX expansion', () => { + it('should rewrite inline --prompt $(cat ...) to stdin redirect to avoid ARG_MAX expansion', () => { const configWithInlinePrompt = { ...mockConfig, agentCommand: 'node /tmp/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" --allow-all-tools', @@ -1306,7 +1306,7 @@ describe('docker-manager', () => { expect(agent.command).toEqual([ '/bin/bash', '-c', - 'cat \'/tmp/gh-aw/aw-prompts/prompt.txt\' | node /tmp/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --prompt - --allow-all-tools', + 'node /tmp/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --prompt - < /tmp/gh-aw/aw-prompts/prompt.txt --allow-all-tools', ]); }); @@ -1321,7 +1321,7 @@ describe('docker-manager', () => { expect(agent.command).toEqual([ '/bin/bash', '-c', - 'cat \'/tmp/gh-aw/aw-prompts/prompt with spaces.txt\' | node /tmp/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --prompt - --allow-all-tools', + 'node /tmp/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --prompt - < \'/tmp/gh-aw/aw-prompts/prompt with spaces.txt\' --allow-all-tools', ]); }); @@ -1336,7 +1336,7 @@ describe('docker-manager', () => { expect(agent.command).toEqual([ '/bin/bash', '-c', - 'cat \'/tmp/gh-aw/aw-prompts/prompt.txt\' | node /tmp/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --prompt - --allow-all-tools', + 'node /tmp/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --prompt - < /tmp/gh-aw/aw-prompts/prompt.txt --allow-all-tools', ]); }); @@ -1351,6 +1351,38 @@ describe('docker-manager', () => { expect(agent.command).toEqual(['/bin/bash', '-c', 'copilot --prompt "hello world"']); }); + it('should preserve shell variable expansion in prompt paths', () => { + const configWithVarPath = { + ...mockConfig, + agentCommand: 'node driver.cjs copilot --prompt "$(cat "$RUNNER_TEMP/prompt.txt")" --allow-all-tools', + }; + const result = generateDockerCompose(configWithVarPath, mockNetworkConfig); + const agent = result.services.agent; + + // Env-var path must NOT be single-quoted; $RUNNER_TEMP must expand at runtime + expect(agent.command).toEqual([ + '/bin/bash', + '-c', + 'node driver.cjs copilot --prompt - < "$$RUNNER_TEMP/prompt.txt" --allow-all-tools', + ]); + }); + + it('should preserve compound command semantics when rewriting prompt', () => { + const configWithPipeline = { + ...mockConfig, + agentCommand: 'node driver.cjs copilot --prompt "$(cat /tmp/prompt.txt)" --allow-all-tools 2>&1 | tee -a /tmp/agent-stdio.log', + }; + const result = generateDockerCompose(configWithPipeline, mockNetworkConfig); + const agent = result.services.agent; + + // Input redirection is in-place; the trailing pipeline is preserved + expect(agent.command).toEqual([ + '/bin/bash', + '-c', + 'node driver.cjs copilot --prompt - < /tmp/prompt.txt --allow-all-tools 2>&1 | tee -a /tmp/agent-stdio.log', + ]); + }); + it('should pass through GITHUB_TOKEN when present in environment', () => { const originalEnv = process.env.GITHUB_TOKEN; process.env.GITHUB_TOKEN = 'ghp_testtoken123'; diff --git a/src/docker-manager.ts b/src/docker-manager.ts index aca685ce..e3495d87 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -51,13 +51,6 @@ let agentExternallyKilled = false; */ let awfDockerHostOverride: string | undefined; -function shellQuote(value: string): string { - // POSIX single-quote escaping: close quote, emit escaped single quote, reopen. - // Example: a'b -> 'a'\''b' - // Replacement token '\'' means: end quote + escaped single quote + reopen quote. - return `'${value.replace(/'/g, '\'\\\'\'')}'`; -} - function trimMatchingOuterQuotes(value: string): string { const trimmed = value.trim(); if ( @@ -69,7 +62,7 @@ function trimMatchingOuterQuotes(value: string): string { return trimmed; } -function findInlinePromptCatRange(command: string): { start: number; end: number; promptPath: string } | null { +function findInlinePromptCatRange(command: string): { start: number; end: number; promptPath: string; rawPromptPath: string } | null { // Variant 1: --prompt "$(cat /path/to/prompt.txt)" const quotedPrefix = '--prompt "$(cat '; const quotedStart = command.indexOf(quotedPrefix); @@ -81,6 +74,7 @@ function findInlinePromptCatRange(command: string): { start: number; end: number start: quotedStart, end: pathEnd + 2, promptPath: trimMatchingOuterQuotes(command.slice(pathStart, pathEnd)), + rawPromptPath: command.slice(pathStart, pathEnd).trim(), }; } } @@ -96,6 +90,7 @@ function findInlinePromptCatRange(command: string): { start: number; end: number start: unquotedStart, end: pathEnd + 1, promptPath: trimMatchingOuterQuotes(command.slice(pathStart, pathEnd)), + rawPromptPath: command.slice(pathStart, pathEnd).trim(), }; } } @@ -107,23 +102,25 @@ function findInlinePromptCatRange(command: string): { start: number; end: number * Rewrites inline prompt expansions of the form: * --prompt "$(cat /path/to/prompt.txt)" * to: - * cat '/path/to/prompt.txt' | ... --prompt - + * --prompt - < /path/to/prompt.txt * - * This keeps large prompts out of argv at execve time, reducing ARG_MAX/E2BIG - * failures when workflows inline big prompt files. + * Uses in-place input redirection (`< file`) instead of prepending `cat |`, + * which preserves shell semantics for compound commands (pipes, `&&`, `;`). + * The original path quoting/expansion is preserved so env-var paths like + * `"$GH_AW_PROMPT"` continue to expand correctly. */ function rewriteInlinePromptCatToStdin(agentCommand: string): string { const inlinePrompt = findInlinePromptCatRange(agentCommand); if (!inlinePrompt) return agentCommand; - const promptPath = inlinePrompt.promptPath; - if (!promptPath) return agentCommand; + const rawPath = inlinePrompt.rawPromptPath; + if (!rawPath) return agentCommand; - const rewrittenCommand = `${agentCommand.slice(0, inlinePrompt.start)}--prompt -${agentCommand.slice(inlinePrompt.end)}`; + const rewrittenCommand = `${agentCommand.slice(0, inlinePrompt.start)}--prompt - < ${rawPath}${agentCommand.slice(inlinePrompt.end)}`; if (rewrittenCommand === agentCommand) return agentCommand; - logger.warn(`Rewriting inline prompt expansion to stdin pipe to avoid ARG_MAX/E2BIG: ${promptPath}`); - return `cat ${shellQuote(promptPath)} | ${rewrittenCommand}`; + logger.warn(`Rewriting inline prompt expansion to stdin redirect to avoid ARG_MAX/E2BIG: ${rawPath}`); + return rewrittenCommand; } /**