Skip to content

fix(skills): inject Skill into --tools when explicit tools= is set (#977)#985

Closed
Zeffut wants to merge 2 commits into
anthropics:mainfrom
Zeffut:fix/977-skills-injects-tool
Closed

fix(skills): inject Skill into --tools when explicit tools= is set (#977)#985
Zeffut wants to merge 2 commits into
anthropics:mainfrom
Zeffut:fix/977-skills-injects-tool

Conversation

@Zeffut
Copy link
Copy Markdown

@Zeffut Zeffut commented May 23, 2026

Summary

Closes #977.

When users pass an explicit tools=[...] list alongside skills="all" (or skills=["pdf", ...]), the CLI receives --tools <user_list> and --allowedTools Skill. The CLI loads only the tools listed in --tools, so Skill is authorized but never loaded — the model cannot invoke any skill. This contradicts the documented behavior of allowed_tools as the single configuration knob for skills.

Root cause

SubprocessCLITransport._apply_skills_defaults in src/claude_agent_sdk/_internal/transport/subprocess_cli.py injects Skill into allowed_tools but never touches the base tools list. _build_command emits --tools from self._options.tools directly. The two paths never meet.

Fix

_apply_skills_defaults now returns a third value, effective_tools, which mirrors the caller's tools list with Skill appended when skills is set. Idempotent; only when tools is an explicit list — preset objects and None are left untouched. The original options object is not mutated. _build_command emits --tools from this effective list.

Behavior matrix

tools skills --tools emitted
None any (not set; CLI default includes Skill)
preset object any default (unchanged)
[] any "" (unchanged)
["Read"] None Read (unchanged)
["Read"] "all" Read,Skill (FIXED)
["Read"] ["pdf"] Read,Skill (FIXED; allowed_tools is Skill(pdf))
["Read","Skill"] "all" Read,Skill (idempotent)

Files modified

  • src/claude_agent_sdk/_internal/transport/subprocess_cli.py_apply_skills_defaults now also returns effective tools; _build_command uses it.
  • tests/test_transport.py — 5 new regression tests covering injection, idempotency, no-skills passthrough, and non-mutation.

Verification

Python 3.12 venv, pip install -e ".[dev]":

  • python -m pytest tests/ --ignore=tests/test_build_wheel.py -> 744 passed, 5 skipped (5 new tests; baseline 739 passed).
  • python -m mypy src/ -> Success: no issues found in 24 source files.
  • python -m ruff check src/ tests/ -> All checks passed!

Relation to #979

This is an alternative approach to #979, which is also open and targets the same bug. #979 injects in _build_command itself; this PR centralizes all skills-related logic in _apply_skills_defaults (the same helper that already handles allowed_tools and setting_sources), which is easier to reason about and test in isolation. Happy to defer to whichever approach maintainers prefer.

Test plan

  • pytest tests/ --ignore=tests/test_build_wheel.py green (744 passed)
  • mypy src/ clean
  • ruff check src/ tests/ clean
  • 5 new regression tests in tests/test_transport.py

)

When users pass an explicit tools=[...] list alongside skills="all",
the CLI's --tools argument did not include Skill, so the Skill tool
was authorized but never loaded. Centralizes the injection inside
_apply_skills_defaults via an effective_tools return value used by
_build_command. Idempotent, no-op when skills is unset.

Adds 5 regression tests. mypy + ruff clean. 744 passed.

Alternative approach to #979.

Closes #977.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 23, 2026 00:01
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a regression fix for skills + explicit tools handling in SubprocessCLITransport so that selecting skills ensures the base --tools includes Skill, and extends the test suite to prevent regressions.

Changes:

  • Update skills defaulting logic to optionally inject Skill into an explicit list-form tools option.
  • Adjust _build_command() to use the skills-adjusted tools list when building the --tools flag.
  • Add regression tests covering injection behavior, idempotency, and immutability.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
tests/test_transport.py Adds regression tests ensuring Skill is added to --tools when skills are enabled with explicit tools, without mutating options.
src/claude_agent_sdk/_internal/transport/subprocess_cli.py Extends _apply_skills_defaults() to also compute effective tools, and wires it into _build_command().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 227 to 238
if tools is not None and "Skill" not in tools:
tools.append("Skill")
else:
for name in skills:
pattern = f"Skill({name})"
if pattern not in allowed_tools:
allowed_tools.append(pattern)
# The CLI's --tools flag matches the bare tool name; allowed_tools
# supplies the per-skill filtering. Ensure Skill is loadable.
if tools is not None and "Skill" not in tools:
tools.append("Skill")

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — addressed in 3bbee1d. The bare-Skill injection now lives in a single statement after the if/else, so the two branches stay in sync by construction. Test suite still green.

Comment thread tests/test_transport.py Outdated
Comment on lines +641 to +642
assert "--tools" in cmd
assert cmd[cmd.index("--tools") + 1] == "WebSearch,WebFetch,Read,Skill"
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. In 3bbee1d the four new regression tests now split the --tools argument on ',' and assert against the resulting list, so they no longer depend on exact comma-joined formatting. Order is still asserted because the injection deterministically appends Skill at the end, which is the contract under test.

Address Copilot review feedback on #985:

- Pull the ``Skill not in tools`` check out of the ``"all"`` / list
  branches in ``_apply_skills_defaults`` into a single post-branch
  statement. The two branches were always doing the same injection,
  so consolidating removes duplication and the risk of the branches
  drifting apart in the future.
- Update the four new regression tests to parse ``--tools`` on ``,``
  and assert against the resulting list rather than the exact
  comma-joined string. Ordering is still verified, but the tests no
  longer depend on the exact formatting of the joined argument.

No behavior change.
@Zeffut
Copy link
Copy Markdown
Author

Zeffut commented May 24, 2026

Closing — this PR was opened as part of an automated triage experiment and does not reflect a real user-reported issue I personally validated. Apologies for the noise.

@Zeffut Zeffut closed this May 24, 2026
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.

skills="all" does not add "Skill" to tools

2 participants