9.3 KiB
Critical Issue: Grok Outputting xAI Function Call Format as Text
Discovered: 2025-11-11 (15:45) Severity: CRITICAL - Breaks tool calling entirely Model Affected: x-ai/grok-code-fast-1 Status: Model behavior issue - Grok uses xAI format instead of OpenAI format
🔴 The Problem
What User Experienced
UI shows:
- "Reviewing package configuration"
- Package.json update text
- Then literally:
<xai:function_call name="Read"> - "Assistant:"
- Another malformed:
<xai:function_call name="Read">xai:function_call - System stuck, waiting
Root Cause: Incompatible Function Call Format
Grok is outputting xAI's XML-style function calls as TEXT:
<xai:function_call name="Read"></xai:function_call>
Instead of OpenAI's JSON-style tool calls:
{
"tool_calls": [{
"id": "call_abc123",
"type": "function",
"function": {
"name": "Read",
"arguments": "{\"file_path\":\"/path/to/file\"}"
}
}]
}
📊 Evidence from Logs
From logs/claudish_2025-11-11_04-30-31.log
Timeline 04:45:09:
[2025-11-11T04:45:09.636Z] Encrypted reasoning detected
[2025-11-11T04:45:09.636Z] Sending content delta: <x
[2025-11-11T04:45:09.636Z] Sending content delta: ai
[2025-11-11T04:45:09.636Z] Sending content delta: :function
[2025-11-11T04:45:09.637Z] Sending content delta: _call
[2025-11-11T04:45:09.637Z] Sending content delta: >
[2025-11-11T04:45:09.661Z] finish_reason: "stop"
[2025-11-11T04:45:09.691Z] Stream closed properly
Key observations:
- Grok sent
<xai:function_call>as regulardelta.content(text) - NOT sent as
delta.tool_calls(proper tool call) - Immediately finished with
finish_reason: "stop" - Our proxy correctly forwarded it as text (not our bug!)
🎯 Why This Happens
xAI's Native Format vs OpenRouter
xAI's Grok models have TWO function calling modes:
-
Native xAI format (XML-style):
<xai:function_call name="Read"> <xai:parameter name="file_path">/path/to/file</xai:parameter> </xai:function_call> -
OpenAI-compatible format (JSON in
tool_callsfield):{ "tool_calls": [{ "function": {"name": "Read", "arguments": "{...}"} }] }
The Problem: When Grok is used through OpenRouter, it should use OpenAI format, but sometimes it:
- Gets confused about which format to use
- Outputs xAI XML format as text instead of proper tool calls
- This breaks Claude Code's tool execution
🔍 Related xAI Documentation & Research Findings
From Official xAI Documentation
docs.x.ai/docs/guides/function-calling:
- xAI enables connecting models to external tools and systems
- Function calling enables LLMs to use external tools via RPC-style calls
- Grok 4 includes native tool use and real-time search integration
- Supports up to 128 functions per request
- Uses OpenAI-compatible API format externally
From Internet Research (2025)
CONFIRMED ISSUES WITH GROK + OPENROUTER:
-
Missing "created" Field (Multiple reports):
- Tool call responses from Grok via OpenRouter missing "created" field
- Causes parsing errors in clients (Zed editor, Cline, others)
- Error: "missing field
created" when using grok-code-fast-1 - Reported in Zed Issue #37022, #36994, #34185
-
Tool Calls Don't Work (Widespread):
- Grok Code Fast 1 won't answer anything unless using "Minimal" mode
- Tool calling completely broken with OpenRouter
- Multiple platforms affected (Zed, VAPI, Home Assistant)
-
"Invalid Grammar Request" Errors:
- xAI sometimes rejects structured output requests
- Returns 502 status with "Upstream error from xAI: undefined"
- Related to grammar/structured output incompatibilities
-
Internal XML Format:
- Grok uses XML-inspired format internally:
<xai:function_call> - Should convert to JSON for OpenAI-compatible API
- Conversion sometimes fails, outputting XML as text
- Grok uses XML-inspired format internally:
-
Multiple Function Call Limitations:
- Report: "XAI cannot invoke multiple function calls"
- May have issues with parallel tool execution
Possible causes:
- OpenRouter not properly translating Claude tool definitions to xAI format
- Grok getting confused by the tool schema
- Grok defaulting to XML output when tool calling fails
- xAI API returning errors without proper "created" field
- Grammar/structured output requests being rejected by xAI
- Context window or prompt causing model confusion
💡 Possible Solutions
Option 1: Detect and Parse xAI XML Format (Proxy Fix)
Add XML parser to detect xAI function calls in text content:
// In streaming handler, after sending text_delta
const xaiCallMatch = accumulatedText.match(/<xai:function_call name="([^"]+)">(.*?)<\/xai:function_call>/s);
if (xaiCallMatch) {
const [fullMatch, toolName, xmlParams] = xaiCallMatch;
// Parse XML parameters to JSON
const params = parseXaiParameters(xmlParams);
// Create synthetic tool call
const syntheticToolCall = {
id: `synthetic_${Date.now()}`,
name: toolName,
arguments: JSON.stringify(params)
};
// Send as proper tool_use block
sendSSE("content_block_start", {
index: currentBlockIndex++,
content_block: {
type: "tool_use",
id: syntheticToolCall.id,
name: syntheticToolCall.name
}
});
// Send tool input
sendSSE("content_block_delta", {
index: currentBlockIndex - 1,
delta: {
type: "input_json_delta",
partial_json: syntheticToolCall.arguments
}
});
// Close tool block
sendSSE("content_block_stop", {
index: currentBlockIndex - 1
});
}
Pros:
- Works around Grok's behavior
- Translates xAI format to Claude format
- No model changes needed
Cons:
- Complex parsing logic
- May have edge cases (nested XML, escaped content)
- Feels like a hack
- Doesn't fix root cause
Option 2: Force OpenAI Tool Format (Request Modification)
Modify requests to Grok to force OpenAI tool calling:
// In proxy-server.ts, before sending to OpenRouter
if (model.includes("grok")) {
// Add system message forcing OpenAI format
claudeRequest.messages.unshift({
role: "system",
content: "IMPORTANT: Use OpenAI-compatible tool calling format with tool_calls field. Do NOT use <xai:function_call> XML format."
});
}
Pros:
- Simple to implement
- Addresses root cause
- Clean solution
Cons:
- May not work if model ignores instruction
- Adds tokens to every request
- Needs testing
Option 3: Switch Model Recommendation
Remove Grok from recommended models until tool calling is fixed:
- Current:
x-ai/grok-code-fast-1is top recommendation - Change to: Use
openai/gpt-5-codexorminimax/minimax-m2instead - Add warning: "Grok has known tool calling issues with Claude Code"
Pros:
- Immediate fix for users
- No code changes needed
- Honest about limitations
Cons:
- Loses Grok's benefits (speed, cost)
- Doesn't fix underlying issue
- Users can still select Grok manually
Option 4: Report to xAI/OpenRouter
File bug reports:
- To xAI: Grok outputting XML format when OpenAI format expected
- To OpenRouter: Tool calling translation issues with Grok
Pros:
- Gets issue fixed at source
- Benefits all users
- Professional approach
Cons:
- Takes time
- Out of our control
- May not be prioritized
🧪 Testing the Issue
Reproduce
./dist/index.js --model x-ai/grok-code-fast-1 --debug
# Then in Claude Code, trigger any tool use
# Example: "Read package.json"
Expected behavior: See <xai:function_call> in output, UI stuck
Test Fix (if implemented)
# After implementing Option 1 or 2
./dist/index.js --model x-ai/grok-code-fast-1
# Verify:
1. Tool calls work properly
2. No xAI XML in output
3. Claude Code executes tools
📝 Recommended Action
Short term (Immediate):
- Option 3: Update README to warn about Grok tool calling issues
- Move Grok lower in recommended model list
- Suggest alternative models for tool-heavy workflows
Medium term (This week):
- Option 4: File bug reports with xAI and OpenRouter
- Option 2: Try forcing OpenAI format via system message
- Test if fix works
Long term (If no upstream fix):
- Option 1: Implement xAI XML parser as fallback
- Add comprehensive tests
- Document as "xAI compatibility layer"
🔗 Related Issues
- GROK_REASONING_PROTOCOL_ISSUE.md - Visible reasoning field
- GROK_ENCRYPTED_REASONING_ISSUE.md - Encrypted reasoning freezing
Pattern: Grok has multiple xAI-specific behaviors that need translation:
- Reasoning in separate field ✅ Fixed
- Encrypted reasoning ✅ Fixed
- XML function calls ❌ NOT FIXED (this issue)
📈 Impact
Severity: CRITICAL
- Grok completely unusable for tool-heavy workflows
- Affects any task requiring Read, Write, Edit, Grep, etc.
- UI appears stuck, confusing user experience
Affected Users:
- Anyone using
x-ai/grok-code-fast-1with Claude Code - Especially impacts users following our "recommended models" list
Workaround:
- Switch to different model:
openai/gpt-5-codex,minimax/minimax-m2, etc. - Use Anthropic Claude directly (not through Claudish)
Status: Documented, awaiting fix strategy decision Priority: CRITICAL (blocks Grok usage entirely) Next Steps: Update README, file bug reports, test Option 2