Skip to content

fix(proxy): bound Replay disconnect memory retention (#1408) - #1414

Merged
AptS-1547 merged 4 commits into
devfrom
fix/issue-1408-replay-oom
Aug 11, 2026
Merged

fix(proxy): bound Replay disconnect memory retention (#1408)#1414
AptS-1547 merged 4 commits into
devfrom
fix/issue-1408-replay-oom

Conversation

@AptS-1547

@AptS-1547 AptS-1547 commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Remove the in-process full-stream body retained by Replay owners and cap the Redis write-behind backlog at 1 MiB.
  • Fall back from the 300-second Replay drain window to the normal 60-second client-abort window as soon as the spool becomes inactive.
  • Bound persisted session response bodies to 5 MiB by default, including before/after detail snapshots.
  • Add a repository-owned load fixture for hanging Responses SSE streams, confirmed client disconnects, memory/resource sampling, and repeatable wave tests.

Root Cause

On v0.9.2, a Replay owner retained the complete upstream stream in ReplaySpool.parts while also queuing Redis writes. After the downstream client disconnected, an active spool selected the 300-second detached drain window. Slow or blocked persistence therefore kept large external buffers, upstream sockets, and async tasks alive for substantially longer than the ordinary 60-second drain.

The fix removes the redundant process-local body, bounds queued writes, releases queued batches on abort/disable, and makes the response handler observe when Replay becomes inactive so it can restore the shorter drain deadline.

Related Work

Review Follow-up

  • Count every input byte before streaming UTF-8 decode so split multibyte sequences cannot under-report the write backlog.
  • Apply the same 1 MiB queued-write reservation to completion tail batches and wait for fenced cleanup after overflow.
  • Reject interrupted mock stats responses and remove a newly created mock container when readiness fails.
  • Keep the issue reference in the investigation conclusion as literal Markdown text.
  • Restrict mock existence checks to containers and clean newly created containers on SIGINT, SIGTERM, timeout, or early command failure.
  • Isolate fixture configuration from parent CCH_MOCK_* values and reset stateful Replay store mocks between tests.
  • Bound each mock readiness probe to a 2-second connect timeout and 5-second total timeout.

Verification

  • bunx vitest run tests/unit/proxy/issue-1408-load-fixture.test.ts tests/unit/proxy/replay-spool.test.ts tests/unit/proxy/response-handler-stream-terminal.test.ts --configLoader bundle: 90 tests passed.
  • Load-fixture contract and boundary coverage: 18 tests passed, including invalid configuration, inherited-environment isolation, container-only inspection, bounded stalled health probes, signal and early-failure cleanup, request-body limits, route boundaries, interrupted stats responses, process shutdown, missing credentials, and driver parameter bounds.
  • Local /v1/responses smoke: mock receipt confirmed, client disconnected after 252 ms, the stream hit the 60-second drain timeout, and both stream tasks cleaned up to remainingTasks: 0.
  • bun run build: passed.
  • bun run lint: passed.
  • bun run lint:fix: passed with no additional changes.
  • bun run typecheck: passed.
  • git diff --check: passed.
  • bun run test: completed with one unrelated existing failure at src/components/ui/__tests__/language-switcher.test.tsx:153; the component, test, package manifest, and lockfile have no diff in this branch.

Operational Notes

  • The load fixture reads its API key only from CCH_API_KEY or CCH_API_KEY_FILE.
  • Per-request mock payloads and request bodies are bounded to prevent an accidental unbounded local load.
  • The root-cause report and CHANGELOG.md document the final behavior and evidence boundary.
  • perf(session): 去重 Redis 中重复存储的 response body #1415 tracks physical deduplication of legacy, before, and after response bodies plus a 5 MiB Redis/RDB load gate.

Closes #1408


Description enhanced by Claude AI

Greptile Summary

The PR bounds memory and Redis retention for disconnected Replay streams while preserving replay persistence and streaming finalization.

  • Removes ReplaySpool’s full in-process response copy and caps queued Redis writes at 1 MiB.
  • Downgrades detached drain time to the standard 60-second window when Replay becomes inactive.
  • Adds configurable size limits for persisted session response bodies and before/after snapshots.
  • Adds focused unit and load-fixture coverage for disconnect, cleanup, persistence, and boundary behavior.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/app/v1/_lib/proxy/replay/replay-spool.ts Removes full-stream local retention, bounds queued writes, reconstructs durable payload from fenced Redis chunks, and signals spool inactivity.
src/app/v1/_lib/proxy/response-handler.ts Recalculates a detached client-abort drain deadline when Replay loses value, preserving the original disconnect timestamp.
src/lib/session-manager.ts Applies UTF-8 byte limits to legacy and phased session response-body storage while retaining snapshot metadata.
src/lib/config/env.schema.ts Defines and validates the configurable session response-body storage limit.

Sequence Diagram

sequenceDiagram
  participant Client
  participant Handler as ResponseHandler
  participant Spool as ReplaySpool
  participant Redis
  participant PG
  Client->>Handler: Start streaming request
  Handler->>Spool: Create owner spool with onInactive
  Spool->>Redis: Fenced write-behind chunks
  Client--xHandler: Disconnect
  Handler->>Handler: Start bounded detached drain
  alt Replay remains active
    Handler->>Handler: Retain Replay drain window
    Spool->>Redis: Flush terminal batch
    Spool->>Redis: Read ordered chunks
    Spool->>PG: Persist durable replay
  else Replay becomes inactive
    Spool-->>Handler: onInactive()
    Handler->>Handler: Cap deadline at 60s from disconnect
    Handler--xHandler: Abort upstream at deadline
  end
Loading

Reviews (5): Last reviewed commit: "fix(session): default stored responses t..." | Re-trigger Greptile

Context used:

@AptS-1547 AptS-1547 added bug Something isn't working oncall Critical blocking issue requiring immediate oncall attention labels Aug 11, 2026
@AptS-1547 AptS-1547 self-assigned this Aug 11, 2026
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

本次变更限制 ReplaySpool 的本地内存和 Redis 写入积压。失效 Replay 的客户端断线排水窗口缩短为 60 秒。会话响应体新增默认 5 MiB 的可配置 Redis 存储上限。新增 Issue #1408 的负载测试夹具、单元测试和调查记录。

Changes

Replay 与会话响应体内存控制

Layer / File(s) Summary
会话响应体大小限制
.env.example, src/lib/config/env.schema.ts, src/lib/session-manager.ts, tests/unit/lib/*session-response-body*, tests/unit/lib/session-manager-redaction.test.ts, src/lib/session-manager-detail-snapshots.test.ts
新增 SESSION_RESPONSE_BODY_MAX_BYTES,范围为 64 KiB 至 64 MiB,默认值为 5 MiB。普通响应和阶段快照按 UTF-8 字节数限制 Redis 正文写入。
ReplaySpool 积压与持久化生命周期
src/app/v1/_lib/proxy/replay/replay-spool.ts, tests/unit/proxy/replay-spool.test.ts
移除本地 parts 正文缓存。新增 1 MiB 写入积压上限。完成时从 Redis chunks 重建 payload,并串行执行 PG 持久化。
失效 Replay 的断线排水窗口
src/app/v1/_lib/proxy/response-handler.ts, tests/unit/proxy/response-handler-stream-terminal.test.ts
统一管理客户端断线排水计时。ReplaySpool 非活跃后,排水窗口改为 60 秒;Replay 保持活跃时继续使用配置的 300 秒窗口。
OOM 负载夹具与调查记录
tests/load/issue-1408-replay-oom/*, tests/unit/proxy/issue-1408-load-fixture.test.ts, docs/troubleshooting/issue-1408-replay-oom.md, CHANGELOG.md
新增 Mock upstream、断连波次驱动、内存探针、容器采样和 Docker 启动脚本。新增夹具行为和参数校验测试,并记录 Issue #1408 调查结果。

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

  • Issue ding113/claude-code-hub#1415:本次变更新增单份 response body 的 Redis 大小限制,与该 issue 计划的跨 key 正文去重直接相关。

Possibly related PRs

Suggested reviewers: ding113

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed 标题准确概括了 PR 的主要变更,即限制 Replay 断线后的内存保留。
Description check ✅ Passed 描述详细说明了 Replay 内存保留问题、修复措施、测试覆盖和验证结果,与变更内容一致。
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-1408-replay-oom

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/v1/_lib/proxy/replay/replay-spool.ts (1)

98-101: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

按所有输入字节计算 pendingBytes

当 UTF-8 字符跨多个 chunk 时,decoder.decode() 可以返回空字符串。当前代码会在 Line 99 提前返回,因此不会计入这些 chunk 的字节数。后续 chunk 解码出文本时,queuedWriteBytes 只包含最后一个片段的大小。

这会低估 Redis 写入积压,并允许实际写入数据超过 1 MiB 上限。请在调用 decoder.decode() 前累计 chunk.byteLength。请增加一个跨 chunk 的多字节 UTF-8 测试。

建议修改
       if (this.totalBytes > env.REPLAY_MAX_PAYLOAD_BYTES) {
         this.disable("payload_too_large");
         return;
       }
+      this.pendingBytes += chunk.byteLength;
       const text = this.decoder.decode(chunk, { stream: true });
       if (text.length === 0) return;
       this.pending.push(text);
-      this.pendingBytes += chunk.byteLength;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/v1/_lib/proxy/replay/replay-spool.ts` around lines 98 - 101, 在负责处理
chunk 的方法中,先将每个输入 chunk 的 chunk.byteLength 累加到 pendingBytes,再调用
decoder.decode(),移除空文本时提前返回导致字节未计数的问题;仅在解码产生文本时追加到 pending。新增跨多个 chunk 的多字节
UTF-8 测试,验证 queuedWriteBytes/pendingBytes 按所有输入字节累计并正确遵守 1 MiB 上限。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/troubleshooting/issue-1408-replay-oom.md`:
- Around line 283-285: 在 issue-1408 调查结论段落中修改行首的 “#1408”,避免其被 Markdown
解析为标题;优先将该文本接回上一行,或对井号进行转义,同时保持原有结论内容不变。

In `@src/app/v1/_lib/proxy/replay/replay-spool.ts`:
- Around line 262-266: 在 completeAfterBilling 中不要直接增加 queuedWriteBytes;复用
enqueueFlush 的 MAX_QUEUED_WRITE_BYTES 预留检查。预留失败时清空尾批、禁用
spool,并等待现有清理链完成;同时增加覆盖“阻塞写入加尾批”超过上限的测试。

In `@tests/load/issue-1408-replay-oom/drive-disconnect-waves.cjs`:
- Around line 86-103: Update the request promise in waitForMock to also reject
when the response emits "aborted" or "error" after the response has started.
Attach these handlers to the response alongside the existing "data" and "end"
listeners, preserving the current status and JSON parsing behavior for normally
completed responses.

In `@tests/load/issue-1408-replay-oom/start-mock-container.sh`:
- Around line 32-44: 在 start-mock-container.sh 的 readiness 重试结束后、现有 docker logs
输出之后,调用 docker rm -f "$container" 清理新建容器,再保持失败退出行为不变。

---

Outside diff comments:
In `@src/app/v1/_lib/proxy/replay/replay-spool.ts`:
- Around line 98-101: 在负责处理 chunk 的方法中,先将每个输入 chunk 的 chunk.byteLength 累加到
pendingBytes,再调用 decoder.decode(),移除空文本时提前返回导致字节未计数的问题;仅在解码产生文本时追加到
pending。新增跨多个 chunk 的多字节 UTF-8 测试,验证 queuedWriteBytes/pendingBytes
按所有输入字节累计并正确遵守 1 MiB 上限。
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 012e4a98-5520-414c-919c-f483d4c6028b

📥 Commits

Reviewing files that changed from the base of the PR and between 3fe3225 and 05291ac.

📒 Files selected for processing (20)
  • .env.example
  • CHANGELOG.md
  • docs/troubleshooting/issue-1408-replay-oom.md
  • src/app/v1/_lib/proxy/replay/replay-spool.ts
  • src/app/v1/_lib/proxy/response-handler.ts
  • src/lib/config/env.schema.ts
  • src/lib/session-manager-detail-snapshots.test.ts
  • src/lib/session-manager.ts
  • tests/load/issue-1408-replay-oom/README.md
  • tests/load/issue-1408-replay-oom/drive-disconnect-waves.cjs
  • tests/load/issue-1408-replay-oom/memory-probe.cjs
  • tests/load/issue-1408-replay-oom/mock-upstream.cjs
  • tests/load/issue-1408-replay-oom/run-wave.sh
  • tests/load/issue-1408-replay-oom/sample-container.sh
  • tests/load/issue-1408-replay-oom/start-mock-container.sh
  • tests/unit/lib/env-store-session-response-body.test.ts
  • tests/unit/lib/session-manager-redaction.test.ts
  • tests/unit/proxy/issue-1408-load-fixture.test.ts
  • tests/unit/proxy/replay-spool.test.ts
  • tests/unit/proxy/response-handler-stream-terminal.test.ts

Comment thread docs/troubleshooting/issue-1408-replay-oom.md
Comment thread src/app/v1/_lib/proxy/replay/replay-spool.ts Outdated
Comment thread tests/load/issue-1408-replay-oom/drive-disconnect-waves.cjs
Comment thread tests/load/issue-1408-replay-oom/start-mock-container.sh
@github-actions

Copy link
Copy Markdown
Contributor

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

@github-actions github-actions Bot added the size/XL Extra Large PR (> 1000 lines) label Aug 11, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review Summary

This PR correctly eliminates the Replay disconnect memory-retention chain described in #1408: it removes the in-process parts[] full-stream retention, bounds the Redis write-behind backlog at 1 MiB, downgrades the detached drain window to 60 s when the spool becomes inactive, and caps persisted session response bodies. The core proxy logic, the drain-window refactor, and the session-body bounding are all sound, and the change is backed by 104+ focused tests covering boundary, race, and failure conditions.

PR Size: XL

  • Lines changed: 2009 (1884 additions, 125 deletions)
  • Files changed: 20

Optional split (non-blocking) — the PR bundles three logical groups that could be reviewed/landed independently if desired:

  1. Core fix + unit tests (~500 lines): replay-spool.ts, response-handler.ts, env.schema.ts, session-manager.ts + their unit tests. This is the essential behavioral fix.
  2. Load fixture (~700 lines): tests/load/issue-1408-replay-oom/* + contract test. Reproducible reproduction tooling.
  3. Documentation (~400 lines): docs/troubleshooting/issue-1408-replay-oom.md + CHANGELOG.md. Root-cause evidence report.

Keeping them together is defensible since the fixture and doc provide the verification evidence for the fix.

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 0 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

No issues met the reporting confidence threshold (>= 80).

Notable Validation Performed

  • completeAfterBilling chunk-count invariant: readChunks(0) is called after this.chunkCount is updated from the terminal writeOwned; fenced ownership guarantees no concurrent writer can mutate the LIST between the terminal write and the read-back, so the chunks.length !== this.chunkCount guard is a true consistency check, not a race.
  • serializeDurablePersistence chain safety: the module-level singleton swallows rejection in the chain-advancement handler (.then(id, id)) while propagating it to the caller, so a failing persist cannot poison the chain for subsequent spools. The serialization is intentional (bounds heap peak to one reconstructed payload) per the PR description.
  • capInactiveReplayDrainWindow correctness: the clientAbortDrainTimeoutMs <= CLIENT_ABORT_DRAIN_MAX_MS guard makes it a no-op in non-Replay mode and when REPLAY_MAX_DETACHED_MS is configured below 60 s (never increases the window). Math.max(0, ...) in scheduleClientAbortDrainTimeout handles the elapsed-exceeds-cap case. All three timing orderings (inactive-after-detach, active-spool-keeps-300 s, inactive-before-detach) are covered by tests.
  • Session-body double check: storeSessionResponse checks both the raw input and the post-redaction responseString, which is necessary because [REDACTED] substitution can grow a small body past the limit. At most one warn log is emitted per call. The snapshot path correctly handles null/string/object bodies and deletes stale keys on oversize.
  • notifyInactive idempotency: the inactiveNotified guard plus the release() idempotency guard correctly handle the disable-then-abort and abort-then-disable orderings without double-firing the callback or double-decrementing activeSpoolCount.
  • CLAUDE.md compliance: no emoji in code/comments/strings; no new user-facing dashboard strings requiring i18n (all changes are backend proxy/server-log/internal-doc); extensive test coverage added.

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Strong (104+ focused tests)
  • Code clarity - Good (helper extraction improves readability)

Automated review by Claude AI

@AptS-1547
AptS-1547 force-pushed the fix/issue-1408-replay-oom branch from 05291ac to b100aff Compare August 11, 2026 10:23
@AptS-1547

Copy link
Copy Markdown
Collaborator Author

CodeRabbit 第一轮 review 已处理(b100aff7):

  • outside-diff 的 UTF-8 计数问题成立:pendingBytes 现在在 streaming decode 前累计每个输入 chunk 的字节数;新增跨 chunk 多字节字符在 64 KiB 边界触发 flush 的测试。
  • 4 条 inline finding 均已修复并分别回复:completion tail backlog 上限、stats response 中断、readiness 容器清理、Markdown issue 引用。
  • 聚焦回归:84/84 passed;build、lint、lint:fix、typecheck、shell/Node syntax、git diff --check 均通过。
  • Docstring Coverage 2.86% 属于 CodeRabbit 通用 finishing-touch 指标;本仓库未要求为私有 TypeScript 函数补齐 80% docstring,且批量注释不改变本次 bug 行为,因此本轮不引入该噪声。

@AptS-1547

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Already reviewed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Comment thread src/app/v1/_lib/proxy/replay/replay-spool.ts Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review Summary

The PR closes the main Replay retention path, but one remaining gap in ReplaySpool.completeAfterBilling() still lets a detached stream exceed the new write-backlog ceiling on the terminal path. After validating the modified production code and the new regression tests in full-file context, I did not find other high-confidence issues on changed lines.

PR Size: XL

  • Lines changed: 2009
  • Files changed: 20
  • Split suggestions: land the core ReplaySpool / ProxyResponseHandler memory-retention fix separately from the load-fixture and troubleshooting-doc additions so the runtime behavior can be reviewed in isolation.
  • Split suggestions: keep the SESSION_RESPONSE_BODY_MAX_BYTES session-debug-artifact limit as a separate PR when possible, since it changes a different subsystem and test surface from the Replay drain fix.

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 1 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

Critical Issues (Must Fix)

  • None.

High Priority Issues (Should Fix)

  • src/app/v1/_lib/proxy/replay/replay-spool.ts:266completeAfterBilling() appends the terminal pendingBytes batch without re-checking MAX_QUEUED_WRITE_BYTES, so a stalled Redis write can still leave a detached spool above the new 1 MiB backlog ceiling until the blocked write drains.

Review Coverage

  • Logic and correctness
  • Security (OWASP Top 10)
  • Error handling
  • Type safety
  • Documentation accuracy
  • Test coverage
  • Code clarity

Automated review by Codex AI

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review Posted

  • Reviewed PR #1414, applied the size/XL label, and submitted the summary review on GitHub.
  • Posted 1 inline HIGH finding on src/app/v1/_lib/proxy/replay/replay-spool.ts:266.
  • Main issue: completeAfterBilling() appends the terminal tail batch without re-checking MAX_QUEUED_WRITE_BYTES, so a stalled Redis write can still push a detached spool above the new 1 MiB backlog ceiling.
  • I did not find other high-confidence issues on modified lines after checking the surrounding code and the new tests in context.
  • I could not execute the added tests in this runner because bun is not installed and node_modules is absent.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
tests/unit/proxy/replay-spool.test.ts (1)

99-99: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

建议重命名 resetOwnedChunkCount

该函数现在清空按 replay ID 保存的 chunks Map,不再重置计数。名称与行为不一致。改为 resetOwnedChunks 更准确。

♻️ 建议的改名
-    resetOwnedChunkCount: () => {
+    resetOwnedChunks: () => {
       ownedChunksByReplayId.clear();
     },

调用处同步更新。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/proxy/replay-spool.test.ts` at line 99, Rename the function
resetOwnedChunkCount to resetOwnedChunks to reflect that it clears the owned
chunks map, and update every call site and reference accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/load/issue-1408-replay-oom/start-mock-container.sh`:
- Around line 17-20: Update the existence check in start-mock-container.sh to
use docker container inspect instead of docker inspect, so only containers with
the specified name prevent startup; preserve the existing error message and exit
behavior.
- Around line 30-44: Add a shared container-cleanup function immediately after
the successful docker run, register POSIX traps for SIGINT, SIGTERM, and EXIT,
and have it remove the tracked container safely. Clear the traps before the
readiness-success exit so the ready container is preserved, and reuse the same
cleanup function in the timeout path. Add CI coverage for signal interruption
and early command failure cleanup.

In `@tests/unit/proxy/issue-1408-load-fixture.test.ts`:
- Around line 75-81: 隔离 fixture 的环境变量,避免继承父进程中的配置。更新
tests/unit/proxy/issue-1408-load-fixture.test.ts:75-81 的 startMock,在设置默认值和
overrides 前删除所有 fixture 配置键;同时在
tests/unit/proxy/issue-1408-load-fixture.test.ts:252-257 复用同一份已清理的 mock
环境,确保配置验证不受外部环境影响。

In `@tests/unit/proxy/replay-spool.test.ts`:
- Around line 676-723: 在测试套件的 beforeEach 中重置 storeControl.store.persistCompleted
的 mock,并重新设置其默认实现,确保不会保留并发测试中针对 identity.replayId 返回未完成 Promise 的实现;保留
envControl.maxPayloadBytes 现有的恢复逻辑。

---

Nitpick comments:
In `@tests/unit/proxy/replay-spool.test.ts`:
- Line 99: Rename the function resetOwnedChunkCount to resetOwnedChunks to
reflect that it clears the owned chunks map, and update every call site and
reference accordingly.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 108a6680-6bc1-49bc-8863-9e7de873dbd1

📥 Commits

Reviewing files that changed from the base of the PR and between 05291ac and b100aff.

📒 Files selected for processing (6)
  • docs/troubleshooting/issue-1408-replay-oom.md
  • src/app/v1/_lib/proxy/replay/replay-spool.ts
  • tests/load/issue-1408-replay-oom/drive-disconnect-waves.cjs
  • tests/load/issue-1408-replay-oom/start-mock-container.sh
  • tests/unit/proxy/issue-1408-load-fixture.test.ts
  • tests/unit/proxy/replay-spool.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/load/issue-1408-replay-oom/drive-disconnect-waves.cjs
  • src/app/v1/_lib/proxy/replay/replay-spool.ts
  • docs/troubleshooting/issue-1408-replay-oom.md

Comment thread tests/load/issue-1408-replay-oom/start-mock-container.sh Outdated
Comment thread tests/load/issue-1408-replay-oom/start-mock-container.sh Outdated
Comment thread tests/unit/proxy/issue-1408-load-fixture.test.ts Outdated
Comment thread tests/unit/proxy/replay-spool.test.ts
@github-actions

Copy link
Copy Markdown
Contributor

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

@AptS-1547

Copy link
Copy Markdown
Collaborator Author

CodeRabbit 第二轮 review 已处理(9f5890df,普通提交,未改写远端历史):

  • 4 条 inline finding 均已修复并分别回复:container-only inspect、SIGINT/SIGTERM/early-exit cleanup、fixture 环境隔离、persistCompleted mock 重置。
  • review body 中的 nitpick 也已处理:resetOwnedChunkCount 重命名为与实际行为一致的 resetOwnedChunks
  • 新增容器同名 image、SIGINT、SIGTERM、提前命令失败、父环境污染 5 类行为覆盖;POSIX 行为测试在 Windows 明确跳过。
  • 聚焦回归 89/89 passed;build、lint、lint:fix、typecheck、shell syntax、git diff --check 均通过。
  • 全量 bun run test 仍仅有既有 language-switcher.test.tsx:153 sessionStorage console 断言失败,与本轮 3 个文件无关。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/load/issue-1408-replay-oom/start-mock-container.sh`:
- Line 42: 为健康检查中的 curl 调用添加 --connect-timeout 2 和 --max-time
5,确保连接或响应无期限阻塞;保留现有重试逻辑,并使用不返回响应的端点验证重试次数及 cleanup_container 清理行为。
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 158ace4d-f899-4293-8e46-61ed905aa29e

📥 Commits

Reviewing files that changed from the base of the PR and between b100aff and 9f5890d.

📒 Files selected for processing (3)
  • tests/load/issue-1408-replay-oom/start-mock-container.sh
  • tests/unit/proxy/issue-1408-load-fixture.test.ts
  • tests/unit/proxy/replay-spool.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit/proxy/replay-spool.test.ts

Comment thread tests/load/issue-1408-replay-oom/start-mock-container.sh Outdated
@github-actions

Copy link
Copy Markdown
Contributor

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

@AptS-1547

Copy link
Copy Markdown
Collaborator Author

CodeRabbit 第三轮(最终 remediation round)已处理(5825a5b4,普通提交,未改写远端历史):

  • /health readiness probe 增加 --connect-timeout 2 --max-time 5,避免已建立连接但无响应时跳不出重试。
  • 新增 curl timeout 退出码 28 模拟,断言 60 次 probe 全部带超时参数,并在重试耗尽后清理容器。
  • 当前聚焦回归 90/90;build、lint、lint:fix、typecheck、shell syntax、git diff --check 通过。
  • 当前全量测试仍仅复现既有 language-switcher.test.tsx:153 失败。

已达到约定的最多三轮真实反馈/修复上限,本 PR 不再触发额外 CodeRabbit review round。

@github-actions

Copy link
Copy Markdown
Contributor

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

@AptS-1547

Copy link
Copy Markdown
Collaborator Author

按当前运维取舍,SESSION_RESPONSE_BODY_MAX_BYTES 默认值已在 ecdc6ae 调整为 5 MiB(5242880),并同步 runtime fallback、EnvSchema、.env.example、单测、CHANGELOG 与 #1408 调查文档。三份 Redis response body 的物理去重和 5 MiB/RDB 负载门禁已拆分到 #1415 跟踪。相关测试 34/34、build/lint/lint:fix/typecheck 通过;全量仍仅有既有 language-switcher.test.tsx:153 失败。

@AptS-1547
AptS-1547 merged commit f01f9f8 into dev Aug 11, 2026
8 of 9 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Claude Code Hub Roadmap Aug 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core area:session bug Something isn't working oncall Critical blocking issue requiring immediate oncall attention size/XL Extra Large PR (> 1000 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant