Skip to content

fix(claude-agents)!: honour the config's max_tokens and disable thinking unless the config asks - #237

Open
XieX wants to merge 3 commits into
mainfrom
xie/AIC-3585/claude-agents-max-tokens
Open

XieX wants to merge 3 commits into
mainfrom
xie/AIC-3585/claude-agents-max-tokens

Conversation

@XieX

@XieX XieX commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

AIC-3585. Spec: launchdarkly/ai-sdks-monorepo#110. The same change for TypeScript is launchdarkly/js-ai-sdk#183; Go is not affected.

claude-agents ran with the Claude Code CLI's own defaults instead of the AI Config's. The config's max_tokens was dropped, because ClaudeAgentOptions has no output-token field. With no thinking setting, the CLI chose one: a 31,999-token budget on claude-haiku-4-5. Python wasn't checked live; the code path is the same as the TypeScript one that was.

_options_parameters (handler.py) is shared by the handler's invoke and stream and by to_claude_agents. It now:

  • Sends max_tokens to the CLI. A positive integer max_tokens up to 2^53 − 1 becomes env={"CLAUDE_CODE_MAX_OUTPUT_TOKENS": "<n>"}. That's the same bound as the JS SDK's safe-integer check. The Agent SDK adds that to the inherited os.environ. A whole-number float such as 200.0 counts as 200, as it does in JavaScript. Any other value is dropped. With no max_tokens, env stays {} and the CLI keeps its default, as before.
  • Turns thinking off by default. When the config sets neither thinking nor max_thinking_tokens, it passes thinking={"type": "disabled"}. A malformed thinking that gets dropped counts as not set. An explicit thinking, or max_thinking_tokens on its own, is passed as before. The CLI leaves thinking out for models that reject disabled thinking, such as claude-fable-5.

max_tokens is read separately rather than added to _CLAUDE_AGENT_OPTIONS_FORWARDED_KEYS, since it isn't a ClaudeAgentOptions field and the drift test checks that list against the dataclass. env stays in the excluded list: the max_tokens value is the only thing written into it. tests/forwarding_spec.py adds max_tokens to the Claude Agents list.

Breaking change

A config that sets no thinking now runs without thinking. Before, the CLI chose: adaptive thinking on models that support it, or a fixed budget on others. Configs that relied on that must set thinking in their model parameters, for example {"type": "adaptive"}. The README's new "Output limit and thinking" section has the migration note.

Tests

  • A new TestOutputLimitAndThinkingDefault, on both invoke and stream, covers:
    • max_tokens: 200 sets the variable and disables thinking, checked against the SDK's own CLI command builder.
    • The environment the SDK's transport gives the CLI still holds os.environ. open_process is stubbed to capture it.
    • 200.0 is read as 200.
    • With no max_tokens, env is empty.
    • 0, -5, 1.5, 2**53, inf, "200", True, None and a dict are each dropped.
    • effort alone gets the default, and thinking with max_thinking_tokens passes both as given.
    • A configured thinking replaces the default.
    • max_thinking_tokens on its own leaves thinking unset.
  • TestNativeGraphModelParameters covers four node cases: max_tokens (and checks that the real SDK builds --thinking disabled for it), its own thinking, an invalid max_tokens, and max_thinking_tokens alone (--max-thinking-tokens 2048).
  • Seven existing TestThinkingIsRebuilt / TestMalformedObjectValuesAreDropped cases expected a dropped thinking to be None, and now expect the default.
  • max_tokens was removed from test_ui_keys_the_sdk_rejects_are_dropped_without_raising, since it's no longer dropped.
  • make test passes (3686 passed, 18 skipped). make lint, make format-check and mypy pass.

🤖 Generated with Claude Code


Note

Overview
Breaking: configs that omit thinking no longer inherit Claude Code’s implicit thinking defaults; the handler now passes thinking={"type": "disabled"} unless the config sets thinking or max_thinking_tokens (malformed thinking falls back to the same default).

The shared _options_parameters path (handler invoke/stream and to_claude_agents) now honours max_tokens: valid positive integers (including whole floats, capped at 2^53−1) are sent to the CLI as env={"CLAUDE_CODE_MAX_OUTPUT_TOKENS": "<n>"} because ClaudeAgentOptions has no output-token field. Invalid values are ignored; omitting max_tokens leaves CLI defaults unchanged.

README documents output limits, thinking behaviour, and migration. Cross-SDK forwarding_spec adds max_tokens to the Claude Agents forwarded list; new handler, native-graph, and updated thinking-rebuild tests lock in the env/thinking rules and CLI command shape.

Reviewed by Cursor Bugbot for commit cd91993. Bugbot is set up for automated code reviews on this repo. Configure here.

…ing unless the config asks

For AIC-3585. The handler ran with the Claude Code CLI's own defaults: the
config's max_tokens was dropped, and with no thinking setting the CLI chose
one (a 31,999-token budget on claude-haiku-4-5).

max_tokens now reaches the CLI as CLAUDE_CODE_MAX_OUTPUT_TOKENS in env,
which the Agent SDK adds to os.environ. When the config sets neither
thinking nor max_thinking_tokens, the handler passes
thinking={"type": "disabled"}.

BREAKING CHANGE: a config that sets no thinking now runs without thinking.
Configs that relied on the CLI's adaptive-thinking default must set
thinking in their model parameters, for example {"type": "adaptive"}.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@XieX
XieX requested a review from jeffdupont October 28, 2026 18:21
…'s test gaps

Drop a max_tokens above 2^53 - 1, as the JS SDK does for a value that is
not a safe integer, so both SDKs send the same thing for the same config.

Tests: the environment the Agent SDK builds for the CLI still holds
os.environ; effort alone and thinking with max_thinking_tokens; and a
native-graph node's CLI command carries --thinking disabled, plus the
invalid max_tokens and max_thinking_tokens-only node cases. README notes
that a settings-file env wins and that some models reject disabled
thinking.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

This matches mono#110 and js-ai-sdk#183. The same max_tokens bounds (a positive whole number up to 2^53 - 1, with 200.0 read as 200), the same disabled-thinking default, and the same rule that a dropped malformed thinking counts as not set. I checked env against the SDK: no host or skills option also sets env, so nothing overwrites it. One small test gap, below, is the same one I raised on the JS PR.

Comment thread packages/claude-agents/tests/test_native_graph.py
…ormed thinking and effort alone

Match the JS graph tests: a node with a thinking that has no budget, and
a node with only effort, each get thinking={"type": "disabled"}.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

cd91993 covers the test gap from my last review: the native-graph test now pins the disabled default for a malformed thinking ({"type": "enabled"} with no budget) and for effort alone, so every output-limit case mono#110 lists runs through invoke, stream and a graph node. Nothing else changed since 837053d, and the behaviour still matches js-ai-sdk#183.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants