Skip to content

fix(claude): bump claude-code-headless to the rotating proxy-events log (#1273) - #1376

Merged
Juliusolsson05 merged 12 commits into
mainfrom
fix/proxy-events-retention
Sep 27, 2026
Merged

Juliusolsson05 merged 12 commits into
mainfrom
fix/proxy-events-retention

Conversation

@Juliusolsson05

@Juliusolsson05 Juliusolsson05 commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

Refs #1273. With the default rotation setting, the events file is now bounded. A live run directory still has one unbounded file, sslkeylog.log, filed as #1380, so this PR does not close #1273.

Problem

#1284 / claude-code-headless#62 capped request bodies per events file, but two items stayed open:

  • a live Claude session's proxy-events.jsonl still grew for the life of the session (responses, stream chunks, body-less requests);
  • debug retention never reclaims a live run.

Change

This PR bumps packages/claude-code-headless to f52fc82, which is claude-code-headless#64 (merged after three reviews, a verification pass and a final round 3):

  • Rotation: the mitmproxy addon rotates the events file at 512 MiB (PROXY_EVENTS_ROTATE_BYTES), keeping exactly one previous generation (proxy-events.1.jsonl). A live run holds at most ~2 × 512 MiB. The bound holds under the default setting only: PROXY_EVENTS_ROTATE_BYTES=0 (the forensic override) disables rotation, and the events file then grows without bound.
  • Generation headers: every generation created by a rotation starts with a header line, {"kind":"generation","generation":n}, created atomically. Generation 0 has no header: the run's first event creates it. The tail treats a missing header as generation 0 and uses the headers to know exactly which generation it holds.
  • The tail (EventsFileTail) holds a FileHandle on its generation, finishes it on rotation, reads an unseen .1 in order, and reports any generation deleted unread as lostGenerations. ProxyServer emits that as a transport-gap event.
    • Package contract (inside claude-code-headless): every event exactly once, or an explicit gap event. It does not extend into the app; see below.
    • Known limitation, stated in feat(command-palette): add command descriptions panel #64: a crash between the rename and the next header is repaired when the addon restarts. If it is never restarted, that generation's unread events are lost without a gap.
  • Addon hardening: a write never raises out of a mitmproxy hook; a crashed partial line is terminated at startup; an interrupted rotation is finished at startup.

On the app side:

  • Bundle reader: W1's rotation-safe reader (fix(codex): bound the proxy mirror on disk and take it off the main process's synchronous path #1332) is already on main; it reads across .1 and ships raw bytes, where the header is one more JSON line.
  • Retention: debugRetention detects a run by proxy-events.jsonl, which the addon now recreates immediately after a rotation.
  • No lockfile resync: the app consumes claude-code-headless from source (tsconfig and Vite aliases), not as a file: dependency. Its runtime deps (chokidar, @xterm/headless) are root deps. The bump changes only the package's devDependencies, and npm install --package-lock-only leaves package-lock.json unchanged.
  • Gap signal in the app: diagnostic only (review a/b/c; manager decision, steering q87). ClaudeSession now subscribes to the proxy's transport-gap as well as event, and SessionManager records a claude.proxy_transport_gap incident for the session.
  • Retention (review a): a run holding only proxy-events.1.jsonl (a rotation whose next file was never created) is now counted, so it cannot accumulate invisibly.
  • Disk bound, stated honestly:
  • Plan: docs/plans/2026-09-26-proxy-events-retention.md.

Verification

  • Package: the tests are in feat(command-palette): add command descriptions panel #64 and green in its CI: a real addon plus real tailer, rotation before every await point, kill-restart, the header chain, and the gap report.
  • This head: npx tsc -b prints 0 lines.
  • New app tests:
    • claudeSession.suspension.test.ts: proxy event forwarding, the gap re-emit, and detach;
    • sessionManager.proxyGap.test.ts: the incident and the re-emit;
    • debugRetention.test.ts: a .1-only run is counted.
  • Mutations killed: the session ignoring transport-gap, the session ignoring event, the manager ignoring the gap, retention ignoring .1.
  • At 8996d9d3, src/providers/claude, proxyEventsReader and debugRetention passed 245/245.

🤖 Generated with Claude Code

Juliusolsson05 and others added 6 commits September 26, 2026 17:32
…esidual)

Refs #1273

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Claude proxy addon now rotates proxy-events.jsonl into
proxy-events.1.jsonl at 512 MiB. Right after a rotation the live file
holds a few lines, so a bundle would miss the recent traffic a report is
about. When the live file is under the 5 MiB cap, the rest is filled
from the end of the previous generation, cut at a line boundary.

Refs #1273

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tsReader (#1332)

Steering q54: W1's #1332 owns one provider-neutral rotation-safe
reader. This branch will merge main after #1332 lands and keep only
the Claude-specific wiring.

Refs #1273

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…wnership

Refs #1273

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…og (#1273)

claude-code-headless#64 (merged, f52fc82): the addon rotates
proxy-events.jsonl at 512 MiB keeping one previous generation, each
generation stamped with a header; the app's tail holds a FileHandle on
the generation it reads and reports generations deleted unread as a
`transport-gap`. A live Claude run is now bounded instead of growing for
the life of the session.

No lockfile resync: the app consumes the package from source and its
runtime deps are root deps; the bump changes only the package's
devDependencies. The plan records the header design, the accepted crash
limitation, and that #1332 (W1) owns the rotation-safe bundle reader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Juliusolsson05 Juliusolsson05 added provider:claude Affects Claude Code integration type:bug Something works wrong class:C6-unbounded Unbounded growth sev:P2 Real bug with a workaround sev:P1 Core flow broken or badly degraded and removed sev:P2 Real bug with a workaround labels Sep 27, 2026
… retention (review)

Review of #1376 (a, b, c):
- ClaudeSession listened to the proxy's `event` channel only, so the
  `transport-gap` claude-code-headless#64 emits for generations deleted
  unread never left the package. attachProxyServer subscribes both and
  re-emits `proxy-transport-gap`; SessionManager records a
  claude.proxy_transport_gap incident and re-emits it with the session
  id. The normal event wiring is now pinned (deleting it passed every
  test before).
- collectProxyRunDirs counts a run holding only proxy-events.1.jsonl.
- sslkeylog.log is a separate unbounded file (and TLS secrets on disk):
  filed as #1380; the PR narrows to Refs #1273.

Mutations killed: session ignoring transport-gap, session ignoring
event, manager ignoring the gap, retention ignoring `.1`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Juliusolsson05

Juliusolsson05 commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner Author

Review disposition (round 1: a, b, c codex at 8996d9d3)

# Finding Verdict Disposition
a1 / b1 / c2 The app drops transport-gap; the "exactly once, or an explicit gap" contract stops at the package boundary. Even the event wiring was untested. valid Fixed in 0de54245: ClaudeSession subscribes to both channels, and SessionManager records a claude.proxy_transport_gap incident and re-emits it with the session id. Pinned at both layers; all four wiring mutants are red.
a2 A run holding only .1 is invisible to retention valid Fixed: collectProxyRunDirs counts it; test plus mutant.
c1 sslkeylog.log in the same run directory grows without bound (and holds TLS secrets), so Fixes #1273 overclaimed valid Filed as #1380 (outside this bump: it needs a package change). The PR is narrowed to Refs #1273, and the body states the real disk bound.
c3 The sidecar bound omitted base64 expansion valid Fixed in the plan and body: ~21.3 MiB on disk, twice that briefly during the replace.

Verification pass: a/b/c at 0de54245, queued under the reviewer cap.

Steering q87: the gap is diagnostic-only in the app: an always-on incident plus a manager re-emit. Nothing renders a missing-span marker yet, so the user-visible gap is filed as #1381. The body and plan say exactly that (33c046cf).

Focused verification (c, steering q87/q89) — the central diagnostic claim was found accurate. Three minors:

  • Body said every generation has a header. Corrected: only generations created by a rotation carry one; generation 0 has none.
  • Body stated the disk bound as absolute. Corrected: it holds under the default PROXY_EVENTS_ROTATE_BYTES, and 0 (the forensic override) disables rotation.
  • The gap subscription's ownership guard was unpinned. Pinned in the new head: a replaced session's late gap is not recorded against its successor; the mutant is red.

Round 2 (verification, a/b/c codex at 49ee5985):

  • a: MERGE-READY. One surviving mutation: stripping {"kind":"generation"} lines from bundled JSONL in proxyEventsReader.ts passes. Accepted, not a defect. The bundle ships raw bytes on purpose; whether a header line appears in a diagnostic bundle changes no behaviour.
  • b: MERGE-READY. No survivors.
  • c: FIX-BEFORE-MERGE, minor docs/format only. No survivors (all four replayed mutants are red).
    • The plan's Decisions/Tests/Delivery sections still stated the pre-review scope. Fixed in f04837d2: the events file is bounded, the run directory is not (claude proxy: sslkeylog.log grows unbounded in every live run dir (and holds TLS secrets on disk) #1380), and the app wiring and its tests are listed.
    • Extra EOF blank lines in three test files. Fixed in f04837d2; git diff --check origin/main...HEAD is clean.
    • f04837d2 touches no source: one plan doc and three trailing newlines. Both review rounds are spent, so this goes to manager-verify-c.

Manager-verify-c (temp/review-1376/manager-verify-c.md) confirms f04837d2 by inspection: plan docs only, and the src changes are trailing blank lines. merge-gate --member --dry: GATE PASS (f04837d2, 0 behind, checks green). READY.

After batch F (69adefbd): merged origin/main because of a real conflict. The only conflicting path was the claude-code-headless gitlink, and this branch's f52fc82 contains main's 728cb494, so it is kept. The merged tree gives npx tsc -b 0 lines, the focused suites pass 17/17, and CI is green. merge-gate --member --dry: GATE PASS (69adefbd, 0 behind). READY.

Juliusolsson05 and others added 4 commits September 27, 2026 01:02
… marker is #1381 (q87)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…inst its successor (focused review)

Focused review of #1376 (c): the ownership guard on the gap subscription
was unpinned (deleting it passed). Also corrects the plan: generation 0
has no header, and PROXY_EVENTS_ROTATE_BYTES=0 disables the bound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lank lines

Reviewer C (round 2): Decisions/Tests/Delivery still stated the pre-review
scope (a fully bounded run, no app tests, pointer-only). They now match the
review appendix: the events file is bounded, the run directory is not
(#1380), and the app-side wiring and its tests are listed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Juliusolsson05
Juliusolsson05 merged commit f831513 into main Sep 27, 2026
2 checks passed
Juliusolsson05 added a commit that referenced this pull request Sep 27, 2026
@Juliusolsson05
Juliusolsson05 deleted the fix/proxy-events-retention branch September 27, 2026 13:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

class:C6-unbounded Unbounded growth provider:claude Affects Claude Code integration sev:P1 Core flow broken or badly degraded type:bug Something works wrong

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Claude proxy-events.jsonl grows to 2+ GB per session and cannot be pruned while live

1 participant