# Forcing a Specific Tool Call in the Claude API

> Source: <https://dev.to/multigrid/forcing-a-specific-tool-call-in-the-claude-api-d5j>
> Published: 2026-08-12 22:20:42+00:00

`tool_choice`

decides whether the model may call a tool, must call some tool, must call one particular tool, or may not call any. The interesting part is not the parameter — it is what each setting removes from the response.

`tool_choice`

is a top-level object with a `type`

discriminator. All four forms:

```
"tool_choice": {"type": "auto"}                              // default
"tool_choice": {"type": "any"}                               // some tool, model picks
"tool_choice": {"type": "tool", "name": "get_deploy_status"}  // this exact tool
"tool_choice": {"type": "none"}                              // no tools this turn
```

`auto`

is what you get when the parameter is omitted and `tools`

is present. `none`

is what you get when there are no tools at all, and setting it explicitly while leaving the `tools`

array in place is the way to keep the tool definitions in the cached prefix while forbidding calls for one turn.

With `auto`

, the model chooses. The response may contain only text, with `stop_reason: "end_turn"`

:

```
{
  "content": [{"type": "text", "text": "Deploy ids look like dpl_XXXX — which one?"}],
  "stop_reason": "end_turn"
}
```

Or text plus one or more `tool_use`

blocks, with `stop_reason: "tool_use"`

. Both are normal and your code has to handle both.

With `any`

or `tool`

, the model is required to call. `stop_reason`

is `"tool_use"`

on every response, and — this is the part worth internalising — the preamble text disappears. A forced response is the tool call and nothing else:

```
// tool_choice: {"type": "tool", "name": "get_deploy_status"}
{
  "content": [
    {"type": "tool_use", "id": "toolu_01B7…", "name": "get_deploy_status",
     "input": {"deploy_id": "dpl_8f21"}}
  ],
  "stop_reason": "tool_use"
}
```

There is no text block to render, and if your UI expected one it now shows a blank turn. That is the trade: you gain a guaranteed shape and lose the model’s ability to say “I need more information first”.

The difference between `any`

and `tool`

is only who picks. With `any`

and three tools declared, you will get exactly one of the three and you will not know which until you read `name`

. With `tool`

you know before you send the request.

The most common reason to force a specific tool has nothing to do with tools. A tool is a JSON Schema the model must fill in, so declaring one tool and forcing it is a way to get a guaranteed object back:

```
{
  "model": "claude-opus-4-6",
  "max_tokens": 1024,
  "tools": [{
    "name": "record_incident",
    "description": "Record the structured fields of an incident report.",
    "input_schema": {
      "type": "object",
      "properties": {
        "severity": {"type": "string", "enum": ["sev1", "sev2", "sev3"]},
        "component": {"type": "string"},
        "customer_impact": {"type": "boolean"}
      },
      "required": ["severity", "component", "customer_impact"]
    }
  }],
  "tool_choice": {"type": "tool", "name": "record_incident"},
  "messages": [{"role": "user", "content": "<incident channel transcript>"}]
}
```

You never execute `record_incident`

. You read `content[0].input`

and that is your parsed object — no fenced code blocks to strip, no leading “Here is the JSON” to discard. The `enum`

constrains `severity`

to three values in a way a prompt instruction does not. This pattern predates structured outputs and remains useful because it works on every model that supports tools; see [getting JSON out via tool_choice](https://multigrid.ai/learn/claude-json-output-tool-choice) for the full technique.

There is one thing to check that the schema does not guarantee. A forced call means the model produced *a* value for each required field, not that the value was in the document. Asked to extract a severity from a transcript that never states one, the model must still emit one of the three enum members. The schema constrains the shape and nothing else, so a nullable field or an explicit `"unknown"`

member is usually worth adding — it gives the model somewhere honest to put the answer.

This is the failure that costs people real money, and it comes from treating `tool_choice`

as configuration rather than as a per-request decision.

Force a tool, get the call, execute it, append the `tool_result`

, and send the conversation back with the same request-building code — which still sets `tool_choice: {"type": "tool"}`

. The model is again required to call, so it calls again. You execute again, append again, and send again. Nothing errors. Every response is a valid 200 with `stop_reason: "tool_use"`

, and the loop runs until your iteration cap catches it or, if there is no cap, until someone notices the spend.

```
// wrong: the same tool_choice on every iteration
while (msg.stop_reason === "tool_use") {
  msg = await client.messages.create({
    model, max_tokens, tools, messages,
    tool_choice: { type: "tool", name: "record_incident" },   // never relaxes
  });
}

// right: force the first call only, then let the model finish
let toolChoice = { type: "tool", name: "record_incident" };
while (true) {
  const msg = await client.messages.create({
    model, max_tokens, tools, messages, tool_choice: toolChoice,
  });
  toolChoice = { type: "auto" };            // relax after the first turn
  if (msg.stop_reason !== "tool_use") break;
  // … execute, append assistant turn and tool_result …
}
```

The same trap exists in a milder form with `{"type": "any"}`

, which is worse to diagnose because the model varies which tool it calls and the transcript looks like exploration rather than a stuck loop.

The general rule: `tool_choice`

belongs to a turn, not to a conversation. If the reason you forced the call was to guarantee the first action, relax it the moment that action has happened. And if you are using the forced-tool pattern purely for extraction — one tool, no execution, read `input`

and stop — then there is no second turn at all, and the loop is the bug rather than the design.

`auto`

.`auto`

: the common shape is to force the initial call and then let the model decide whether it needs more.`tool_choice`

is not part of the cached prefix in the way the `tools`

array is, so you can vary it per request without paying to re-cache the tool definitions.Any `tool_choice`

value can carry `disable_parallel_tool_use`

, which caps the response at one `tool_use`

block:

```
"tool_choice": {"type": "auto", "disable_parallel_tool_use": true}
```

By default the model may emit several calls in one turn, which is faster when the calls are independent and wrong when they are not — a tool that mutates state usually wants to see the result of call one before deciding on call two. Setting the flag serialises the loop at the cost of a round trip per call.
