Skip to content

fix(codebuddy): harden captured parallel tool blocks - #6022

Closed
mdwsk88 wants to merge 2 commits into
lidge-jun:devfrom
mdwsk88:codex/codebuddy-capture-hardening
Closed

mdwsk88 wants to merge 2 commits into
lidge-jun:devfrom
mdwsk88:codex/codebuddy-capture-hardening

Conversation

@mdwsk88

@mdwsk88 mdwsk88 commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Follow-up to the CodeBuddy parallel tool-use fix that landed through fix: bug-PR merge train batch 8 (CodeBuddy parallel tool-use, ci-privacy-gate hang) #5945.
  • Fails closed when same-index reuse would close a tool call before its JSON arguments are complete.
  • Prevents an unindexed stop or delta from completing an indexed tool block, leaving terminal accounting to reject the turn.
  • Fails closed immediately when a nonempty unindexed input_json_delta cannot be attributed to an open tool block, so a later indexed stop cannot normalize the loss into an empty {} call.
  • Enforces the per-turn tool-call limit when a block opens, before buffering delays the emitted tool_call_start.

Verification

  • bun test tests/providers/codebuddy-protocol.test.ts tests/providers/codebuddy-tool-bridge-turn.test.ts — 68 pass, 0 fail.
  • bun run typecheck — pass.
  • cd docs-site && bun install --frozen-lockfile && bun run build — pass, 537 pages built.
  • git diff --check upstream/dev..HEAD — pass.
  • bun run test in a clean checkout outside ~/.codex — exit 1. 32134 pass / 43 skip / 26 fail / 3 errors in the parallel lane, plus 1 serial-lane failure. Failures are 20 DNS fake-IP environment failures, 3 failures because an existing OpenCodex service is already listening on port 10100, 1 macOS concurrency timeout, and 3 load/order flakes that pass when their files are rerun alone. None are attributable to this PR.

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.

Review readiness checklist

This PR stays in draft until every box below is ticked. Tick all four boxes once the requirements are met:

  • Required local validation passed; commands, results, and any full-suite exception are documented.

  • I pushed my PR to a recent dev commit (at most 10 behind; a maintainer may still ask for the exact tip before merge).

  • I resolved all correct Codex and CodeRabbit findings.

  • My PR is ready for review.

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of parallel CodeBuddy tool calls when stream fragments interleave or content-block indexes are reused.
    • Calls with incomplete or invalid arguments, or argument fragments that cannot be matched to a tool call, now fail instead of being incorrectly completed.
    • Tool-call limits are enforced as soon as a call begins.
  • Documentation
    • Clarified how CodeBuddy handles parallel tool calls, incomplete blocks, and tool-call limits.

@coderabbitai

coderabbitai Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: lidge-jun/opencodex/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: d0bbe5ba-e0da-430a-9620-2de462e601b9

📥 Commits

Reviewing files that changed from the base of the PR and between d8b85ad and cc368d0.

📒 Files selected for processing (6)
  • docs-site/src/content/docs/guides/providers.md
  • src/adapters/coding-agent/protocol.ts
  • src/adapters/coding-agent/turn.ts
  • structure/providers-and-adapters.md
  • tests/providers/codebuddy-protocol.test.ts
  • tests/providers/codebuddy-tool-bridge-turn.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.


📝 Walkthrough

Walkthrough

CodeBuddy tool bridging now applies strict indexed-block matching, validates buffered arguments before implicit closure, and rejects unattributable fragments. Turn limits are enforced when blocks open. Tests and documentation cover these cases.

Changes

CodeBuddy tool-block capture

Layer / File(s) Summary
Strict tool-block parsing
src/adapters/coding-agent/protocol.ts, tests/providers/codebuddy-protocol.test.ts, structure/providers-and-adapters.md, docs-site/src/content/docs/guides/providers.md
Strict capture rejects index-less frames that cannot resolve to an indexed block. It validates arguments before implicitly closing a block, and rejects unattributable argument fragments. Parser tests and documentation describe these cases.
Bridge turn enforcement
src/adapters/coding-agent/turn.ts, tests/providers/codebuddy-tool-bridge-turn.test.ts
The bridge enables strict capture. It checks the tool-call limit when a block opens, before tool-call events are emitted. Turn tests cover protocol failures and limit enforcement.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Possibly related PRs

Merge Risk: ⚪ Minimal · up to cc368

The reviewed tool-block handling has no established merge-blocking defect; it is ready for normal merge checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to cc368

The reviewed paths reject ambiguous or incomplete tool calls earlier and apply the call limit before buffered calls are forwarded. No newly introduced security concern was established, but the assessment cannot cover every downstream consumer or independently compare the full previous implementation.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The directly reviewed exposure is a tool-enabled CodeBuddy turn translating CLI-produced frames into client-visible tool calls. The configured limit is 16 captured calls per turn; downstream consumers outside this path were not exhaustively established.

Trust Boundaries and Controls

  • observed — Tool-bridge turns enable strict parsing. An unmatchable nonempty argument fragment fails, and a block with invalid JSON arguments cannot be closed into executable tool-call events.
  • observed — Before forwarding mapped events, the turn rejects uncaptured fallback tool use and opened-block counts above the configured limit; it also rejects forwarded tool names outside the selected catalog.

Resilience and Maintainability Implications

  • observed — Parser errors, an incomplete terminal call, and a terminal result arriving before the required message stop take error paths rather than completing a tool-use turn.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 4 files. (2 skipped: 2 …
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: strengthening CodeBuddy handling for captured parallel tool blocks.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

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.

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

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@lidge-jun

Copy link
Copy Markdown
Owner

리뷰 · 우선순위 62 / 80

이 PR은 CodeBuddy가 도구를 여러 개 부를 때, 인자가 다 쓰이기 전에 그 호출을 끝내지 못하게 막습니다. #5945 다음에 오는 보강입니다. 베이스는 dev입니다.

같은 번호로 다음 도구가 시작되면, 앞에 열린 도구의 인자가 완전한 JSON 객체일 때만 그 도구를 닫습니다. 글자가 없거나 {"value":처럼 중간에 끊기면 턴은 502 protocol_error로 끝납니다. []처럼 객체가 아닌 JSON도 거절합니다. 번호가 없는 종료 프레임은 번호가 있는 도구를 닫지 못합니다. 턴 끝 검사가 열린 도구를 보고 실패시킵니다.

한 턴의 도구 상한은 16개입니다. 세는 시점은 도구가 열리는 순간입니다. 17번째가 열리면 tool_call_limit이고, 그 프레임에서 만들다 만 호출은 나가지 않습니다.

이 검사는 도구 다리가 켜진 CodeBuddy 턴에만 붙습니다. Qoder와 Claude CLI는 그 다리를 넘기지 않습니다.

라인 - src/adapters/coding-agent/protocol.ts resolveToolBlockKey 384행, input_json_delta 449행. 번호가 있는 도구가 하나 열려 있을 때, 번호가 없는 인자 조각은 사라집니다. 예외는 나지 않습니다. 이어서 번호가 맞는 종료가 오면 남은 조각만으로 도구를 닫습니다. 남은 조각이 없으면 closeToolBlock 394행이 빈 인자를 그대로 통과시킵니다. 인자 없는 도구 호출이 성공으로 나가고, 클라이언트가 그 도구를 실행합니다.

라인 - tests/providers/codebuddy-protocol.test.ts 349행. 번호 없는 조각 {"wrong":true}가 사라진 뒤, 번호 있는 {}로 호출이 성공하는 것을 정답으로 고정합니다. 조각이 버려져도 턴은 성공입니다.

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

번호 없는 종료는 턴을 실패로 끝냅니다. 번호 없는 인자 조각은 조용히 사라집니다. 둘을 같은 실패로 볼지 정해야 합니다.

인자가 한 조각도 없는 종료는 빈 도구 호출로 남습니다. 주석은 이것을 CLI의 기존 표현이라고 합니다. 처음부터 인자가 없는 도구와, 번호가 없어서 조각이 사라진 도구를 어떻게 가를지 정해야 합니다.

너의 추천

엄격 모드에서는 번호 없는 input_json_delta가 번호 있는 도구에 붙지 못하면, 열린 도구가 둘 이상일 때와 같이 바로 protocol_error로 끊으세요. 349행 테스트에서 그 조각을 버린 뒤 {}로 닫는 성공은 빼세요. 같은 번호 재사용의 실패와, 17번째가 열릴 때 한도를 거는 동작은 유지하세요. 이 PR은 #5945 다음 보강이니 닫지 마세요. 베이스는 dev로 두세요.

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

@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

✅ READY

  • all PR quality gates passed; the review readiness checklist is complete.

Review readiness checklist

  • ✅ Required local validation passed; commands, results, and any full-suite exception are documented.
  • ✅ I pushed my PR to a recent dev commit (at most 10 behind; a maintainer may still ask for the exact tip before merge).
  • ✅ I resolved all correct Codex and CodeRabbit findings.
  • ✅ My PR is ready for review.

✅ 4/4 boxes ticked.

This pull request is already Ready for Review.
The review-ready label marks this PR as ready; review automation runs independently.
Maintainers notified: @lidge-jun @Ingwannu

@Ingwannu

Copy link
Copy Markdown
Owner

Early draft blocker on exact head 87d27693699b03900dcfd002cba57474bc951aa7: an unindexed argument delta for the sole indexed tool block is silently dropped at protocol.ts:384, while a later indexed stop closes the block. Because closeToolBlock skips JSON validation when no fragments were retained, the call completes and downstream normalizes empty arguments to {}. A nonempty proposed call can therefore become a successful empty/default-argument call.

Please fail the capture on an unresolvable argument delta and add this exact regression: INIT → START(index 2/id A) → nonempty input_json_delta with no index → STOP(index 2) → message_stop must reject, not emit {}. The current test supplies a later valid indexed {}, which masks the loss. Holding approval while the PR remains draft; no executable exact-head test CI is visible yet.

@mdwsk88
mdwsk88 marked this pull request as ready for review September 27, 2026 02:34
@github-actions
github-actions Bot marked this pull request as draft September 27, 2026 02:34
@mdwsk88
mdwsk88 marked this pull request as ready for review September 27, 2026 02:44
@Ingwannu

Copy link
Copy Markdown
Owner

Re-review on exact head cc368d0235: the previously reported unindexed-argument loss is fixed. Strict capture now rejects a nonempty unattributable fragment before a later indexed stop can emit an empty/default-argument call, and the exact regression covers that sequence. I found no replacement source blocker. I approved the pending exact-head workflow run; holding the formal approval only until executable CI completes successfully.

@lidge-jun

Copy link
Copy Markdown
Owner

Landed on dev in #6061 (merge 06d7914e6a) as one squashed commit that keeps your authorship. Thank you. Closing because this repository merges into dev, so GitHub does not close carried PRs automatically.

@lidge-jun lidge-jun closed this Sep 27, 2026
robin-bially pushed a commit to robin-bially/opencodex that referenced this pull request Sep 27, 2026
Carried from lidge-jun#6022 into merge train round 3.

Co-authored-by: mdwsk88 <924038395@qq.com>
robin-bially added a commit to robin-bially/opencodex that referenced this pull request Sep 27, 2026
…ge-jun#6022

dev's lidge-jun#6022 hardened the coding-agent capture path and moved the per-turn tool-call
limit from the emitted tool_call_start to the block open, because the parser buffers a
block until its stop. This adapter reads the same parse state through its own capture-only
bridge, so both halves had to follow.

The state now sets strictToolBlockCapture for a turn with a bridge, which is what makes
the parser refuse incomplete JSON arguments, refuse a delta that belongs to no open block,
and treat a same-index start as an implicit stop only once the previous arguments are
complete. The cap is checked when toolBlockStarts grows instead of when the buffered start
is finally emitted, so a stream that only opens blocks is bounded at the open rather than
after it parks.

The added test opens more blocks than the cap allows without closing one: without the move
it ran on to message_stop and failed there for a different reason.
robin-bially added a commit to robin-bially/opencodex that referenced this pull request Sep 27, 2026
…ge-jun#6022

dev's lidge-jun#6022 hardened the coding-agent capture path and moved the per-turn tool-call
limit from the emitted tool_call_start to the block open, because the parser buffers a
block until its stop. This adapter reads the same parse state through its own capture-only
bridge, so both halves had to follow.

The state now sets strictToolBlockCapture for a turn with a bridge, which is what makes
the parser refuse incomplete JSON arguments, refuse a delta that belongs to no open block,
and treat a same-index start as an implicit stop only once the previous arguments are
complete. The cap is checked when toolBlockStarts grows instead of when the buffered start
is finally emitted, so a stream that only opens blocks is bounded at the open rather than
after it parks.

The added test opens more blocks than the cap allows without closing one: without the move
it ran on to message_stop and failed there for a different reason.
robin-bially added a commit to robin-bially/opencodex that referenced this pull request Sep 27, 2026
…ge-jun#6022

dev's lidge-jun#6022 hardened the coding-agent capture path and moved the per-turn tool-call
limit from the emitted tool_call_start to the block open, because the parser buffers a
block until its stop. This adapter reads the same parse state through its own capture-only
bridge, so both halves had to follow.

The state now sets strictToolBlockCapture for a turn with a bridge, which is what makes
the parser refuse incomplete JSON arguments, refuse a delta that belongs to no open block,
and treat a same-index start as an implicit stop only once the previous arguments are
complete. The cap is checked when toolBlockStarts grows instead of when the buffered start
is finally emitted, so a stream that only opens blocks is bounded at the open rather than
after it parks.

The added test opens more blocks than the cap allows without closing one: without the move
it ran on to message_stop and failed there for a different reason.
robin-bially added a commit to robin-bially/opencodex that referenced this pull request Sep 28, 2026
…ge-jun#6022

dev's lidge-jun#6022 hardened the coding-agent capture path and moved the per-turn tool-call
limit from the emitted tool_call_start to the block open, because the parser buffers a
block until its stop. This adapter reads the same parse state through its own capture-only
bridge, so both halves had to follow.

The state now sets strictToolBlockCapture for a turn with a bridge, which is what makes
the parser refuse incomplete JSON arguments, refuse a delta that belongs to no open block,
and treat a same-index start as an implicit stop only once the previous arguments are
complete. The cap is checked when toolBlockStarts grows instead of when the buffered start
is finally emitted, so a stream that only opens blocks is bounded at the open rather than
after it parks.

The added test opens more blocks than the cap allows without closing one: without the move
it ran on to message_stop and failed there for a different reason.
robin-bially added a commit to robin-bially/opencodex that referenced this pull request Sep 28, 2026
dev's lidge-jun#6081/lidge-jun#6083 moved the ceiling and the budget for coding-agent tool state into the
shared parser, which is the same state this adapter reads through its capture-only bridge.
Two halves had to follow, the same class as lidge-jun#6022 and lidge-jun#5945 before them.

The parser now enforces its ceiling (16 starts, or a bridge's tighter limit) before it
allocates the block and reports the refusal through `toolCallLimitExceeded`; the parse
state carries the bridge's limit for that. This adapter only compared `toolBlockStarts`
after the fact, so a start the parser refused before allocation left no trace: the dropped
call never entered `toolBlockStarts`, the completeness invariants therefore saw starts and
completions as equal, and a turn could end as a successful completion with a call missing.

The parser also charges retained tool identity and argument fragments to the request's
translator budget and releases them on close, EOF, protocol error and abort. The state now
carries `incoming.translatorBudget`, `releaseOpenToolBlocks(state)` runs on every exit
path, and a budget overflow keeps its own `translation_buffer_limit` code instead of being
flattened into the generic SDK error the surrounding branch reports.

Two tests cover it: a turn without a bridge that passes the shared ceiling fails closed,
and a retained argument past a small per-call budget surfaces the budget's code. Reverting
the adapter fails four tests, those two and the two that pinned the cap before, and the
existing cap tests now pass through the parser's own admission rather than a parallel count.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working review-ready

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants