@@ -62,14 +62,15 @@ class DisposableProcess implements Disposable {
6262 */
6363export const createBashTool : ToolFactory = ( config : ToolConfiguration ) => {
6464 // Select limits based on overflow policy
65- // truncate = IPC calls (generous limits for UI features, no line limit)
65+ // truncate = IPC calls (generous limits for UI features, no line limit, no per-line limit )
6666 // tmpfile = AI agent calls (conservative limits for LLM context)
6767 const overflowPolicy = config . overflow_policy ?? "tmpfile" ;
6868 const maxTotalBytes =
6969 overflowPolicy === "truncate" ? BASH_TRUNCATE_MAX_TOTAL_BYTES : BASH_MAX_TOTAL_BYTES ;
7070 const maxFileBytes =
7171 overflowPolicy === "truncate" ? BASH_TRUNCATE_MAX_FILE_BYTES : BASH_MAX_FILE_BYTES ;
7272 const maxLines = overflowPolicy === "truncate" ? Infinity : BASH_HARD_MAX_LINES ;
73+ const maxLineBytes = overflowPolicy === "truncate" ? Infinity : BASH_MAX_LINE_BYTES ;
7374
7475 return tool ( {
7576 description : TOOL_DEFINITIONS . bash . description + "\nRuns in " + config . cwd + " - no cd needed" ,
@@ -237,25 +238,26 @@ export const createBashTool: ToolFactory = (config: ToolConfiguration) => {
237238 const lineBytes = Buffer . byteLength ( line , "utf-8" ) ;
238239
239240 // Check if line exceeds per-line limit (hard stop - this is likely corrupt data)
240- if ( lineBytes > BASH_MAX_LINE_BYTES ) {
241+ if ( lineBytes > maxLineBytes ) {
241242 triggerFileTruncation (
242- `Line ${ lines . length + 1 } exceeded per-line limit: ${ lineBytes } bytes > ${ BASH_MAX_LINE_BYTES } bytes`
243+ `Line ${ lines . length + 1 } exceeded per-line limit: ${ lineBytes } bytes > ${ maxLineBytes } bytes`
243244 ) ;
244245 return ;
245246 }
246247
247- // Collect this line (even if display is truncated, keep for file)
248- lines . push ( line ) ;
249- totalBytesAccumulated += lineBytes + 1 ; // +1 for newline
250-
251- // Check file limit first (hard stop)
252- if ( totalBytesAccumulated > maxFileBytes ) {
248+ // Check file limit BEFORE adding line to prevent overlong lines from being returned
249+ const bytesAfterLine = totalBytesAccumulated + lineBytes + 1 ; // +1 for newline
250+ if ( bytesAfterLine > maxFileBytes ) {
253251 triggerFileTruncation (
254- `Total output exceeded file preservation limit: ${ totalBytesAccumulated } bytes > ${ maxFileBytes } bytes (at line ${ lines . length } )`
252+ `Total output would exceed file preservation limit: ${ bytesAfterLine } bytes > ${ maxFileBytes } bytes (at line ${ lines . length + 1 } )`
255253 ) ;
256254 return ;
257255 }
258256
257+ // Collect this line (even if display is truncated, keep for file)
258+ lines . push ( line ) ;
259+ totalBytesAccumulated = bytesAfterLine ;
260+
259261 // Check display limits (soft stop - keep collecting for file)
260262 if ( ! displayTruncated ) {
261263 if ( totalBytesAccumulated > maxTotalBytes ) {
@@ -279,25 +281,26 @@ export const createBashTool: ToolFactory = (config: ToolConfiguration) => {
279281 const lineBytes = Buffer . byteLength ( line , "utf-8" ) ;
280282
281283 // Check if line exceeds per-line limit (hard stop - this is likely corrupt data)
282- if ( lineBytes > BASH_MAX_LINE_BYTES ) {
284+ if ( lineBytes > maxLineBytes ) {
283285 triggerFileTruncation (
284- `Line ${ lines . length + 1 } exceeded per-line limit: ${ lineBytes } bytes > ${ BASH_MAX_LINE_BYTES } bytes`
286+ `Line ${ lines . length + 1 } exceeded per-line limit: ${ lineBytes } bytes > ${ maxLineBytes } bytes`
285287 ) ;
286288 return ;
287289 }
288290
289- // Collect this line (even if display is truncated, keep for file)
290- lines . push ( line ) ;
291- totalBytesAccumulated += lineBytes + 1 ; // +1 for newline
292-
293- // Check file limit first (hard stop)
294- if ( totalBytesAccumulated > maxFileBytes ) {
291+ // Check file limit BEFORE adding line to prevent overlong lines from being returned
292+ const bytesAfterLine = totalBytesAccumulated + lineBytes + 1 ; // +1 for newline
293+ if ( bytesAfterLine > maxFileBytes ) {
295294 triggerFileTruncation (
296- `Total output exceeded file preservation limit: ${ totalBytesAccumulated } bytes > ${ maxFileBytes } bytes (at line ${ lines . length } )`
295+ `Total output would exceed file preservation limit: ${ bytesAfterLine } bytes > ${ maxFileBytes } bytes (at line ${ lines . length + 1 } )`
297296 ) ;
298297 return ;
299298 }
300299
300+ // Collect this line (even if display is truncated, keep for file)
301+ lines . push ( line ) ;
302+ totalBytesAccumulated = bytesAfterLine ;
303+
301304 // Check display limits (soft stop - keep collecting for file)
302305 if ( ! displayTruncated ) {
303306 if ( totalBytesAccumulated > maxTotalBytes ) {
0 commit comments