fix(daemon): grok-build — pass prompt inline as -p value, drop stdin (#2259)

* fix(daemon): grok-build runtime — pass prompt inline as -p value, drop stdin

Grok Build CLI 0.1.212 enforces `-p, --single <PROMPT>` as a value-requiring
flag — invoking with bare `-p` and piping the prompt to stdin now fails with:

  error: a value is required for '--single <PROMPT>' but none was supplied

The previous runtime def used `promptViaStdin: true` + `buildArgs` returning
`['-p']`, which only worked against earlier grok builds that read the prompt
from stdin when `-p` had no inline value.

This change inlines the prompt as the `-p` argument value and flips
`promptViaStdin: false`. Linux `MAX_ARG_STRLEN` (128 KB) is enough headroom
for typical Open Design prompts; if we ever hit `E2BIG` on a very large
brief, a follow-up could shell out to `--prompt-file <tempfile>`.

Verified against grok 0.1.212 (b7b8204a4) — single-turn invocations now
return clean text replies instead of exit 2.

* fix(daemon): declare grok-build argv prompt budget + regression coverage

@mrcfps' review on #2259 flagged that moving the Grok Build adapter from
the (no-longer-working) stdin path to argv would regress oversized
composed prompts from the actionable AGENT_PROMPT_TOO_LARGE error we
already emit for DeepSeek to a raw spawn ENAMETOOLONG / E2BIG instead.
Fixed by mirroring the DeepSeek argv-budget shape:

- grok-build.ts: `maxPromptArgBytes: 30_000` (same headroom as DeepSeek,
  ~2.7 KB under the Windows CreateProcess 32_767-char cap) so
  `checkPromptArgvBudget` pre-flights composed prompts (system + history
  + skills + design-system content + user message) before spawn.
- prompt-budget.ts: Grok-Build-specific message — names the `-p /
  --single` flag, the xAI CLI 0.1.212+ behavior change, and points the
  user at stdin-capable adapters (claude / codex / hermes) when they
  need to ship large local context.
- Tests: 3 new vitest cases in prompt-budget.test.ts — pin the budget
  field, exercise the strict-overrun + at-limit + CJK byte-count guards
  exactly like the DeepSeek regression set, and assert the Grok-named
  diagnostic copy. New `grokBuild` + `grokBuildMaxPromptArgBytes`
  helpers exported alongside the existing `deepseek*` ones.

All 23 prompt-budget tests pass locally (`pnpm exec vitest run
tests/runtimes/prompt-budget.test.ts`).

---------

Co-authored-by: Sriram Sivakumar <sriram155@gmail.com>
Co-authored-by: Siri-Ray <2667192167@qq.com>
This commit is contained in:
Sriram Sivakumar 2026-05-29 16:45:57 +08:00 committed by GitHub
parent 73b2dc853f
commit 0bd07b2a3d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 92 additions and 7 deletions

View file

@ -49,11 +49,10 @@ export const grokBuildAgentDef = {
label: 'grok-4.20-multi-agent (xAI · orchestration)', label: 'grok-4.20-multi-agent (xAI · orchestration)',
}, },
], ],
// Prompt delivered via stdin so Windows `spawn ENAMETOOLONG` and Linux // Grok Build CLI v0.1.212 enforces `-p, --single <PROMPT>` as value-
// `spawn E2BIG` can't truncate large composed prompts. `grok -p` with // required — stdin piping no longer satisfies it. Inline the prompt.
// no positional argument reads from piped stdin. buildArgs: (prompt, _imagePaths, _extra = [], options = {}) => {
buildArgs: (_prompt, _imagePaths, _extra = [], options = {}) => { const args = ['-p', prompt];
const args = ['-p'];
if (options.model && options.model !== DEFAULT_MODEL_OPTION.id) { if (options.model && options.model !== DEFAULT_MODEL_OPTION.id) {
args.push('--model', options.model); args.push('--model', options.model);
} }
@ -69,7 +68,21 @@ export const grokBuildAgentDef = {
{ id: 'xhigh', label: 'xhigh' }, { id: 'xhigh', label: 'xhigh' },
{ id: 'max', label: 'max' }, { 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', streamFormat: 'plain',
installUrl: 'https://x.ai/cli', installUrl: 'https://x.ai/cli',
docsUrl: 'https://x.ai/cli', docsUrl: 'https://x.ai/cli',

View file

@ -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.' '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 ( 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). ` + `${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.' 'Reduce the selected skills/design-system context, shorten the conversation, or pick an adapter with stdin support.'

View file

@ -86,6 +86,7 @@ export const gemini = requireAgent('gemini');
export const qoder = requireAgent('qoder'); export const qoder = requireAgent('qoder');
export const qwen = requireAgent('qwen'); export const qwen = requireAgent('qwen');
export const opencode = requireAgent('opencode'); export const opencode = requireAgent('opencode');
export const grokBuild = requireAgent('grok-build');
export const aider = requireAgent('aider'); export const aider = requireAgent('aider');
export const antigravity = requireAgent('antigravity'); export const antigravity = requireAgent('antigravity');
export const deepseekMaxPromptArgBytes = (() => { export const deepseekMaxPromptArgBytes = (() => {
@ -95,6 +96,13 @@ export const deepseekMaxPromptArgBytes = (() => {
); );
return deepseek.maxPromptArgBytes; 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 originalDisablePlugins = process.env.OD_CODEX_DISABLE_PLUGINS;
const originalPath = process.env.PATH; const originalPath = process.env.PATH;
const originalHome = process.env.HOME; const originalHome = process.env.HOME;

View file

@ -1,6 +1,6 @@
import { test } from 'vitest'; import { test } from 'vitest';
import { import {
assert, checkPromptArgvBudget, checkWindowsCmdShimCommandLineBudget, checkWindowsDirectExeCommandLineBudget, claude, deepseek, deepseekMaxPromptArgBytes, vibe, assert, checkPromptArgvBudget, checkWindowsCmdShimCommandLineBudget, checkWindowsDirectExeCommandLineBudget, claude, deepseek, deepseekMaxPromptArgBytes, grokBuild, grokBuildMaxPromptArgBytes, vibe,
} from './helpers/test-helpers.js'; } from './helpers/test-helpers.js';
import type { TestAgentDef } from './helpers/test-helpers.js'; import type { TestAgentDef } from './helpers/test-helpers.js';
@ -107,6 +107,64 @@ test('checkPromptArgvBudget gives DeepSeek-specific guidance for large contexts'
assert.match(flagged.message, /stdin-capable adapter/); 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 // Adapters that ship the prompt over stdin (every other code agent
// today) don't declare `maxPromptArgBytes` and must skip the guard // today) don't declare `maxPromptArgBytes` and must skip the guard
// entirely — applying it to them would refuse perfectly valid huge // entirely — applying it to them would refuse perfectly valid huge