Skip to content

Commit 2583928

Browse files
Yuxin-Qiaosteipete
andauthored
Bound Codex session metadata reads (#2050)
* Bound Codex session metadata reads * fix: bound Codex metadata pre-scan memory --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
1 parent eae1910 commit 2583928

4 files changed

Lines changed: 95 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
### Fixed
99
- Claude: prevent CodexBar's passive CLI probes from starting background Claude Code updates, avoiding repeated partial downloads when a probe exits before an update completes. Thanks @PG2047!
10+
- Codex cost history: bound malformed session-metadata lines and release read chunks promptly, preventing metadata pre-scans from retaining memory in proportion to oversized JSONL records. Thanks @Yuxin-Qiao!
1011

1112
## 0.42.0 — 2026-07-11
1213

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Generated by Scripts/regenerate-codex-parser-hash.sh. Do not edit by hand.
22

33
enum CodexParserHash {
4-
static let value = "ed9cd73bd15c2398"
4+
static let value = "4cf70ccd355c42ac"
55
}

Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1315,13 +1315,15 @@ enum CostUsageScanner {
13151315
}
13161316
}
13171317

1318-
private static func parseCodexSessionIdentifier(
1318+
static func parseCodexSessionIdentifier(
13191319
fileURL: URL,
13201320
checkCancellation: CancellationCheck? = nil) throws -> String?
13211321
{
13221322
try self.parseCodexSessionMetadata(fileURL: fileURL, checkCancellation: checkCancellation)?.sessionId
13231323
}
13241324

1325+
static let codexSessionMetadataMaxLineBytes = 256 * 1024
1326+
13251327
private static func parseCodexSessionMetadata(
13261328
fileURL: URL,
13271329
checkCancellation: CancellationCheck? = nil) throws -> CodexSessionMetadata?
@@ -1338,7 +1340,7 @@ enum CostUsageScanner {
13381340
defer { try? handle.close() }
13391341

13401342
var buffer = Data()
1341-
let newline = Data([0x0A])
1343+
var discardingOversizedLine = false
13421344

13431345
func parseSessionMetadata(from lineData: Data) -> CodexSessionMetadata? {
13441346
guard !lineData.isEmpty else { return nil }
@@ -1365,16 +1367,47 @@ enum CostUsageScanner {
13651367
}
13661368

13671369
do {
1368-
while let chunk = try handle.read(upToCount: 64 * 1024), !chunk.isEmpty {
1369-
try checkCancellation?()
1370-
buffer.append(chunk)
1371-
while let newlineRange = buffer.range(of: newline) {
1372-
let lineData = buffer.subdata(in: 0..<newlineRange.lowerBound)
1373-
buffer.removeSubrange(0..<newlineRange.upperBound)
1374-
if let metadata = parseSessionMetadata(from: lineData) {
1375-
return metadata
1370+
var matchedMetadata: CodexSessionMetadata?
1371+
while true {
1372+
let reachedEOF = try autoreleasepool { () throws -> Bool in
1373+
guard let chunk = try handle.read(upToCount: 64 * 1024), !chunk.isEmpty else {
1374+
return true
13761375
}
1376+
try checkCancellation?()
1377+
1378+
var segmentStart = chunk.startIndex
1379+
while segmentStart < chunk.endIndex {
1380+
let newlineIndex = chunk[segmentStart...].firstIndex(of: 0x0A)
1381+
let segmentEnd = newlineIndex ?? chunk.endIndex
1382+
1383+
if !discardingOversizedLine {
1384+
let segmentCount = chunk.distance(from: segmentStart, to: segmentEnd)
1385+
let remainingBytes = Self.codexSessionMetadataMaxLineBytes - buffer.count
1386+
if segmentCount <= remainingBytes {
1387+
buffer.append(contentsOf: chunk[segmentStart..<segmentEnd])
1388+
} else {
1389+
// Release the retained prefix immediately. The buffer never exceeds the line limit.
1390+
buffer.removeAll(keepingCapacity: false)
1391+
discardingOversizedLine = true
1392+
}
1393+
}
1394+
1395+
guard let newlineIndex else { break }
1396+
if !discardingOversizedLine,
1397+
let metadata = parseSessionMetadata(from: buffer)
1398+
{
1399+
matchedMetadata = metadata
1400+
break
1401+
}
1402+
buffer.removeAll(keepingCapacity: true)
1403+
discardingOversizedLine = false
1404+
segmentStart = chunk.index(after: newlineIndex)
1405+
}
1406+
1407+
return false
13771408
}
1409+
if let matchedMetadata { return matchedMetadata }
1410+
if reachedEOF { break }
13781411
}
13791412
} catch is CancellationError {
13801413
throw CancellationError()
@@ -1385,7 +1418,9 @@ enum CostUsageScanner {
13851418
return nil
13861419
}
13871420

1388-
if let metadata = parseSessionMetadata(from: buffer) {
1421+
if !discardingOversizedLine,
1422+
let metadata = parseSessionMetadata(from: buffer)
1423+
{
13891424
return metadata
13901425
}
13911426
return nil

Tests/CodexBarTests/CostUsageScannerTests.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,53 @@ import Testing
33
@testable import CodexBarCore
44

55
struct CostUsageScannerTests {
6+
@Test
7+
func `codex session metadata skips an oversized line without retaining it`() throws {
8+
let env = try CostUsageTestEnvironment()
9+
defer { env.cleanup() }
10+
11+
let fileURL = env.root.appendingPathComponent("oversized-session-meta.jsonl")
12+
FileManager.default.createFile(atPath: fileURL.path, contents: nil)
13+
let handle = try FileHandle(forWritingTo: fileURL)
14+
defer { try? handle.close() }
15+
16+
let oversizedPrefix = "{\"type\":\"session_meta\",\"payload\":{\"id\":\"too-large\",\"padding\":\""
17+
try handle.write(contentsOf: Data(oversizedPrefix.utf8))
18+
let chunk = Data(repeating: 0x78, count: 64 * 1024)
19+
for _ in 0..<128 {
20+
try handle.write(contentsOf: chunk)
21+
}
22+
let expectedLine = #"{"type":"session_meta","payload":{"id":"expected-session"}}"#
23+
try handle.write(contentsOf: Data((#""}}"# + "\n" + expectedLine).utf8))
24+
try handle.close()
25+
26+
let sessionID = try CostUsageScanner.parseCodexSessionIdentifier(fileURL: fileURL)
27+
#expect(sessionID == "expected-session")
28+
}
29+
30+
@Test
31+
func `codex session metadata accepts a line exactly at the byte limit`() throws {
32+
let env = try CostUsageTestEnvironment()
33+
defer { env.cleanup() }
34+
35+
let prefix = "{\"type\":\"session_meta\",\"payload\":{\"id\":\"limit-session\",\"padding\":\""
36+
let suffix = "\"}}"
37+
let paddingCount = CostUsageScanner.codexSessionMetadataMaxLineBytes
38+
- prefix.utf8.count
39+
- suffix.utf8.count
40+
var line = Data(prefix.utf8)
41+
line.append(Data(repeating: 0x78, count: paddingCount))
42+
line.append(contentsOf: suffix.utf8)
43+
#expect(line.count == CostUsageScanner.codexSessionMetadataMaxLineBytes)
44+
45+
let fileURL = env.root.appendingPathComponent("max-size-session-meta.jsonl")
46+
try line.write(to: fileURL)
47+
#expect(try (JSONSerialization.jsonObject(with: line)) is [String: Any])
48+
49+
let sessionID = try CostUsageScanner.parseCodexSessionIdentifier(fileURL: fileURL)
50+
#expect(sessionID == "limit-session")
51+
}
52+
653
@Test
754
func `codex file metadata detects append truncation and replacement`() throws {
855
let root = FileManager.default.temporaryDirectory

0 commit comments

Comments
 (0)