From 8b7987ae0d4ab3a8db10a76652b56695c9c845c2 Mon Sep 17 00:00:00 2001 From: Travis Thieman Date: Wed, 25 Mar 2026 16:12:20 -0400 Subject: [PATCH 1/2] fix(tail): reduce ring buffer and byte-mode buffer caps to 5 MiB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MaxRingBytes: 64 MiB → 5 MiB Byte-mode circular buffer: 32 MiB → 5 MiB A single tail invocation could previously hold up to 64 MiB of live data in the ring buffer, and up to 32 MiB in byte mode. These limits are reduced to 5 MiB to keep single-command memory usage bounded. Co-Authored-By: Claude Sonnet 4.6 --- builtins/tail/tail.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/builtins/tail/tail.go b/builtins/tail/tail.go index 4eeaff5b..57512a6b 100644 --- a/builtins/tail/tail.go +++ b/builtins/tail/tail.go @@ -49,7 +49,7 @@ // Line mode uses a ring buffer of size min(N, MaxRingLines) slots. Each slot // holds one line; lines exceeding MaxLineBytes cause a scanner error. The // ring buffer's total memory footprint is additionally capped at MaxRingBytes -// (64 MiB). If the input has more lines than the ring can hold and N exceeds +// (5 MiB). If the input has more lines than the ring can hold and N exceeds // MaxRingLines, tail returns an error rather than silently truncating output. // // Byte mode uses a circular buffer of size min(N, MaxBytesBuffer). If the @@ -104,12 +104,12 @@ const MaxRingLines = 100_000 // MaxRingBytes is the maximum total bytes the ring buffer may hold at any // one time. Without this cap, MaxRingLines (100 000) × MaxLineBytes (1 MiB) // yields a worst-case memory envelope of ~97.6 GiB. This constant reduces -// the bound to 64 MiB. -const MaxRingBytes = 64 << 20 // 64 MiB +// the bound to 5 MiB. +const MaxRingBytes = 5 << 20 // 5 MiB // MaxBytesBuffer is the maximum size of the circular byte buffer used in // last-N-bytes mode. -const MaxBytesBuffer = 32 << 20 // 32 MiB +const MaxBytesBuffer = 5 << 20 // 5 MiB // MaxTotalReadBytes is the maximum total bytes tail will consume from a // single input source. Both last-N-lines and last-N-bytes modes must read @@ -432,11 +432,11 @@ func readLastBytes(ctx context.Context, callCtx *builtins.CallContext, r io.Read } // Allocate the circular buffer eagerly. bufSize is capped at MaxBytesBuffer - // (32 MiB), so this allocation is bounded regardless of the user-supplied + // (5 MiB), so this allocation is bounded regardless of the user-supplied // count value. bufSize := MaxBytesBuffer if count < MaxBytesBuffer { - bufSize = int(count) // count < MaxBytesBuffer (32 MiB) fits safely in int + bufSize = int(count) // count < MaxBytesBuffer (5 MiB) fits safely in int } circ := make([]byte, bufSize) var totalWritten int64 From 9ad60823704c21e64cf03db3a7ff2eccf7826a35 Mon Sep 17 00:00:00 2001 From: Travis Thieman Date: Wed, 25 Mar 2026 16:19:32 -0400 Subject: [PATCH 2/2] style(tail): use builtin max() per modernize linter --- builtins/tail/tail.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/builtins/tail/tail.go b/builtins/tail/tail.go index 57512a6b..e5f50270 100644 --- a/builtins/tail/tail.go +++ b/builtins/tail/tail.go @@ -201,10 +201,7 @@ func registerFlags(fs *builtins.FlagSet) builtins.HandlerFunc { // Determine header printing using last-flag-wins: the highest pos among // quiet/silent (suppress) vs verbose (force) controls the outcome. - suppressPos := quietFlag.pos - if silentFlag.pos > suppressPos { - suppressPos = silentFlag.pos - } + suppressPos := max(quietFlag.pos, silentFlag.pos) printHeaders := len(files) > 1 if verboseFlag.pos > suppressPos { printHeaders = true