Skip to content

Sanitize null tool_calls[].type in proxied chat history for GPT-5.4#3101

Merged
lpcox merged 3 commits into
mainfrom
copilot/fix-null-tool-call-type-error
May 13, 2026
Merged

Sanitize null tool_calls[].type in proxied chat history for GPT-5.4#3101
lpcox merged 3 commits into
mainfrom
copilot/fix-null-tool-call-type-error

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 13, 2026

Bug Fix

What was the bug?

GPT-5.4 requests coming from Copilot CLI could include prior messages with tool_calls[].type = null (notably after edit-tool turns), which OpenAI rejects with HTTP 400 (expected 'function'|'alltools'|'custom'). Retry logic could not recover because the same poisoned history was resent.

How did you fix it?

  • Request-history sanitization in API proxy
    • Added a targeted sanitizer in containers/api-proxy/proxy-request.js that runs on outbound JSON bodies for POST/PUT/PATCH.
    • For each messages[*].tool_calls[*]:
      • type: null + valid function payload → normalize to type: "function".
      • type: null without usable function payload → drop malformed entry.
  • Behavioral guardrails
    • No-op for non-JSON payloads and requests without messages.
    • Leaves already-valid tool calls untouched.
  • Regression coverage
    • Extended containers/api-proxy/server.proxy.test.js with focused cases for:
      • null-type normalization path
      • malformed null-type drop path
if (Object.hasOwn(toolCall, 'type') && toolCall.type === null) {
  if (toolCall.function && typeof toolCall.function === 'object') {
    nextToolCalls.push({ ...toolCall, type: 'function' });
  } else {
    // drop malformed entry
  }
}

Testing

Added regression tests asserting forwarded request bodies are sanitized before upstream dispatch for both:

  1. null -> "function" normalization
  2. malformed null-type tool-call removal

Copilot AI changed the title [WIP] Fix null tool call type error in GPT-5.4 edit tool Sanitize null tool_calls[].type in proxied chat history for GPT-5.4 May 13, 2026
Copilot finished work on behalf of lpcox May 13, 2026 13:53
Copilot AI requested a review from lpcox May 13, 2026 13:53
@lpcox lpcox marked this pull request as ready for review May 13, 2026 14:24
@lpcox lpcox requested a review from Mossaka as a code owner May 13, 2026 14:24
Copilot AI review requested due to automatic review settings May 13, 2026 14:24
@github-actions
Copy link
Copy Markdown
Contributor

Smoke Test Results

  • ❌ GitHub API: Failed (auth not available in this environment)
  • ✅ Playwright: Passed (GitHub.com title contains "GitHub")
  • ✅ File verify: Passed (marker file exists with timestamp)

Total: 2/3 PASS

💥 [THE END] — Illustrated by Smoke Claude

@github-actions github-actions Bot mentioned this pull request May 13, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Smoke Test: Codex

PRs reviewed: #3080 Remove dead __testing export from host-iptables public API; #3077 perf(export-audit): move phases 1–3 to pre-steps, cut ~75% token usage
GitHub PR review: ✅
SafeInputs GH: ❌ unavailable
Playwright GitHub title: ✅
Tavily search: ❌ no callable tools
File write/bash read/build: ✅
Discussion comment: ❌ tool unavailable
Overall status: FAIL

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • registry.npmjs.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "registry.npmjs.org"

See Network Configuration for more information.

🔮 The oracle has spoken through Smoke Codex

@github-actions
Copy link
Copy Markdown
Contributor

Smoke Test: Copilot BYOK (Offline) Mode

Test Result
1. GitHub MCP connectivity ❌ GitHub MCP returned 401 (unauthenticated)
2. GitHub.com HTTP connectivity ⚠️ No data (template vars not expanded)
3. File write/read ⚠️ No data (template vars not expanded)
4. BYOK inference (api-proxy → api.githubcopilot.com)

Running in BYOK offline mode (COPILOT_OFFLINE=true) via api-proxy → api.githubcopilot.com.

Overall status: FAIL — pre-step template variables were not substituted; tests 1–3 could not be verified.

🔑 BYOK report filed by Smoke Copilot BYOK

@github-actions
Copy link
Copy Markdown
Contributor

🧪 Smoke Test Results — Copilot Engine

Test Result
GitHub MCP connectivity ❌ (401 auth error)
GitHub.com HTTP connectivity
File write/read ✅ (smoke-test-copilot-25803492205.txt verified)

Overall: FAIL — MCP tool returned 401 on PR list query.

PR triggered by @lpcox. No assignees detected.

📰 BREAKING: Report filed by Smoke Copilot

📰 BREAKING: Report filed by Smoke Copilot

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a proxying bug where Copilot CLI chat history could include messages[].tool_calls[].type = null, causing upstream OpenAI-compatible endpoints (e.g., GPT-5.4) to reject requests with HTTP 400 and making retries ineffective.

Changes:

  • Added request-body sanitization in the API proxy to normalize tool_calls[].type: null to "function" when a function payload exists, otherwise dropping malformed tool calls.
  • Added regression tests ensuring the proxied outbound body is sanitized before upstream forwarding.
Show a summary per file
File Description
containers/api-proxy/proxy-request.js Adds outbound JSON-body sanitization for null tool_calls[].type and logs normalization/drop counts.
containers/api-proxy/server.proxy.test.js Extends proxy request tests to assert normalization and malformed-entry dropping behavior.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 1

Comment on lines +695 to +706
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
const sanitized = sanitizeNullToolCallTypes(body);
if (sanitized) {
body = sanitized.body;
logRequest('info', 'request_sanitized', {
request_id: requestId,
provider,
normalized_tool_calls: sanitized.normalizedCount,
dropped_tool_calls: sanitized.droppedCount,
});
}
}
@github-actions
Copy link
Copy Markdown
Contributor

Smoke Test Results\n\n- GitHub MCP Testing: ❌ (mcpscripts command not found)\n- GitHub.com Connectivity: ❌ (SSL error 35)\n- File Writing Testing: ✅\n- Bash Tool Testing: ✅\n\nOverall status: FAIL

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • localhost

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "localhost"

See Network Configuration for more information.

💎 Faceted by Smoke Gemini

@github-actions
Copy link
Copy Markdown
Contributor

🏗️ Build Test Suite Results

Ecosystem Project Build/Install Tests Status
Bun elysia 1/1 passed ✅ PASS
Bun hono 1/1 passed ✅ PASS
C++ fmt N/A ✅ PASS
C++ json N/A ✅ PASS
Deno oak N/A 1/1 passed ✅ PASS
Deno std N/A 1/1 passed ✅ PASS
.NET hello-world N/A ✅ PASS
.NET json-parse N/A ✅ PASS
Go color 1/1 passed ✅ PASS
Go env 1/1 passed ✅ PASS
Go uuid 1/1 passed ✅ PASS
Java gson 1/1 passed ✅ PASS
Java caffeine 1/1 passed ✅ PASS
Node.js clsx All passed ✅ PASS
Node.js execa All passed ✅ PASS
Node.js p-limit All passed ✅ PASS
Rust fd 1/1 passed ✅ PASS
Rust zoxide 1/1 passed ✅ PASS

Overall: 8/8 ecosystems passed — ✅ PASS

Generated by Build Test Suite for issue #3101 · ● 4.9M ·

@github-actions
Copy link
Copy Markdown
Contributor

Smoke Test Results

  • Redis PING: ❌ (no response)
  • PostgreSQL pg_isready: ❌ (no response)
  • PostgreSQL SELECT 1: ❌ (no response)

Overall: FAILhost.docker.internal unreachable on both ports 6379 and 5432.

🔌 Service connectivity validated by Smoke Services

@github-actions
Copy link
Copy Markdown
Contributor

Chroot Smoke Test Results

Runtime Host Version Chroot Version Match?
Python Python 3.12.13 Python 3.12.3
Node.js v24.14.1 v20.20.2
Go go1.22.12 go1.22.12

Overall: FAILED — Python and Node.js versions differ between host and chroot environment.

Tested by Smoke Chroot

@lpcox lpcox merged commit 16dd904 into main May 13, 2026
67 of 74 checks passed
@lpcox lpcox deleted the copilot/fix-null-tool-call-type-error branch May 13, 2026 14:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[awf] copilot-harness: GPT-5.4 edit tool fails with null tool_call type (400 error)

3 participants