Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions apps/daemon/src/runtimes/defs/grok-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,10 @@ export const grokBuildAgentDef = {
label: 'grok-4.20-multi-agent (xAI · orchestration)',
},
],
// Prompt delivered via stdin so Windows `spawn ENAMETOOLONG` and Linux
// `spawn E2BIG` can't truncate large composed prompts. `grok -p` with
// no positional argument reads from piped stdin.
buildArgs: (_prompt, _imagePaths, _extra = [], options = {}) => {
const args = ['-p'];
// Grok Build CLI v0.1.212 enforces `-p, --single <PROMPT>` as value-
// required — stdin piping no longer satisfies it. Inline the prompt.
buildArgs: (prompt, _imagePaths, _extra = [], options = {}) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching buildArgs to [-p, prompt] and promptViaStdin: false moves the full composed OD prompt back onto argv, but this adapter still does not declare a maxPromptArgBytes budget or any prompt-budget regression coverage. That matters because the daemon only preflights argv-bound adapters when maxPromptArgBytes is set (apps/daemon/src/runtimes/prompt-budget.ts), and those composed prompts can include system text, history, skills, and design-system content. In this shape, a large Grok run will regress from the old stdin path to a raw spawn E2BIG/ENAMETOOLONG failure instead of the actionable chat error we already emit for other argv-only adapters like DeepSeek. Please either keep the prompt off argv (for example via a temp --prompt-file shim) or add a conservative maxPromptArgBytes limit plus prompt-budget tests before we rely on inline prompts here.

🔁 Powered by Looper · runner=reviewer · agent=opencode · An autonomous AI dev team for your GitHub repos.

const args = ['-p', prompt];
if (options.model && options.model !== DEFAULT_MODEL_OPTION.id) {
args.push('--model', options.model);
}
Expand All @@ -69,7 +68,21 @@ export const grokBuildAgentDef = {
{ id: 'xhigh', label: 'xhigh' },
{ id: 'max', label: 'max' },
],
promptViaStdin: true,
promptViaStdin: false,
// Guard against prompts that would blow Windows' ~32 KB CreateProcess
// limit (or Linux MAX_ARG_STRLEN on extreme edges) before spawn. Same
// shape as the DeepSeek adapter — the previous stdin path is gone (CLI
// 0.1.212 enforces `-p <value>`), so the composed prompt now rides
// argv and a sufficiently large one — system text + history + skills/
// design-system content + user message — could surface as a generic
// spawn ENAMETOOLONG / E2BIG instead of a Grok-specific, user-
// actionable message. The /api/chat spawn path checks this byte
// budget against the composed prompt and emits AGENT_PROMPT_TOO_LARGE
// ("reduce skills/design-system context, or pick an adapter with
// stdin support") before calling `spawn`. 30_000 bytes leaves ~2.7 KB
// of argv headroom under the Windows command-line limit for `-p
// --model <id> --effort <level>` and internal quoting.
maxPromptArgBytes: 30_000,
streamFormat: 'plain',
installUrl: 'https://x.ai/cli',
docsUrl: 'https://x.ai/cli',
Expand Down
6 changes: 6 additions & 0 deletions apps/daemon/src/runtimes/prompt-budget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ function promptArgvBudgetMessage(
'Reduce the selected skills/design-system context or conversation length, or use DeepSeek through an API/provider model connection for large contexts. Pick a stdin-capable adapter when the prompt must include large local context.'
);
}
if (def.id === 'grok-build') {
return (
`${def.name} requires the prompt as the value of -p / --single (xAI CLI 0.1.212+ no longer reads piped stdin), and this run's composed prompt exceeds the safe size (${bytes} > ${def.maxPromptArgBytes} bytes). ` +
'Reduce the selected skills/design-system context or conversation length, or pick an adapter with stdin support (e.g. claude, codex, hermes) when the prompt must include large local context.'
);
}
return (
`${def.name} requires the prompt as a command-line argument and this run's composed prompt exceeds the safe size (${bytes} > ${def.maxPromptArgBytes} bytes). ` +
'Reduce the selected skills/design-system context, shorten the conversation, or pick an adapter with stdin support.'
Expand Down
8 changes: 8 additions & 0 deletions apps/daemon/tests/runtimes/helpers/test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,21 @@ export const gemini = requireAgent('gemini');
export const qoder = requireAgent('qoder');
export const qwen = requireAgent('qwen');
export const opencode = requireAgent('opencode');
export const grokBuild = requireAgent('grok-build');
export const deepseekMaxPromptArgBytes = (() => {
assert.ok(
deepseek.maxPromptArgBytes !== undefined,
'deepseek must define maxPromptArgBytes for argv budget tests',
);
return deepseek.maxPromptArgBytes;
})();
export const grokBuildMaxPromptArgBytes = (() => {
assert.ok(
grokBuild.maxPromptArgBytes !== undefined,
'grok-build must define maxPromptArgBytes for argv budget tests',
);
return grokBuild.maxPromptArgBytes;
})();
const originalDisablePlugins = process.env.OD_CODEX_DISABLE_PLUGINS;
const originalPath = process.env.PATH;
const originalHome = process.env.HOME;
Expand Down
60 changes: 59 additions & 1 deletion apps/daemon/tests/runtimes/prompt-budget.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test } from 'vitest';
import {
assert, checkPromptArgvBudget, checkWindowsCmdShimCommandLineBudget, checkWindowsDirectExeCommandLineBudget, claude, deepseek, deepseekMaxPromptArgBytes, vibe,
assert, checkPromptArgvBudget, checkWindowsCmdShimCommandLineBudget, checkWindowsDirectExeCommandLineBudget, claude, deepseek, deepseekMaxPromptArgBytes, grokBuild, grokBuildMaxPromptArgBytes, vibe,
} from './helpers/test-helpers.js';
import type { TestAgentDef } from './helpers/test-helpers.js';

Expand Down Expand Up @@ -107,6 +107,64 @@ test('checkPromptArgvBudget gives DeepSeek-specific guidance for large contexts'
assert.match(flagged.message, /stdin-capable adapter/);
});

// Grok Build CLI 0.1.212+ enforces `-p, --single <PROMPT>` as value-
// required, so the prompt rides argv just like DeepSeek. Pin the budget
// field and the byte-vs-codepoint guard so a future runtime-def edit
// can't silently drop the guard or let it drift over the Windows
// CreateProcess limit.
test('grok-build declares a conservative argv-byte budget for the prompt', () => {
assert.equal(
typeof grokBuildMaxPromptArgBytes,
'number',
'grok-build must set maxPromptArgBytes so the spawn path can pre-flight oversized prompts before hitting CreateProcess / E2BIG',
);
assert.ok(
grokBuildMaxPromptArgBytes > 0 && grokBuildMaxPromptArgBytes < 32_768,
`grokBuildMaxPromptArgBytes must stay strictly under the Windows CreateProcess limit (~32 KB); got ${grokBuildMaxPromptArgBytes}`,
);
});

test('checkPromptArgvBudget flags oversized Grok Build prompts and lets short prompts through', () => {
const oversized = 'x'.repeat(grokBuildMaxPromptArgBytes + 1);
const flagged = checkPromptArgvBudget(grokBuild, oversized);
assert.ok(flagged, 'oversized prompts must trip the argv-byte guard');
assert.equal(flagged.code, 'AGENT_PROMPT_TOO_LARGE');
assert.equal(flagged.limit, grokBuildMaxPromptArgBytes);
assert.equal(flagged.bytes, grokBuildMaxPromptArgBytes + 1);
assert.match(flagged.message, /Grok Build/);
assert.match(flagged.message, /-p \/ --single/);
assert.match(flagged.message, /stdin/);

// Happy path: chat must keep working for normal-sized prompts.
assert.equal(checkPromptArgvBudget(grokBuild, 'hello'), null);

// Exact-budget edge: at-limit prompts pass; guard fires only on strict
// overrun.
const atLimit = 'x'.repeat(grokBuildMaxPromptArgBytes);
assert.equal(checkPromptArgvBudget(grokBuild, atLimit), null);

// Multi-byte UTF-8 (CJK = 3 bytes) must be byte-counted, not code-
// point-counted — mirrors the DeepSeek byte-count regression guard.
const cjkOversized = '汉'.repeat(
Math.ceil(grokBuildMaxPromptArgBytes / 3) + 1,
);
const cjkFlagged = checkPromptArgvBudget(grokBuild, cjkOversized);
assert.ok(cjkFlagged, 'byte-counted UTF-8 prompts must also trip the guard');
assert.equal(cjkFlagged.code, 'AGENT_PROMPT_TOO_LARGE');
});

test('checkPromptArgvBudget gives Grok-Build-specific guidance for large contexts', () => {
const oversized = 'x'.repeat(grokBuildMaxPromptArgBytes + 1);
const flagged = checkPromptArgvBudget(grokBuild, oversized);

assert.ok(flagged, 'oversized Grok Build prompts must return a diagnostic');
assert.match(flagged.message, /Grok Build/);
assert.match(flagged.message, /-p \/ --single/);
assert.match(flagged.message, /xAI CLI 0\.1\.212\+/);
assert.match(flagged.message, /no longer reads piped stdin/);
assert.match(flagged.message, /stdin support/);
});

// Adapters that ship the prompt over stdin (every other code agent
// today) don't declare `maxPromptArgBytes` and must skip the guard
// entirely — applying it to them would refuse perfectly valid huge
Expand Down