Skip to content

fix(transport): guard against None allowed_tools in _apply_skills_defaults#963

Open
Aispotlightlab wants to merge 1 commit into
anthropics:mainfrom
Aispotlightlab:fix/none-iterable-allowed-tools
Open

fix(transport): guard against None allowed_tools in _apply_skills_defaults#963
Aispotlightlab wants to merge 1 commit into
anthropics:mainfrom
Aispotlightlab:fix/none-iterable-allowed-tools

Conversation

@Aispotlightlab
Copy link
Copy Markdown

Summary

SubprocessCLITransport._apply_skills_defaults calls list(self._options.allowed_tools) unconditionally (subprocess_cli.py:196). While the type annotation declares list[str] with default_factory=list, the ClaudeAgentOptions dataclass does not validate the attribute at runtime — wrapper libraries can (and do) assign None to express "no restriction".

When that happens, every ClaudeSDKClient.connect() dies with TypeError: 'NoneType' object is not iterable before the CLI even starts, and the failure surfaces as the opaque Unexpected error in Claude SDK log line.

Reproduced on both 0.1.81 and 0.2.82 (current latest).

Reproduction

from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions

opts = ClaudeAgentOptions(allowed_tools=None)  # or assign after construction
async with ClaudeSDKClient(options=opts) as c:  # crashes here
    ...

Traceback:

File "…/transport/subprocess_cli.py", line 423, in connect
    cmd = self._build_command()
File "…/transport/subprocess_cli.py", line 253, in _build_command
    self._apply_skills_defaults()
File "…/transport/subprocess_cli.py", line 196, in _apply_skills_defaults
    allowed_tools: list[str] = list(self._options.allowed_tools)
TypeError: 'NoneType' object is not iterable

Real-world impact

claude-code-telegram (a popular wrapper, ~v1.6.0) does exactly this when its DISABLE_TOOL_VALIDATION=true is set — which is the standard recipe in its docs for adding third-party MCP servers (MemPalace, Mem0, custom MCPs, etc.). Every Telegram message then fails. Companion PR on their side: RichardAtCT/claude-code-telegram#206 — but a defensive guard in the SDK protects every other downstream wrapper too.

Fix

-        allowed_tools: list[str] = list(self._options.allowed_tools)
+        allowed_tools: list[str] = list(self._options.allowed_tools or [])

An empty list is the natural "no restriction" sentinel and is already handled correctly downstream — line 256 only emits --allowedTools when the list is truthy.

disallowed_tools is already None-safe (line 265 uses a truthy check), so it needs no change.

Test

Added test_build_command_allowed_tools_none in tests/test_transport.py that:

  • pre-patch: fails with the exact TypeError above
  • post-patch: passes and verifies no --allowedTools flag is emitted
$ pytest tests/test_transport.py::TestSubprocessCLITransport::test_build_command_allowed_tools_none -v
PASSED [100%]

🤖 Generated with Claude Code

`SubprocessCLITransport._apply_skills_defaults` calls
`list(self._options.allowed_tools)` unconditionally. While the type
annotation declares `list[str]` with `default_factory=list`, the
`ClaudeAgentOptions` dataclass does not validate the attribute at
runtime — wrapper libraries can (and do) assign `None` to express
"no restriction".

Real-world impact: `claude-code-telegram` sets
`options.allowed_tools = None` when `DISABLE_TOOL_VALIDATION=true`
(its recipe for adding third-party MCP servers). Every connect
attempt then dies with `TypeError: 'NoneType' object is not
iterable` inside `_build_command` — before the CLI even starts —
and the failure surfaces to end users as the opaque
`Unexpected error in Claude SDK`. Reproduced on 0.1.81 and 0.2.82.

Fix: `list(... or [])`. An empty list is the natural "no
restriction" sentinel and is already handled correctly downstream
(line 256 only emits `--allowedTools` when the list is truthy).

Added a regression test that fails before the patch and passes after.
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.

1 participant