Skip to content

Commit 222ee0e

Browse files
authored
[codex] Cap persisted Claude tool-result reads (#1293)
Closes #1292. Claude Code can persist oversized tool output in a sidecar under a session's `tool-results` directory. AgentsView previously loaded that sidecar with `os.ReadFile` and embedded the complete value into the reconstructed message. A multi-gigabyte sidecar therefore produced several multi-gigabyte allocations during parsing and JSON marshaling, eventually exceeded SQLite's value-size limit, left startup sync incomplete, and could trigger an operating-system SIGKILL during retry. This change reads persisted tool-result sidecars through a 16 MiB limit. Larger results retain their bounded prefix and gain an explicit truncation marker, keeping the archive searchable and the truncation visible without allowing one sidecar to exhaust process memory. The regression test uses a sparse oversized sidecar and verifies both the cap and marker. I also validated the change against the original failure shape: the affected session synced in under a second with roughly 202 MiB maximum RSS instead of reaching roughly 26 GiB, and a full archive resync completed successfully. Validation: `go test ./internal/parser -count=1`. Co-authored-by: Alex Kreidler <alexkreidler@users.noreply.github.com>
1 parent 9762387 commit 222ee0e

2 files changed

Lines changed: 42 additions & 4 deletions

File tree

internal/parser/claude.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"bytes"
77
"encoding/json"
88
"fmt"
9+
"io"
910
"log"
1011
"os"
1112
"path/filepath"
@@ -30,9 +31,10 @@ var (
3031
)
3132

3233
const (
33-
initialScanBufSize = 64 * 1024 // 64KB
34-
maxLineSize = 64 * 1024 * 1024 // 64MB
35-
forkThreshold = 3
34+
initialScanBufSize = 64 * 1024 // 64KB
35+
maxLineSize = 64 * 1024 * 1024 // 64MB
36+
maxPersistedToolResultSize = 16 * 1024 * 1024 // 16MB
37+
forkThreshold = 3
3638
)
3739

3840
// dagEntry holds metadata for a single JSONL entry participating
@@ -1944,10 +1946,19 @@ func readClaudePersistedToolResult(
19441946
if !pathWithinDir(cleanResult, dir) {
19451947
continue
19461948
}
1947-
b, err := os.ReadFile(cleanResult)
1949+
f, err := os.Open(cleanResult)
19481950
if err != nil {
19491951
return "", false
19501952
}
1953+
b, readErr := io.ReadAll(io.LimitReader(f, maxPersistedToolResultSize+1))
1954+
closeErr := f.Close()
1955+
if readErr != nil || closeErr != nil {
1956+
return "", false
1957+
}
1958+
if len(b) > maxPersistedToolResultSize {
1959+
b = b[:maxPersistedToolResultSize]
1960+
b = append(b, "\n\n[agentsview: persisted tool result truncated at 16 MiB]"...)
1961+
}
19511962
return string(b), true
19521963
}
19531964
return "", false

internal/parser/claude_parser_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1431,6 +1431,33 @@ func TestParseClaudeSession_ResolvesPersistedToolResultOutput(
14311431
assert.Equal(t, fullOutput, DecodeContent(got.ContentRaw))
14321432
}
14331433

1434+
func TestReadClaudePersistedToolResultTruncatesOversizedFile(
1435+
t *testing.T,
1436+
) {
1437+
t.Parallel()
1438+
1439+
dir := t.TempDir()
1440+
sessionDir := filepath.Join(dir, "project", "parent-session")
1441+
resultPath := filepath.Join(sessionDir, "tool-results", "oversized.txt")
1442+
require.NoError(t, os.MkdirAll(filepath.Dir(resultPath), 0o755))
1443+
require.NoError(t, os.WriteFile(resultPath, []byte("prefix"), 0o644))
1444+
require.NoError(t, os.Truncate(resultPath, maxPersistedToolResultSize+1))
1445+
1446+
sessionPath := filepath.Join(dir, "project", "parent-session.jsonl")
1447+
got, ok := readClaudePersistedToolResult(sessionPath, resultPath)
1448+
require.True(t, ok)
1449+
assert.True(t, strings.HasPrefix(got, "prefix"))
1450+
assert.Equal(
1451+
t,
1452+
maxPersistedToolResultSize+len("\n\n[agentsview: persisted tool result truncated at 16 MiB]"),
1453+
len(got),
1454+
)
1455+
assert.True(t, strings.HasSuffix(
1456+
got,
1457+
"[agentsview: persisted tool result truncated at 16 MiB]",
1458+
))
1459+
}
1460+
14341461
func TestParseClaudeSession_PersistedToolResultDoesNotOverwriteSiblings(
14351462
t *testing.T,
14361463
) {

0 commit comments

Comments
 (0)