fix: increase output limits for truncating collector (#575)
This Pull Request addresses an issue where the output of commands executed in the raw-exec utility was being truncated due to restrictive limits on the number of lines and bytes collected. The truncation caused the message [Output truncated: too many lines or bytes] to appear when processing large outputs, which could hinder the functionality of the CLI. Changes Made Increased the maximum output limits in the [createTruncatingCollector](https://github.com/openai/codex/pull/575) utility: Bytes: Increased from 10 KB to 100 KB. Lines: Increased from 256 lines to 1024 lines. Installed the @types/node package to resolve missing type definitions for [NodeJS](https://github.com/openai/codex/pull/575) and [Buffer](https://github.com/openai/codex/pull/575). Verified and fixed any related errors in the [createTruncatingCollector](https://github.com/openai/codex/pull/575) implementation. Issue Solved: This PR ensures that larger outputs can be processed without truncation, improving the usability of the CLI for commands that generate extensive output. https://github.com/openai/codex/issues/509 --------- Co-authored-by: Michael Bolin <bolinfest@gmail.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import type { AppConfig } from "../config.js";
|
||||
import type { ExecInput, ExecResult } from "./sandbox/interface.js";
|
||||
import type { SpawnOptions } from "child_process";
|
||||
import type { ParseEntry } from "shell-quote";
|
||||
@@ -41,6 +42,7 @@ export function exec(
|
||||
additionalWritableRoots,
|
||||
}: ExecInput & { additionalWritableRoots: ReadonlyArray<string> },
|
||||
sandbox: SandboxType,
|
||||
config: AppConfig,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<ExecResult> {
|
||||
const opts: SpawnOptions = {
|
||||
@@ -52,7 +54,7 @@ export function exec(
|
||||
switch (sandbox) {
|
||||
case SandboxType.NONE: {
|
||||
// SandboxType.NONE uses the raw exec implementation.
|
||||
return rawExec(cmd, opts, abortSignal);
|
||||
return rawExec(cmd, opts, config, abortSignal);
|
||||
}
|
||||
case SandboxType.MACOS_SEATBELT: {
|
||||
// Merge default writable roots with any user-specified ones.
|
||||
@@ -61,10 +63,16 @@ export function exec(
|
||||
os.tmpdir(),
|
||||
...additionalWritableRoots,
|
||||
];
|
||||
return execWithSeatbelt(cmd, opts, writableRoots, abortSignal);
|
||||
return execWithSeatbelt(cmd, opts, writableRoots, config, abortSignal);
|
||||
}
|
||||
case SandboxType.LINUX_LANDLOCK: {
|
||||
return execWithLandlock(cmd, opts, additionalWritableRoots, abortSignal);
|
||||
return execWithLandlock(
|
||||
cmd,
|
||||
opts,
|
||||
additionalWritableRoots,
|
||||
config,
|
||||
abortSignal,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +94,7 @@ export async function handleExecCommand(
|
||||
/* applyPatch */ undefined,
|
||||
/* runInSandbox */ false,
|
||||
additionalWritableRoots,
|
||||
config,
|
||||
abortSignal,
|
||||
).then(convertSummaryToResult);
|
||||
}
|
||||
@@ -142,6 +143,7 @@ export async function handleExecCommand(
|
||||
applyPatch,
|
||||
runInSandbox,
|
||||
additionalWritableRoots,
|
||||
config,
|
||||
abortSignal,
|
||||
);
|
||||
// If the operation was aborted in the meantime, propagate the cancellation
|
||||
@@ -179,6 +181,7 @@ export async function handleExecCommand(
|
||||
applyPatch,
|
||||
false,
|
||||
additionalWritableRoots,
|
||||
config,
|
||||
abortSignal,
|
||||
);
|
||||
return convertSummaryToResult(summary);
|
||||
@@ -213,6 +216,7 @@ async function execCommand(
|
||||
applyPatchCommand: ApplyPatchCommand | undefined,
|
||||
runInSandbox: boolean,
|
||||
additionalWritableRoots: ReadonlyArray<string>,
|
||||
config: AppConfig,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<ExecCommandSummary> {
|
||||
let { workdir } = execInput;
|
||||
@@ -252,6 +256,7 @@ async function execCommand(
|
||||
: await exec(
|
||||
{ ...execInput, additionalWritableRoots },
|
||||
await getSandbox(runInSandbox),
|
||||
config,
|
||||
abortSignal,
|
||||
);
|
||||
const duration = Date.now() - start;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Maximum output cap: either MAX_OUTPUT_LINES lines or MAX_OUTPUT_BYTES bytes,
|
||||
// whichever limit is reached first.
|
||||
const MAX_OUTPUT_BYTES = 1024 * 10; // 10 KB
|
||||
const MAX_OUTPUT_LINES = 256;
|
||||
import { DEFAULT_SHELL_MAX_BYTES, DEFAULT_SHELL_MAX_LINES } from "../../config";
|
||||
|
||||
/**
|
||||
* Creates a collector that accumulates data Buffers from a stream up to
|
||||
@@ -10,8 +9,8 @@ const MAX_OUTPUT_LINES = 256;
|
||||
*/
|
||||
export function createTruncatingCollector(
|
||||
stream: NodeJS.ReadableStream,
|
||||
byteLimit: number = MAX_OUTPUT_BYTES,
|
||||
lineLimit: number = MAX_OUTPUT_LINES,
|
||||
byteLimit: number = DEFAULT_SHELL_MAX_BYTES,
|
||||
lineLimit: number = DEFAULT_SHELL_MAX_LINES,
|
||||
): {
|
||||
getString: () => string;
|
||||
hit: boolean;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ExecResult } from "./interface.js";
|
||||
import type { AppConfig } from "../../config.js";
|
||||
import type { SpawnOptions } from "child_process";
|
||||
|
||||
import { exec } from "./raw-exec.js";
|
||||
@@ -19,6 +20,7 @@ export async function execWithLandlock(
|
||||
cmd: Array<string>,
|
||||
opts: SpawnOptions,
|
||||
userProvidedWritableRoots: ReadonlyArray<string>,
|
||||
config: AppConfig,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<ExecResult> {
|
||||
const sandboxExecutable = await getSandboxExecutable();
|
||||
@@ -44,7 +46,7 @@ export async function execWithLandlock(
|
||||
...cmd,
|
||||
];
|
||||
|
||||
return exec(fullCommand, opts, abortSignal);
|
||||
return exec(fullCommand, opts, config, abortSignal);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ExecResult } from "./interface.js";
|
||||
import type { AppConfig } from "../../config.js";
|
||||
import type { SpawnOptions } from "child_process";
|
||||
|
||||
import { exec } from "./raw-exec.js";
|
||||
@@ -24,6 +25,7 @@ export function execWithSeatbelt(
|
||||
cmd: Array<string>,
|
||||
opts: SpawnOptions,
|
||||
writableRoots: ReadonlyArray<string>,
|
||||
config: AppConfig,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<ExecResult> {
|
||||
let scopedWritePolicy: string;
|
||||
@@ -72,7 +74,7 @@ export function execWithSeatbelt(
|
||||
"--",
|
||||
...cmd,
|
||||
];
|
||||
return exec(fullCommand, opts, abortSignal);
|
||||
return exec(fullCommand, opts, config, abortSignal);
|
||||
}
|
||||
|
||||
const READ_ONLY_SEATBELT_POLICY = `
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ExecResult } from "./interface";
|
||||
import type { AppConfig } from "../../config";
|
||||
import type {
|
||||
ChildProcess,
|
||||
SpawnOptions,
|
||||
@@ -20,6 +21,7 @@ import * as os from "os";
|
||||
export function exec(
|
||||
command: Array<string>,
|
||||
options: SpawnOptions,
|
||||
config: AppConfig,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<ExecResult> {
|
||||
// Adapt command for the current platform (e.g., convert 'ls' to 'dir' on Windows)
|
||||
@@ -142,9 +144,21 @@ export function exec(
|
||||
// ExecResult object so the rest of the agent loop can carry on gracefully.
|
||||
|
||||
return new Promise<ExecResult>((resolve) => {
|
||||
// Get shell output limits from config if available
|
||||
const maxBytes = config?.tools?.shell?.maxBytes;
|
||||
const maxLines = config?.tools?.shell?.maxLines;
|
||||
|
||||
// Collect stdout and stderr up to configured limits.
|
||||
const stdoutCollector = createTruncatingCollector(child.stdout!);
|
||||
const stderrCollector = createTruncatingCollector(child.stderr!);
|
||||
const stdoutCollector = createTruncatingCollector(
|
||||
child.stdout!,
|
||||
maxBytes,
|
||||
maxLines,
|
||||
);
|
||||
const stderrCollector = createTruncatingCollector(
|
||||
child.stderr!,
|
||||
maxBytes,
|
||||
maxLines,
|
||||
);
|
||||
|
||||
child.on("exit", (code, signal) => {
|
||||
const stdout = stdoutCollector.getString();
|
||||
|
||||
@@ -48,6 +48,10 @@ export const DEFAULT_FULL_CONTEXT_MODEL = "gpt-4.1";
|
||||
export const DEFAULT_APPROVAL_MODE = AutoApprovalMode.SUGGEST;
|
||||
export const DEFAULT_INSTRUCTIONS = "";
|
||||
|
||||
// Default shell output limits
|
||||
export const DEFAULT_SHELL_MAX_BYTES = 1024 * 10; // 10 KB
|
||||
export const DEFAULT_SHELL_MAX_LINES = 256;
|
||||
|
||||
export const CONFIG_DIR = join(homedir(), ".codex");
|
||||
export const CONFIG_JSON_FILEPATH = join(CONFIG_DIR, "config.json");
|
||||
export const CONFIG_YAML_FILEPATH = join(CONFIG_DIR, "config.yaml");
|
||||
@@ -145,6 +149,12 @@ export type StoredConfig = {
|
||||
saveHistory?: boolean;
|
||||
sensitivePatterns?: Array<string>;
|
||||
};
|
||||
tools?: {
|
||||
shell?: {
|
||||
maxBytes?: number;
|
||||
maxLines?: number;
|
||||
};
|
||||
};
|
||||
/** User-defined safe commands */
|
||||
safeCommands?: Array<string>;
|
||||
reasoningEffort?: ReasoningEffort;
|
||||
@@ -186,6 +196,12 @@ export type AppConfig = {
|
||||
saveHistory: boolean;
|
||||
sensitivePatterns: Array<string>;
|
||||
};
|
||||
tools?: {
|
||||
shell?: {
|
||||
maxBytes: number;
|
||||
maxLines: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// Formatting (quiet mode-only).
|
||||
@@ -388,6 +404,14 @@ export const loadConfig = (
|
||||
instructions: combinedInstructions,
|
||||
notify: storedConfig.notify === true,
|
||||
approvalMode: storedConfig.approvalMode,
|
||||
tools: {
|
||||
shell: {
|
||||
maxBytes:
|
||||
storedConfig.tools?.shell?.maxBytes ?? DEFAULT_SHELL_MAX_BYTES,
|
||||
maxLines:
|
||||
storedConfig.tools?.shell?.maxLines ?? DEFAULT_SHELL_MAX_LINES,
|
||||
},
|
||||
},
|
||||
disableResponseStorage: storedConfig.disableResponseStorage === true,
|
||||
reasoningEffort: storedConfig.reasoningEffort,
|
||||
};
|
||||
@@ -517,6 +541,18 @@ export const saveConfig = (
|
||||
};
|
||||
}
|
||||
|
||||
// Add tools settings if they exist
|
||||
if (config.tools) {
|
||||
configToSave.tools = {
|
||||
shell: config.tools.shell
|
||||
? {
|
||||
maxBytes: config.tools.shell.maxBytes,
|
||||
maxLines: config.tools.shell.maxLines,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (ext === ".yaml" || ext === ".yml") {
|
||||
writeFileSync(targetPath, dumpYaml(configToSave), "utf-8");
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user