mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 16:02:29 +00:00
fix(text-generation): refuse an oversized argv prompt on Linux too
The pre-spawn size guard only ran on Windows. Linux caps a single argv entry at MAX_ARG_STRLEN (32 pages, 128 KiB on a 4-KiB-page host) and execve fails with E2BIG past it, so an agent that delivers the whole prompt as one argument — jcode, and the other argv-delivery agents — failed on a large staged diff with an error the user could not act on. The cap is per-argument and in bytes, which is why it is not the Windows line budget: 40k chars trips Windows and is nowhere near the Linux limit, so folding them together would have refused prompts Linux runs fine. Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
This commit is contained in:
committed by
Neil
co-authored by
czzczz
parent
07d8aff138
commit
154b2fdab8
@@ -68,6 +68,45 @@ describe('generateCommitMessageFromContext', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('fails clearly before spawning when a jcode argv prompt exceeds the Linux single-argument cap', async () => {
|
||||
await withPlatform('linux', async () => {
|
||||
const pending = generateCommitMessageFromContext(
|
||||
{
|
||||
branch: 'main',
|
||||
stagedSummary: 'M\tREADME.md',
|
||||
// Why past 120 KiB and not the Windows 30k: Linux fails on ONE argument
|
||||
// over MAX_ARG_STRLEN, which is far larger than the Windows line budget.
|
||||
stagedPatch: `+${'x'.repeat(140_000)}`
|
||||
},
|
||||
{ agentId: 'jcode', model: 'default' },
|
||||
{ kind: 'local', cwd: '/repo', env: { ...process.env } }
|
||||
)
|
||||
|
||||
await expect(pending).resolves.toMatchObject({
|
||||
success: false,
|
||||
error: expect.stringContaining('single command-line argument')
|
||||
})
|
||||
expect(spawnMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it('still spawns on Linux for a prompt that only Windows would refuse', async () => {
|
||||
await withPlatform('linux', async () => {
|
||||
// 40k chars trips the Windows line budget but is far under the Linux per-arg cap,
|
||||
// so the guard must not have become a lowest-common-denominator limit.
|
||||
await generateCommitMessageFromContext(
|
||||
{
|
||||
branch: 'main',
|
||||
stagedSummary: 'M\tREADME.md',
|
||||
stagedPatch: `+${'x'.repeat(40_000)}`
|
||||
},
|
||||
{ agentId: 'jcode', model: 'default' },
|
||||
{ kind: 'local', cwd: '/repo', env: { ...process.env } }
|
||||
)
|
||||
expect(spawnMock).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps local commit-message and pull-request cancellation lanes separate', async () => {
|
||||
const children: {
|
||||
pid: number
|
||||
|
||||
@@ -56,6 +56,28 @@ function exceedsWindowsCommandLineBudget(command: string, args: string[]): boole
|
||||
return units > WINDOWS_COMMAND_LINE_UNIT_BUDGET
|
||||
}
|
||||
|
||||
// Why separate from the Windows budget: Linux caps a SINGLE argv entry at
|
||||
// MAX_ARG_STRLEN (32 pages, so 128 KiB on a 4-KiB-page host) and execve fails with
|
||||
// E2BIG past it, well before the much larger total-argv limit. Agents that deliver the
|
||||
// whole prompt as one argument trip this on a big staged diff, so the cap is per-arg
|
||||
// and in bytes, not units. Headroom left for hosts whose page size differs.
|
||||
const LINUX_SINGLE_ARGUMENT_BYTE_BUDGET = 120 * 1024
|
||||
|
||||
function exceedsLinuxArgumentBudget(args: string[]): boolean {
|
||||
return args.some((arg) => Buffer.byteLength(arg, 'utf8') > LINUX_SINGLE_ARGUMENT_BYTE_BUDGET)
|
||||
}
|
||||
|
||||
/** The user-facing reason this plan cannot be spawned here, or null when it can. */
|
||||
function argumentBudgetFailure(plan: CommitMessagePlan): string | null {
|
||||
if (process.platform === 'win32' && exceedsWindowsCommandLineBudget(plan.binary, plan.args)) {
|
||||
return `${plan.label} prompt is too large for the Windows command line. Stage fewer changes and try again.`
|
||||
}
|
||||
if (process.platform === 'linux' && exceedsLinuxArgumentBudget(plan.args)) {
|
||||
return `${plan.label} prompt is too large to pass as a single command-line argument. Stage fewer changes and try again.`
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function runLocalSourceControlPlan(input: {
|
||||
plan: CommitMessagePlan
|
||||
cwd: string
|
||||
@@ -74,14 +96,12 @@ export function runLocalSourceControlPlan(input: {
|
||||
const result = new Promise<InternalTextGenerationResult>((resolve) => {
|
||||
let child: SpawnedSourceControlAgentProcess
|
||||
try {
|
||||
if (process.platform === 'win32' && exceedsWindowsCommandLineBudget(plan.binary, plan.args)) {
|
||||
// Why before spawn: agents like jcode ride the whole prompt on argv, so a large
|
||||
// staged diff fails at execve with an error the user cannot act on.
|
||||
const budgetFailure = argumentBudgetFailure(plan)
|
||||
if (budgetFailure) {
|
||||
markProcessClosed()
|
||||
resolve({
|
||||
success: false,
|
||||
// Why: jcode rides the whole prompt on argv; a large staged diff would
|
||||
// exceed Windows' 32,767-unit command line and fail to spawn at all.
|
||||
error: `${plan.label} prompt is too large for the Windows command line. Stage fewer changes and try again.`
|
||||
})
|
||||
resolve({ success: false, error: budgetFailure })
|
||||
return
|
||||
}
|
||||
child = input.spawnAgent({
|
||||
|
||||
Reference in New Issue
Block a user