Skip to content

fix(openai-chat): treat a non-string repeat as padding once that field is known - #2165

Merged
lidge-jun merged 1 commit into
devfrom
codex/absorb-openai-chat-padding-repeats
Aug 20, 2026
Merged

fix(openai-chat): treat a non-string repeat as padding once that field is known#2165
lidge-jun merged 1 commit into
devfrom
codex/absorb-openai-chat-padding-repeats

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Summary

Absorbs #2155 by @waw4303. Stack layer 5 — base is codex/absorb-opencode-go-quota-siblings (#2164), not dev.

The defect. Some OpenAI-compatible streamers repeat an already-sent id, name, or arguments as a non-string placeholder on a continuation delta rather than as null. Validation ran before the pending-call lookup, so the adapter could not see that the same wire index already held that value in canonical form. The stream died with a 502 and the tool never ran.

Reproduced on unpatched dev: upstream response contained invalid tool calls (tool_call_function_name_invalid; callIndex=0; valueType=object), with the following valid {"command":"ls"} fragment never consumed.

Two corrections on top of #2155.

Too wide for arguments. The original gated acceptance on the call having a canonical name. A name says nothing about whether arguments was ever sent as a string — so a chunk establishing name: "terminal" with no arguments field, followed by arguments: {command:"ls"}, would silently drop a real payload the model intended to send. PendingToolCall now carries sawArgumentsString. An empty string counts: it proves the upstream sent the field with the right wire type, which is exactly what a later malformed repeat would be padding for.

Too narrow for id. A non-string repeated id stayed unconditionally terminal even after a canonical id was stored. Ids now follow the same per-field rule.

Diagnostics. These are now passed from the rejection site rather than rescanned. A stateless rescan stops at the first structurally odd value it sees — so a stream carrying accepted padding on call 0 and a real defect on call 1 blamed call 0. Both invalidToolCallsEvent and logInvalidToolCalls take an optional override; structural failures before pending state is relevant still use the stateless fallback.

Sibling-vs-stacked note: #2155 has no dependency on the openai-chat hunks in #2151 (imports/service-tier around lines 14–1343 vs stream parsing around 1628–1677). It sits here for a clean single chain, not because the code requires it.

Closes #2155.

Verification

  • bun run typecheck — clean.
  • bun test --isolate tests/openai-chat-hardening.test.ts tests/openai-chat-invalid-tool-call-diagnostics.test.ts — 62 pass / 0 fail (58 pre-existing, unchanged by the refactor).
  • RED-first, and the split is the point. Reverting only src/adapters/openai-chat.ts gives 2 pass / 2 fail. The two that fail are the bug being fixed. The two that pass — canonical-name-does-not-authorize-arguments, and malformed-id-stays-terminal — pass on unpatched dev because dev rejects everything; they are the guards proving this PR did not widen tolerance while fixing it.
  • bun run test at the stack tip — 13562 pass / 10 skip / 0 fail across 857 files.
  • bun run privacy:scan — passed.

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.

Diagnostics still report shape only, never values — the existing privacy contract for this channel is unchanged.


Stack (bottom to top): #2134#2160#2162#2164this

Summary by CodeRabbit

  • Bug Fixes

    • Improved reliability when processing streamed tool calls with repeated or malformed fields.
    • Preserved valid tool-call data when non-critical padding is malformed.
    • Reports more precise diagnostics, including the specific failing call and field.
    • Prevents unresolved invalid tool-call values from being accepted.
  • Tests

    • Added coverage for parallel tool calls, malformed IDs, repeated fields, and diagnostic attribution.

@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner August 20, 2026 01:14
@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5c22a5be-48f3-4d67-84a9-0fb7ddd967fc

📥 Commits

Reviewing files that changed from the base of the PR and between 293494c and 772d375.

📒 Files selected for processing (2)
  • src/adapters/openai-chat.ts
  • tests/openai-chat-hardening.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

The adapter now tracks canonical streamed tool-call fields, tolerates malformed repeated padding after resolution, and emits call-specific diagnostics for invalid fields. Tests cover accepted padding, terminal errors, provenance isolation, malformed IDs, and parallel-call attribution.

Changes

Stream validation hardening

Layer / File(s) Summary
Canonical provenance and diagnostic routing
src/adapters/openai-chat.ts
Pending tool calls track whether string arguments were received. Invalid-call events and debug logging can use caller-supplied diagnostics.
Per-field streamed tool-call validation
src/adapters/openai-chat.ts, tests/openai-chat-hardening.test.ts
Stream processing resolves calls before validating repeated fields. Non-string padding is accepted only after canonical values exist. Invalid fields produce call-indexed terminal diagnostics. Tests cover repeated fields, malformed IDs, provenance isolation, and parallel calls.

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

Merge Risk: ⚪ Minimal · up to 772d3

The change is localized to OpenAI-compatible stream parsing and its tests, with no actionable merge-blocking risk remaining beyond normal checks and review.

Possibly related PRs

Suggested labels: bug

Suggested reviewers: ingwannu, wibias, olddonkey

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy issue #2155 by validating fields defensively, applying per-field provenance, failing closed for unresolved values, and adding regression tests.
Out of Scope Changes check ✅ Passed The added ID provenance, argument tracking, diagnostics, and debug logging directly support the stated stream-hardening objectives and remain in scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary fix: accepting repeated non-string tool-call fields as padding after canonical field data exists.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/absorb-openai-chat-padding-repeats

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.

@lidge-jun

Copy link
Copy Markdown
Owner Author

Stack map

Merge bottom-up; each layer's base is the branch below it.

# PR Branch Absorbs
1 #2134 codex/fix-subagent-roster-truncation maintainer fix (base dev)
2 #2160 codex/absorb-opencode-free-static-headers #2067 @waw4303
3 #2162 codex/absorb-agentrouter-language-framing #2082 @yzxcj797
4 #2164 codex/absorb-opencode-go-quota-siblings #2027 @yzxcj797
5 #2165 codex/absorb-openai-chat-padding-repeats #2155 @waw4303

All five are rebased onto the current dev tip and the full suite is green at the top of the stack (13562 pass / 0 fail). Retarget the remaining children to dev as each parent lands.

@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Aug 20, 2026
…d is known

Some OpenAI-compatible streamers repeat an already-sent id, name, or
arguments as a non-string placeholder on a continuation delta rather than
as null. Validation ran before the pending-call lookup, so the whole turn
died with a 502 and the tool never ran -- even though the value being
repeated was already held in canonical form.

The lookup now happens first and tolerance is per field, keyed on that
field's own provenance. Two corrections on top of @waw4303's #2155.

It gated arguments acceptance on the call having a canonical NAME. A name
says nothing about whether arguments was ever sent as a string, so a real
argument payload could be silently dropped. PendingToolCall now carries
sawArgumentsString; an empty string counts, because it proves the upstream
sent the field with the right wire type.

It also left a non-string repeated id unconditionally terminal even after
a canonical id was stored. Ids now follow the same rule as the other two.

Diagnostics are passed from the rejection site instead of rescanned.
A stateless rescan stops at the first structurally odd value, so a stream
carrying accepted padding on call 0 and a real defect on call 1 blamed
call 0.
@lidge-jun
lidge-jun force-pushed the codex/absorb-openai-chat-padding-repeats branch from fc97a6d to 772d375 Compare August 20, 2026 01:22
@lidge-jun
lidge-jun force-pushed the codex/absorb-opencode-go-quota-siblings branch from 4ee6161 to 293494c Compare August 20, 2026 01:22
@Ingwannu

Copy link
Copy Markdown
Owner

Manual review of the current layer found the per-field provenance correction materially safer than #2155: name, arguments, and id no longer authorize one another, unresolved fields still fail closed, and diagnostics identify the actual rejected call without logging values. The exact stack-tip focused run passed 97/97 tests, including the hardening and diagnostic suites.

I am not approving yet because CodeRabbit skipped this non-dev base and macOS CI is still running. @coderabbitai review

After automated review is complete, the lower-stack blockers are resolved, and the child is retargeted with exact-head CI, this remains a strong merge candidate.

@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

@Ingwannu: I will review the current PR layer against its stacked base. I will check the per-field provenance logic, rejection diagnostics, and affected tests.

✅ Action performed

Review finished.

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.

@Ingwannu Ingwannu 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.

Approved for this stacked layer at exact head 772d375f. The per-field provenance model keeps name, arguments, and id independent, accepts non-string repeats only after that exact field has a canonical value, and preserves call-specific fail-closed diagnostics without logging attacker-controlled values. Focused validation passed 97/97 at the stack tip; exact-head CI is now green on all reported platforms, and the requested CodeRabbit review produced no actionable findings.

This approval is for the #2165 layer only. Do not merge it out of order: resolve the lower-stack blockers, retarget this child to dev after its parent lands, and require fresh exact-head CI on the retargeted PR.

@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 56 / 80

#2155를 가져온 거임. 지금 스트림 루프가 pending 조회 전에 isInvalidStreamStringField를 무조건 봄. zen/go가 continuation에 function.name을 객체로 반복하면 앞 청크 이름 받아놓고도 502. #1731은 null만 패딩이었어서 객체는 죽음.

#2155 구멍 둘이 여기서 막힘. 원본은 args 검사를 hasCanonicalName에 묶음. 이름만 있고 args 없는 다음 청크가 객체 args면 침묵하고 페이로드가 날아감. 이번엔 PendingToolCall.sawArgumentsString. 빈 문자열도 본 거임. 나중에 이상한 반복은 패딩. id도 같은 필드 단위. 진단은 거절 지점에서 넘김. 상태 없는 리스캔이 콜0 패딩을 콜1 결함으로 덮어씀.

#2156이랑 다른 구멍임. 이건 패딩 502. 저건 finish_reason 없이 툴콜 잘림. 이 PR로 #2156 닫지 말 것. 스택 레이어 5. 베이스 #2164. #2151이랑 openai-chat 구간이 달라서 코드 의존은 아님. types.ts/config.ts 안 건드림. #2155는 머지되면 닫고 드래프트 리베이스하지 말 것.

점수는 56임. 맞음. 2.28. #2164 뒤.

해결방안: #2164 다음. sawArgumentsString 테스트 유지. 진단 override 유지.

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

@lidge-jun
lidge-jun changed the base branch from codex/absorb-opencode-go-quota-siblings to dev August 20, 2026 02:56
@lidge-jun
lidge-jun merged commit 41689b3 into dev Aug 20, 2026
30 of 33 checks passed
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.

2 participants