Skip to content

fix(ai): bound the HTTP 400 request-dump directory - #3319

Merged
Yeachan-Heo merged 1 commit into
Yeachan-Heo:devfrom
10kH:fix/http400-dump-retention
Jul 29, 2026
Merged

fix(ai): bound the HTTP 400 request-dump directory#3319
Yeachan-Heo merged 1 commit into
Yeachan-Heo:devfrom
10kH:fix/http400-dump-retention

Conversation

@10kH

@10kH 10kH commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Supersedes #3317, closed as mergeable: CONFLICTING under the external-PR policy. The verdict there was explicit that this was not a rejection of the approach:

Technical content review of the retention diff (http-inspector.ts) is sound: bounded pruning under a fixed, contained directory, correct lexical-timestamp ordering for the writer's own filename format, best-effort error handling on both readdir and rm that structurally cannot throw and therefore cannot mask the original HTTP 400 failure, no arbitrary-file-deletion risk, and non-tautological retention-boundary tests. CI is terminal and green (15 pass, 0 fail) at this exact head.

Requested changes: rebase onto current dev and resolve the conflict, then resubmit.

Rebased onto dev at b853f15d. Zero conflicts — the diff touches http-inspector.ts and its new test only. Content is byte-identical to the reviewed head.


Measured on a developer machine

~/.gjc                          7.3 GB
~/.gjc/logs                     7.0 GB
~/.gjc/logs/http-400-requests   7.0 GB   27,249 files, 264 KB average

That one directory is 96% of everything the agent stores under $HOME.

Cause

appendRawHttpRequestDumpFor400 writes one file per HTTP 400 containing the full sanitized request body — and nothing ever removes one. No cap, no rotation, no sweep anywhere in the tree; http-400-requests appears exactly once in packages/*/src, at the write site.

The rotating application log right next to it is already bounded (maxSize: 10m, maxFiles: 5, gzipped) and occupies 12 KB per archived day. The policy exists; these diagnostics never adopted it.

Fix

Retain the newest 50 dumps. File names are ${Date.now()}-${hash}.json, so a lexical sort is chronological for this writer.

Pruning runs after each write and swallows every error — a diagnostics path must never turn a request failure into a second failure, which is exactly why the write itself already catches.

Tests

http-inspector-dump-retention.test.ts (4 cases): holds everything under the cap, trims to the cap while keeping the newest, leaves non-dump files alone, and treats a missing directory as a no-op rather than throwing.

Proof-first: disabling the trim fails the trim case.

Verification

Re-run after the rebase: bun run check in packages/ai exit 0, new suite 4 pass / 0 fail.

@Yeachan-Heo Yeachan-Heo left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signed exact-head verdict — PR #3319 @ 54305360580e8edc57bc974426c07cfe97507a3e

Disposition: APPROVE

Clean successor to conflict-closed #3317 for HTTP 400 dump retention. Independent review: bounds ~/.gjc/logs/http-400-requests to the newest 50 dumps (unbounded growth measured at 27,249 files / 7.0 GB, 96% of everything under ~/.gjc), matching the retention policy the rotating application log already uses. Pruning runs after each write, is best-effort (swallows errors so a diagnostics path can't cause a second failure), and file-name lexical sort is chronological for this writer. Covered by http-inspector-dump-retention.test.ts (under-cap no-op, trims to cap keeping newest, non-dump files untouched, missing-dir no-op). CI green, MERGEABLE, head is clean.

No blocker found. Not merged (dev is red).

appendRawHttpRequestDumpFor400 writes one file per HTTP 400 containing the full
sanitized request body, and nothing ever removed one. There is no cap, no
rotation, and no sweep anywhere in the tree.

Measured on a developer machine:

  ~/.gjc                       7.3 GB
  ~/.gjc/logs                  7.0 GB
  ~/.gjc/logs/http-400-requests  7.0 GB across 27,249 files, 264 KB average

That single directory was 96% of everything the agent stores under $HOME. The
rotating application log next to it is bounded (maxSize 10m, maxFiles 5,
gzipped) and occupies 12 KB per archived day, so the policy already exists —
these diagnostics simply never adopted it.

Retain the newest 50 dumps. File names are `${Date.now()}-${hash}.json`, so a
lexical sort is chronological for this writer. Pruning runs after each write and
swallows every error: a diagnostics path must never turn a request failure into
a second failure, which is why the write itself already catches.

Regression: retention holds at the cap, trims to it while keeping the newest,
leaves non-dump files alone, and treats a missing directory as a no-op rather
than throwing. Disabling the trim fails the trim case.
@10kH
10kH force-pushed the fix/http400-dump-retention branch from 5430536 to 20afc45 Compare July 29, 2026 00:02

@Yeachan-Heo Yeachan-Heo left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signed exact-head verdict — PR #3319 @ 20afc455745c939550b3fbb52288bc2a52cbf460

Disposition: APPROVE (MERGE_READY)

Prior approval was stale-head (54305360); this is a fresh review against the current exact head.

Independent review

Bounds ~/.gjc/logs/http-400-requests to the newest 50 dumps. Each dump carries the full sanitized request body (avg 264 KB), and nothing ever removed one — measured growth to 27,249 files / 7.0 GB (96% of everything under ~/.gjc).

Correctness checks

  • Retention logic is correct: pruneHttpRequestDumps reads dir, filters .json, sorts lexically, and if length > MAX_RETAINED_DUMPS removes slice(0, length - MAX_RETAINED_DUMPS) — the oldest. File names are ${Date.now()}-${hash}.json, so lexical sort = chronological order. ✓
  • Pruning site is correct: called after every Bun.write in appendRawHttpRequestDumpFor400, so the cap is enforced on every dump, not just on startup. ✓
  • Best-effort contract: readdir and every rm are .catch(() => ...) — diagnostics never turn a request failure into a second failure. Missing dir returns 0, no throw. ✓
  • Non-dump files preserved: filter is name.endsWith(".json") only — a README.txt or unrelated file survives. ✓
  • Extracted helpers are clean: httpRequestDumpDir() and pruneHttpRequestDumps() are exported, single-responsibility, default-arg for testability. ✓

Adversarial edge cases probed

  • Clock skew / non-monotonic Date.now(): if timestamps aren't monotonic, lexical order may not be perfectly chronological, but this only affects which dumps are kept — the cap is still enforced. Acceptable for best-effort diagnostics.
  • Concurrent writers: two processes pruning simultaneously could each compute the same removal set and race on rm — but force: true makes the loser a no-op. ✓

Test verification

  • 4 tests: under-cap no-op, over-cap trims to 50 keeping newest, unrelated file preserved, missing-dir no-op. All externally observable contract behavior. ✓
  • CI: all checks pass (run 30409891893), including test:http-inspector-dump-retention.test.ts.

Verdict

No defects found. No rebase needed. MERGE_READY — merge blocked only on dev CI green.

Yeachan-Heo added a commit that referenced this pull request Jul 29, 2026
…#3318)

Batch retention #3318/#3319/#3320: merge #3318 (exact-head approved @ ccd5991). Dev CI green (run 30413152675). Sequential batch, dev refreshed after each.
@Yeachan-Heo
Yeachan-Heo merged commit 07cbce0 into Yeachan-Heo:dev Jul 29, 2026
21 checks passed
Yeachan-Heo added a commit that referenced this pull request Jul 29, 2026
…3320)

Retention batch #3318/#3319/#3320: merge #3320 (exact-head approved @ b2d870d, rebased from 2703bc5 — content-identical). Dev CI green (run 30418204640 on f3c32e2). Sequential batch complete.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants