From 03cd2eca57600747397d6355aaf6b4119d0de85e Mon Sep 17 00:00:00 2001 From: anish Date: Wed, 20 May 2026 17:50:23 +0000 Subject: [PATCH] fix(options): auto-inject Skill tool when skills option is set with explicit tools When `skills="all"` or a skill list is set alongside an explicit `tools` list in Options, the SDK was not automatically injecting the "Skill" tool into the base tools list. This caused the system to be unable to invoke skills because the Skill tool wasn't available, even though it was configured in allowed_tools. Signed-off-by: anish --- .../_internal/transport/subprocess_cli.py | 10 +++- tests/test_transport.py | 51 +++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/claude_agent_sdk/_internal/transport/subprocess_cli.py b/src/claude_agent_sdk/_internal/transport/subprocess_cli.py index 833cba4c..3e86a4a1 100644 --- a/src/claude_agent_sdk/_internal/transport/subprocess_cli.py +++ b/src/claude_agent_sdk/_internal/transport/subprocess_cli.py @@ -241,10 +241,16 @@ def _build_command(self) -> list[str]: if self._options.tools is not None: tools = self._options.tools if isinstance(tools, list): - if len(tools) == 0: + # When skills are enabled, ensure "Skill" is in the tools list + # so the skill tool is available for the agent to invoke + tools_list = list(tools) # Copy to avoid mutating the original + if self._options.skills is not None and "Skill" not in tools_list: + tools_list.append("Skill") + + if len(tools_list) == 0: cmd.extend(["--tools", ""]) else: - cmd.extend(["--tools", ",".join(tools)]) + cmd.extend(["--tools", ",".join(tools_list)]) else: # Preset object - 'claude_code' preset maps to 'default' cmd.extend(["--tools", "default"]) diff --git a/tests/test_transport.py b/tests/test_transport.py index 1e61e9ad..def729e9 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -627,6 +627,57 @@ def test_build_command_skills_does_not_duplicate_entries(self): cmd = transport._build_command() assert cmd[cmd.index("--allowedTools") + 1] == "Skill(pdf)" + def test_build_command_skills_all_with_explicit_tools_adds_skill(self): + """When skills='all' is set with explicit tools list, 'Skill' is added to tools.""" + transport = SubprocessCLITransport( + prompt="test", + options=make_options( + tools=["WebSearch", "WebFetch", "Read"], + skills="all", + ), + ) + cmd = transport._build_command() + # Skill should be added to the tools list + assert "--tools" in cmd + tools_idx = cmd.index("--tools") + assert cmd[tools_idx + 1] == "WebSearch,WebFetch,Read,Skill" + # Skill should also be in allowed_tools + assert "--allowedTools" in cmd + assert cmd[cmd.index("--allowedTools") + 1] == "Skill" + + def test_build_command_skills_list_with_explicit_tools_adds_skill(self): + """When skills list is set with explicit tools list, 'Skill' is added to tools.""" + transport = SubprocessCLITransport( + prompt="test", + options=make_options( + tools=["WebSearch", "WebFetch", "Read"], + skills=["pdf", "docx"], + ), + ) + cmd = transport._build_command() + # Skill should be added to the tools list + assert "--tools" in cmd + tools_idx = cmd.index("--tools") + assert cmd[tools_idx + 1] == "WebSearch,WebFetch,Read,Skill" + # Skill patterns should be in allowed_tools + assert "--allowedTools" in cmd + assert cmd[cmd.index("--allowedTools") + 1] == "Skill(pdf),Skill(docx)" + + def test_build_command_skills_with_explicit_tools_already_has_skill(self): + """When skills='all' is set and 'Skill' already in tools list, it's not duplicated.""" + transport = SubprocessCLITransport( + prompt="test", + options=make_options( + tools=["WebSearch", "Skill", "Read"], + skills="all", + ), + ) + cmd = transport._build_command() + # Skill should not be duplicated + assert "--tools" in cmd + tools_idx = cmd.index("--tools") + assert cmd[tools_idx + 1] == "WebSearch,Skill,Read" + @pytest.mark.parametrize( ("skills", "extra", "want_tools", "want_sources", "want_init_skills"), [