Mitigate ARG_MAX/E2BIG by rewriting inline prompt expansion to stdin in AWF agent command#1999
Mitigate ARG_MAX/E2BIG by rewriting inline prompt expansion to stdin in AWF agent command#1999
Conversation
There was a problem hiding this comment.
Pull request overview
Mitigates Linux ARG_MAX / E2BIG failures caused by large inline prompt expansions (e.g. --prompt "$(cat ...)") by rewriting the agent command to feed the prompt via stdin, and improves preflight diagnostics by warning on estimated combined argv + env size.
Changes:
- Adds a command rewriter in
generateDockerCompose()to detect--prompt $(cat ...)/--prompt "$(cat ...)"and convert it tocat 'file' | ... --prompt -. - Enhances the existing
--env-allwarning to estimate combinedargv + envbytes (instead of env-only). - Adds unit tests validating the rewrite behavior for quoted/unquoted forms and a no-op for literal prompts.
Show a summary per file
| File | Description |
|---|---|
src/docker-manager.ts |
Introduces inline-prompt rewrite + enhanced argv+env sizing warning; switches compose command to use rewritten agent command. |
src/docker-manager.test.ts |
Adds test coverage for prompt rewrite variants and ensures literal prompts are not rewritten. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 2/2 changed files
- Comments generated: 2
| 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}`); | ||
| return `cat ${shellQuote(promptPath)} | ${rewrittenCommand}`; |
There was a problem hiding this comment.
The rewrite currently prepends cat ... | to the entire agentCommand. This changes shell semantics when agentCommand is a compound command (e.g. contains &&, ;, an existing pipe, or redirects): stdin gets consumed by the left-most command and the pipeline exit status/precedence can differ from the original, potentially breaking execution.
Also, shellQuote(promptPath) forces single-quoting, which will prevent any $VAR expansion that may have worked in the original $(cat $VAR/...) or $(cat "$VAR") form.
Consider either (a) restricting the rewrite to “simple” commands (no shell operators / variable expansions in the prompt path) and otherwise leaving the command unchanged, or (b) rewriting in-place to use --prompt - with an input redirection that applies only to the copilot invocation, while preserving the original path quoting/expansion semantics.
| 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 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 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, | ||
| 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"']); | ||
| }); |
There was a problem hiding this comment.
Tests cover literal paths (including single-quoted paths with spaces), but there’s no coverage for prompt file paths that rely on shell expansion (e.g. $RUNNER_TEMP/prompt.txt or "$GH_AW_PROMPT"). Given the rewrite logic currently normalizes then single-quotes the extracted path, adding a test for an env-var-based path would help catch regressions/semantic changes once the quoting/rewriting strategy is adjusted.
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>
✅ Coverage Check PassedOverall Coverage
📁 Per-file Coverage Changes (1 files)
Coverage comparison generated by |
|
Smoke test results (run 24485544682)
Overall: PASS
|
|
Smoke test results:
|
|
abandoning since copilot cli does not support --prompt - |
Large gh-aw workflows can inline 100KB+ prompts into
argvvia--prompt "$(cat ...)", which combines with container env size and can exceed LinuxARG_MAX, failing process start withE2BIG. This change adds an AWF-side mitigation by keeping prompt payload on disk and passing it via stdin instead of argv.Command rewrite for inline prompt expansions
agentCommand:--prompt "$(cat /path/to/prompt.txt)"--prompt $(cat /path/to/prompt.txt)cat '/path/to/prompt.txt' | ... --prompt -ARG_MAX diagnostics improvement
--env-allwarning from env-only size to estimated combinedargv + envsize.execvefailure.Focused coverage
$(cat ...)rewrite$(cat ...)rewrite--promptno-op behavior