Skip to content

fix(images): bound bridge iteration buffering - #4478

Merged
lidge-jun merged 1 commit into
devfrom
codex/260913-carry-4388-media-bounds
Sep 13, 2026
Merged

fix(images): bound bridge iteration buffering#4478
lidge-jun merged 1 commit into
devfrom
codex/260913-carry-4388-media-bounds

Conversation

@lidge-jun

Copy link
Copy Markdown
Owner

Summary

Carries #4388 by @luvs01 (source head 9518b5281, fork branch agent/media-loop-bounds-20260912) and folds in the Codex review finding that was still open on that head.

A hidden image or video iteration collected adapter events into an unbounded array. Both the runTurn emit path and ordinary stream collection retained every non-heartbeat event until the scanner ran, so one oversized tool argument or a long text stream could grow a single iteration without limit — even though src/lib/translator-budget.ts already defines the turn and per-call ceilings the rest of the translator honours. Each iteration now owns an independent budget of 32 MiB of serialized retained events (including array framing) and 2 MiB of UTF-8 arguments per open call, counting a surrogate pair that straddles two deltas once. Overflow aborts the producer and surfaces translation_buffer_limit instead of a truncated success.

The folded review finding

Codex flagged src/images/loop.ts for charging events the queue then threw away, and that finding was never addressed on the source branch.

createAdapterEventQueue merges adjacent text and thinking deltas into chunks of up to 64 KiB whenever no reader is waiting, which is the normal case for a synchronous producer. Charging every pre-merge envelope billed roughly 31 bytes for each one-character delta, so about 1 MiB of retained output crossed the 32 MiB turn limit and aborted a healthy turn — the failure this PR exists to prevent, arriving about thirty times too early.

push now returns whether it merged the event into its buffered tail, and a merged delta is charged only for the payload it appended. The return value is purely additive: core.ts, compact.ts, collaboration.ts and encrypted-payload.ts all pass push as a void callback and are unaffected.

The accounting is deliberately conservative in one spot. JSON escaping is per character, so a merged delta costs its quoted form minus the quotes; a surrogate pair split across two deltas over-counts by eight bytes and never under-counts.

tests/images/loop.test.ts gains a regression that emits 1.2 M one-character deltas, whose envelopes exceed the turn limit while their merged form is about 1 MiB. It was driven red against the unfixed emit path first, where it reproduced exactly the spurious translation_buffer_limit response.

This is the middle link of lane B in the contributor carry train, based on the #4381 carry. The lane tip is the #4460 carry and its CI run is this lane's suite proof; this branch's head carries [skip ci] deliberately.

Verification

  • bun test tests/images/loop.test.ts tests/adapters/run-turn-queue.test.ts — 74 pass, 0 fail, 2242 assertions.
  • New coalescing regression confirmed red before the fix, green after.
  • bun run typecheck — passed.
  • bun run structure:check — passed.
  • bun run privacy:scan — passed.
  • Local full suite: not run. Hosted CI on the lane tip is the suite proof for this branch.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Co-authored-by: luvs01 27862058+luvs01@users.noreply.github.com

Carry #4388 from 9518b52 onto the lane B
chain above the #4381 carry.

A hidden image/video iteration collected adapter events into an unbounded
array. Both the runTurn emit path and ordinary stream collection kept every
non-heartbeat event until the scanner ran, so an oversized tool argument or a
long text stream grew one iteration without limit even though
src/lib/translator-budget.ts already defines the turn and call-argument
ceilings. Each iteration now owns an independent budget: 32 MiB of serialized
retained events including array framing, and 2 MiB of UTF-8 arguments per open
call, with surrogate pairs that straddle two deltas counted once. Overflow
aborts the producer and surfaces translation_buffer_limit instead of a
truncated success.

Folded in the Codex review finding left on the source head, which was not
addressed there. createAdapterEventQueue merges adjacent text and thinking
deltas into chunks of up to 64 KiB while no reader is waiting, so a
synchronous producer's token-granular deltas survive as a handful of strings.
Charging each pre-merge envelope billed about 31 bytes for every one-character
delta, so roughly 1 MiB of retained output crossed the 32 MiB turn limit and
aborted a healthy turn. push now reports whether it merged the event into its
buffered tail, and a merged delta is charged only for the payload it appended.
The return value is additive: every other caller passes push as a void
callback and is unaffected.

That accounting is deliberately conservative in one place. JSON escaping is
per character, so a merged delta costs its quoted form minus the quotes; a
surrogate pair split across two deltas over-counts by eight bytes and never
under-counts.

Verification on this carry: bun test tests/images/loop.test.ts
tests/adapters/run-turn-queue.test.ts (74 pass, 0 fail), bun run typecheck,
bun run structure:check, bun run privacy:scan. The new coalescing regression
was driven red against the unfixed emit path first, where it reproduced the
spurious translation_buffer_limit response.
Local full suite: NOT RUN. Hosted CI on the lane tip is the suite proof.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 13, 2026 06:59
@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • ^dev$
  • ^preview$

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: cd54110a-8028-4da1-a20f-e6932e28cdb7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 13, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-13T07:04:13.006612Z b1453a3 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions github-actions Bot added the bug Something isn't working label Sep 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b1453a3838

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/images/loop.ts
// Check at emission, before even a synchronous producer can fill the queue. push
// reports whether it merged this delta into the buffered tail, which is what the
// iteration actually retains once the consumer drains it.
iterationBudget.retain(event, queue.push(event));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate runTurn events before queue insertion

When a runTurn adapter emits a single oversized event, queue.push(event) makes that event queue-visible before retain detects that it exceeds the 32 MiB limit. The catch then closes rather than clears the queue, so queue.stream() drains the rejected event into events before collectionError is thrown; an arbitrarily large tool-argument or text event can therefore remain retained and exhaust memory despite this change's bound. Make admission transactional or pre-charge the event before inserting it, adjusting the charge afterward if the queue coalesces it.

Useful? React with 👍 / 👎.

@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 73 / 80

설명
이 PR은 @luvs01의 #4388을 tip(b4dec3469, package 2.53.0)으로 캐리하고, 열려 있던 Codex 리뷰 지적(병합된 delta 과금)을 접어 넣었다. 숨은 image/video 반복이 어댑터 이벤트를 무한 배열에 모았다. runTurn emit과 일반 스트림 collect 모두 heartbeat가 아닌 이벤트를 스캔 전까지 붙잡아서, 큰 tool argument나 긴 텍스트 한 번이면 반복 하나가 메모리 한도를 넘을 수 있었다.

고침은 두 층이다. src/adapters/run-turn-queue.tspush가 “꼬리에 병합됐는지”를 boolean으로 돌려준다. src/images/loop.ts는 반복마다 독립 retention budget을 두고, 직렬화 바이트·TRANSLATOR_MAX_CALL_ARGUMENT_BYTES·TRANSLATOR_MAX_TURN_BYTES를 넘기면 translation_buffer_limit으로 끊는다. 병합된 delta는 붙은 payload만 과금해서, 토큰 단위 delta가 많다고 한도의 1/30에서 잘못 끊기지 않게 했다. heartbeat는 모으지 않는다. 테스트 tests/images/loop.test.ts가 runTurn/parseStream 양쪽 overflow·경계·취소·과금 정확도를 잠그고, structure runtime/inventory에 계약을 적었다.

우선순위 73은 DoS급 메모리 경로를 막고, 변경이 media loop+queue로 좁으며 tip과 독립이기 때문이다. types/config 분할과 무관하다.

src/adapters/run-turn-queue.ts AdapterEventQueue.push - 병합 여부를 boolean으로. budget 호출부가 이중 과금하지 않게 한다.

src/images/loop.ts iteration budget / TRANSLATOR_MAX_* - 반복당 32MiB급 retained collect, tool_args 한도, overflow→translation_buffer_limit.

tests/images/loop.test.ts - overflow·경계·한글 chunk·merge 과금·late emit 안전.

심볼 원본 #4388 / TranslatorBudgetExceededError - 캐리+리뷰 접힘 범위가 본문과 맞다.

메인테이너의 판단이 필요한 지점

너의 추천
테스트 초록이면 머지하세요. 이어서 #4388을 Landed via #4478로 닫으세요.

이 댓글은 grok-bot이 작성했습니다

lidge-jun added a commit that referenced this pull request Sep 13, 2026
…-availability

Lane B of the contributor carry train: truncated-terminal search failure (#4381 by luvs01), bounded bridge iteration buffering (#4388 by luvs01), and distinguishing model availability from auth failure (#4460 by AgenticLab-SH).

Tip-only CI by owner authorization for this batch. Cross-platform CI run 34744793611 concluded success on bd39d3b, the exact head merged here, and it covers every link because the lane is cumulative. #4472 and #4478 carry no ci check of their own.

Lane S (#4477, carrying #4447) is deliberately not part of this lane and is held for maintainer security review.
Base automatically changed from codex/260913-carry-4381-truncated-search to dev September 13, 2026 07:50
@lidge-jun
lidge-jun merged commit b1453a3 into dev Sep 13, 2026
6 of 7 checks passed
@lidge-jun
lidge-jun deleted the codex/260913-carry-4388-media-bounds branch September 13, 2026 07:50
@lidge-jun

Copy link
Copy Markdown
Owner Author

Merged into dev as part of lane B, under the tip-only CI economy the repository owner authorized for this batch.

  • Owner authorization for tip-only CI in this batch.
  • Covering run: Cross-platform CI 34744793611, concluded success on bd39d3b, the exact head of tip fix(codex): distinguish model availability from auth failure #4480 that was merged. The lane is cumulative, so that run executed this branch's content as a strict subset.
  • This pull request's own ci check never ran; its head commit carries [skip ci] by design.

Landed via merge commit 2af30c2, verified as an ancestor of origin/dev.

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

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant