Skip to content

fix(openai-chat): log redacted invalid tool-call shape - #1530

Merged
lidge-jun merged 3 commits into
devfrom
agent/diagnose-1483-openai-chat-shape
Aug 12, 2026
Merged

fix(openai-chat): log redacted invalid tool-call shape#1530
lidge-jun merged 3 commits into
devfrom
agent/diagnose-1483-openai-chat-shape

Conversation

@Ingwannu

@Ingwannu Ingwannu commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Summary

Closes no issue; supports diagnosis of #1483.

Verification

  • bun scripts/test.ts tests/openai-chat-hardening.test.ts tests/issue-452-empty-503.test.ts tests/debug.test.ts (66 passed, 0 failed)
  • bun run typecheck
  • bun run privacy:scan
  • git diff --check

To capture one redacted reproduction after this lands:

ocx debug provider on
ocx debug provider logs -f
# reproduce once
ocx debug provider off

Expected diagnostic shape:

[ocx:openai-chat:invalid-tool-calls] {"mode":"stream","reason":"tool_call_not_object","callIndex":1,"valueType":"null"}

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed. No user-visible behavior/configuration changes; existing provider-debug commands expose the diagnostic.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults. Diagnostics remain opt-in and values are never serialized.

Summary by CodeRabbit

  • Bug Fixes
    • Improved diagnostics when malformed tool-call data is received.
    • Added clearer identification of the processing mode, validation reason, item position, and value type.
    • Ensured sensitive tool-call values are never included in diagnostic logs.
    • Preserved existing invalid tool-call error behavior and validation precedence.

@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The OpenAI adapter now classifies malformed streamed and buffered tool-call payloads. It logs shape-only diagnostics before returning the existing invalid-tool-calls error. Tests cover logging, redaction, disabled defaults, validation precedence, and streaming metadata.

Changes

Tool-call diagnostics

Layer / File(s) Summary
Diagnosis and validation instrumentation
src/adapters/openai-chat.ts
The adapter classifies malformed tool-call containers, identifiers, names, arguments, and function fields. Streaming and buffered paths log the mode, reason, index, and value type without logging payload values. Existing fail-closed rejection behavior remains unchanged.
Diagnostic behavior tests
tests/openai-chat-hardening.test.ts
Tests verify diagnostic output, sensitive-value redaction, disabled default logging, validation precedence, streaming metadata, and cleanup of debug state between tests.

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

Possibly related PRs

Suggested reviewers: lidge-jun, wibias

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding redacted logging for invalid OpenAI Chat tool-call shapes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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 agent/diagnose-1483-openai-chat-shape

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.

Ingwannu and others added 3 commits August 12, 2026 23:19
…idation

Rebase follow-up on top of @Ingwannu's diagnostic commit. #1531 landed
between this PR being written and being rebased, and it moved the
validation the diagnostic describes.

The streamed path now validates function/name/arguments/id at ingest
rather than only checking container and member shapes, so the diagnostic
no longer skips per-member inspection in stream mode, and the three new
ingest rejections log through the same helper. Blank and whitespace-only
names are now rejected on the buffered path; reporting that as
name_invalid would claim a type problem for a correctly-typed value, so
it gets its own reason code.

A diagnostic that describes a boundary the code no longer has is worse
than none: it would send provider-compatibility work after the wrong
shape.
Review follow-up. The buffered validator checks the function container
first, then id/name/arguments types together, and only then the blank
name. The diagnostic checked id before the container and blank name
before arguments, so a payload carrying two defects at once was reported
under the wrong reason.

That matters precisely because of what this diagnostic is for: it exists
to point provider-compatibility work at the shape that was rejected. A
wrong reason code sends that work after the wrong shape.

Four tests each carry two defects at once, so only the matching order
produces the expected reason.
@lidge-jun
lidge-jun force-pushed the agent/diagnose-1483-openai-chat-shape branch from fdb26c6 to be101c3 Compare August 12, 2026 14:37
@lidge-jun

Copy link
Copy Markdown
Owner

Rebased onto current dev (d03755ee9) and pushed two commits on top of yours; your commit 933d00974 is preserved.

Why it conflicted. #1531 merged between this being written and being rebased, and it changed the exact validation this diagnostic describes. Two consequences:

The streamed path now validates function/name/arguments/id at ingest rather than only checking container and member shapes. diagnoseInvalidToolCalls explicitly continued in stream mode, which was right against the old code and wrong against the new — the three new ingest rejections would have produced no diagnostic at all. Stream mode now inspects those same fields and all three sites log through the helper.

The buffered path now rejects blank and whitespace-only names. The diagnostic would have reported that as tool_call_function_name_invalid — a type problem for a correctly-typed value — so it gets its own tool_call_function_name_blank code.

A second review round then caught a precedence bug in my own follow-up. The validator checks the function container first, then id/name/arguments types together, and only then the blank name. My diagnosis checked id before the container and blank name before arguments, so a payload carrying two defects at once reported the wrong reason. That is exactly the failure this diagnostic is supposed to prevent — a wrong reason code sends provider-compatibility work after the wrong shape. Fixed, with four tests that each carry two defects simultaneously so only the matching order passes.

Privacy was re-verified independently: reason codes and modes are finite literals, valueType is a fixed label or a bare typeof, and the buffer stores only the already-redacted serialized line. No caller-controlled string reaches the log.

On whether this is still worth landing now that #1531 fixed #1514: yes. #1531 fixed one known shape; this records which check rejected a payload, which is what makes the next provider incompatibility diagnosable instead of a guess. It stays opt-in and off by default.

Verification at be101c32b on a Linux runner (Bun 1.3.14): bun x tsc --noEmit exit 0; openai-chat-hardening + eof + parallel-stream 91 pass / 0 fail; 11-file adapter sweep 165 pass / 0 fail; privacy:scan passed. Your tests remain valid post-rebase and go red on dev without the source change.

@lidge-jun
lidge-jun marked this pull request as ready for review August 12, 2026 14:38

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

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 `@src/adapters/openai-chat.ts`:
- Around line 295-298: Update logInvalidToolCalls to return immediately when
isDebugEnabled() is false, before calling diagnoseInvalidToolCalls; retain the
existing diagnostic classification and debugProviderDiagnostic behavior when
debugging is enabled.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 83f1de84-cbf2-46c8-8095-70c0202ce456

📥 Commits

Reviewing files that changed from the base of the PR and between d03755e and be101c3.

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

Comment on lines +295 to +298
function logInvalidToolCalls(mode: "stream" | "response", rawToolCalls: unknown): void {
const diagnostic = diagnoseInvalidToolCalls(rawToolCalls, mode);
if (diagnostic) debugProviderDiagnostic("openai-chat", "invalid-tool-calls", { mode, ...diagnostic });
}

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.

🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Gate classification when debug diagnostics are disabled.

Line 296 calls diagnoseInvalidToolCalls before debugProviderDiagnostic checks the debug setting. A malformed array is already scanned by the validator. This adds a second scan even when diagnostics are disabled. Return before classification when isDebugEnabled() is false.

Proposed fix
 function logInvalidToolCalls(mode: "stream" | "response", rawToolCalls: unknown): void {
+  if (!isDebugEnabled()) return;
   const diagnostic = diagnoseInvalidToolCalls(rawToolCalls, mode);
   if (diagnostic) debugProviderDiagnostic("openai-chat", "invalid-tool-calls", { mode, ...diagnostic });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function logInvalidToolCalls(mode: "stream" | "response", rawToolCalls: unknown): void {
const diagnostic = diagnoseInvalidToolCalls(rawToolCalls, mode);
if (diagnostic) debugProviderDiagnostic("openai-chat", "invalid-tool-calls", { mode, ...diagnostic });
}
function logInvalidToolCalls(mode: "stream" | "response", rawToolCalls: unknown): void {
if (!isDebugEnabled()) return;
const diagnostic = diagnoseInvalidToolCalls(rawToolCalls, mode);
if (diagnostic) debugProviderDiagnostic("openai-chat", "invalid-tool-calls", { mode, ...diagnostic });
}
🤖 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/adapters/openai-chat.ts` around lines 295 - 298, Update
logInvalidToolCalls to return immediately when isDebugEnabled() is false, before
calling diagnoseInvalidToolCalls; retain the existing diagnostic classification
and debugProviderDiagnostic behavior when debugging is enabled.

@lidge-jun

Copy link
Copy Markdown
Owner

Full-suite result at be101c32b on the Linux runner (Bun 1.3.14):

11341 pass
0 fail
EXIT=0

All four Linux CI shards are green here as well.

@lidge-jun
lidge-jun merged commit ccd3a25 into dev Aug 12, 2026
65 of 69 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