From 829febb47ac9b62662e55ae236df99b5e5f52b78 Mon Sep 17 00:00:00 2001 From: Philip Zeyliger Date: Thu, 22 Jan 2026 04:29:34 +0000 Subject: [PATCH] shelley: truncate long lines in large output summaries Prompt: In a new worktree, reset to origin/main after fetching, and look at https://github.com/boldsoftware/shelley/issues/37. I think 0bbdde76269d93170ba27ef88f1a3d034468ac75 is buggy, because if the output is binary or has really long lines, we end up putting a lot in the context. Fix it by either reverting that change or by presneting somethign much shorter if the lines are long. When bash output exceeds 50KB, we save to a file and show first/last lines. However, if those lines are very long (e.g., binary data or minified JS), we could still blow up the context. Fix by truncating each displayed line to 200 characters. Fixes https://github.com/boldsoftware/shelley/issues/37 Co-authored-by: Shelley --- claudetool/bash.go | 13 +++++++++++-- claudetool/bash_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/claudetool/bash.go b/claudetool/bash.go index f50d4cbdadda0e165c2700adf0fb664ea8b3a66d..78d3653ddf0c8a0c86cd61e8a88de1c62f059fd0 100644 --- a/claudetool/bash.go +++ b/claudetool/bash.go @@ -231,6 +231,7 @@ const ( largeOutputThreshold = 50 * 1024 // 50KB - threshold for saving to file firstLinesCount = 2 lastLinesCount = 5 + maxLineLength = 200 // truncate displayed lines to this length ) func (b *BashTool) makeBashCommand(ctx context.Context, command string, out io.Writer) *exec.Cmd { @@ -342,19 +343,27 @@ func formatForegroundBashOutput(out string) (string, error) { result.WriteString("First lines:\n") firstN := min(firstLinesCount, len(lines)) for i := 0; i < firstN; i++ { - result.WriteString(fmt.Sprintf("%5d: %s\n", i+1, lines[i])) + result.WriteString(fmt.Sprintf("%5d: %s\n", i+1, truncateLine(lines[i]))) } // Last N lines result.WriteString("\n...\n\nLast lines:\n") startIdx := max(0, len(lines)-lastLinesCount) for i := startIdx; i < len(lines); i++ { - result.WriteString(fmt.Sprintf("%5d: %s\n", i+1, lines[i])) + result.WriteString(fmt.Sprintf("%5d: %s\n", i+1, truncateLine(lines[i]))) } return result.String(), nil } +// truncateLine truncates a line to maxLineLength characters, appending "..." if truncated. +func truncateLine(line string) string { + if len(line) <= maxLineLength { + return line + } + return line[:maxLineLength] + "..." +} + func humanizeBytes(bytes int) string { switch { case bytes < 4*1024: diff --git a/claudetool/bash_test.go b/claudetool/bash_test.go index deb5d36c5850879ec9a89d333d6c7c519dbfe490..4346a1b5b990a0201ca95702b165ecd5c3c0b36a 100644 --- a/claudetool/bash_test.go +++ b/claudetool/bash_test.go @@ -645,6 +645,39 @@ func TestFormatForegroundBashOutput(t *testing.T) { t.Logf("Large binary-like output result:\n%s", result) }) + + // Test large output with very long lines (e.g., minified JS) + t.Run("Large Output With Long Lines", func(t *testing.T) { + // Generate output > 50KB with few very long lines + longLine := strings.Repeat("abcdefghij", 1000) // 10KB per line + lines := []string{longLine, longLine, longLine, longLine, longLine, longLine} + largeOutput := strings.Join(lines, "\n") + if len(largeOutput) < largeOutputThreshold { + t.Fatalf("Test setup error: output is only %d bytes, need > %d", len(largeOutput), largeOutputThreshold) + } + + result, err := formatForegroundBashOutput(largeOutput) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Result should be reasonable size (not blow up context) + if len(result) > 4096 { + t.Errorf("Expected truncated result < 4KB, got %d bytes:\n%s", len(result), result) + } + + // Should mention the file + if !strings.Contains(result, "saved to:") { + t.Errorf("Expected result to mention saved file, got:\n%s", result) + } + + // Lines should be truncated + if !strings.Contains(result, "...") { + t.Errorf("Expected truncated lines with '...', got:\n%s", result) + } + + t.Logf("Large output with long lines result:\n%s", result) + }) } // waitForFile waits for a file to exist and be non-empty or times out