Skip to content

Claude (OAuth) provider sends OpenAI-only parallel_tool_calls field, causing 400 from Anthropic #11

Description

@benwoody

Summary

When AI Provider is set to Claude (OAuth) and a Claude setup-token is supplied, every request fails with HTTP 400 from Anthropic:

Extra inputs are not permitted (parallel_tool_calls)

The token is valid; the request body is malformed. AndyClaw is sending an OpenAI-only field to Anthropic's Messages API, which rejects unknown fields.

Steps to reproduce

  1. Stock Android (or ethOS — same code path).
  2. Settings → AI Provider → Claude (OAuth).
  3. Paste an OAuth token from claude setup-token (sk-ant-oat01-…).
  4. Model: claude-sonnet-4-6 (any Claude model reproduces it).
  5. Send any prompt that triggers a tool call (i.e. anything in the agent loop).

Expected

Request succeeds; agent runs normally.

Actual

HTTP 400 from Anthropic with parallel_tool_calls flagged as an unknown input. Agent halts on the first tool turn.

Root cause

File: app/src/main/java/org/ethereumphone/andyclaw/llm/AnthropicClient.kt
Lines: ~223–226

request.tools?.let { tools ->
    put("tools", kotlinx.serialization.json.JsonArray(tools))
    put("parallel_tool_calls", kotlinx.serialization.json.JsonPrimitive(request.parallelToolCalls))
}

parallel_tool_calls is an OpenAI Chat Completions field. Anthropic's Messages API does not accept it — the equivalent is tool_choice.disable_parallel_tool_use (boolean, inverted). Because the field is
added unconditionally whenever tools is non-null, the Budget Mode "parallel tool calls" toggle does not avoid it (it only changes the value, not whether the field is sent).

The same field is set in OpenAiFormatAdapter.kt, where it's correct.

Suggested fix

Either:

Option A — drop the field from the Anthropic request entirely:

  request.tools?.let { tools ->
      put("tools", kotlinx.serialization.json.JsonArray(tools))
  }

Option B — translate to the Anthropic equivalent:

  request.tools?.let { tools ->
      put("tools", kotlinx.serialization.json.JsonArray(tools))
      if (!request.parallelToolCalls) {
          put("tool_choice", kotlinx.serialization.json.buildJsonObject {
              put("type", kotlinx.serialization.json.JsonPrimitive("auto"))
              put("disable_parallel_tool_use", kotlinx.serialization.json.JsonPrimitive(true))
          })
      }
  }

Option B preserves the Budget Mode toggle's intent (off → tools execute serially) using Anthropic's actual API. Option A is fine if the toggle is considered system-prompt-only for the Anthropic path.

Related question for maintainers

Requests are routed through https://api.markushaas.com/api/premium-llm-andy rather than api.anthropic.com directly. When using the OAuth token path (Authorization: Bearer sk-ant-oat01-…), is the token
forwarded to Anthropic so usage bills against the user's Claude Pro/Max subscription, or is it terminated at the proxy? Worth documenting either way.

Environment

  • Device: dGEN1
  • Provider: Claude (OAuth)
  • Model: claude-sonnet-4-6

Screenshot

Image

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions