Skip to content

fix(claude-agents)!: switch the built-ins off by default and load no host settings - #201

Merged
jeffdupont merged 3 commits into
mainfrom
xie/claude-agents-host-options
Oct 23, 2026
Merged

jeffdupont merged 3 commits into
mainfrom
xie/claude-agents-host-options

Conversation

@XieX

@XieX XieX commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This is the Python side of the core half of launchdarkly/ai-sdks-monorepo#59 (TESTING.md §2.x "Claude Agents Handler — host options and built-in tool set"). The TypeScript side is launchdarkly/js-ai-sdk#135.

  • tools=[] when a config attaches no NativeTool (§2.x.2). This reverses 50e82fd. With the key omitted, every Python run got the full Claude Code built-in set (Read, Bash, Skill and the rest), and the read-only tools in it need no permission. A config now turns a built-in on by attaching its sentinel, which is what TypeScript has always done. User-defined tools still go through the in-process MCP server and allowed_tools, whatever tools holds.
  • create_claude_agents_handler(*, capture_content=None, cwd=None, setting_sources=None) (§2.x.1). Both options are forwarded to every query() on the blocking and streaming paths.
    • setting_sources not given sends []. The Agent SDK reads None as "load every source", which put the host's plugins, skills, hooks, permission rules and CLAUDE.md into every run (AIC-3584).
    • cwd is sent only when given.
    • capture_content=None still follows configure().
    • A config can't set or override either option: since fix!: pass AI Config model parameters through to every provider handler #107 the handler forwards only allowlisted model.parameters, and both keys are on the excluded list. A test pins that.
  • The wrappers and the native adapter take both options too, matching feat(claude-agents)!: accept trusted cwd and settingSources options, and load no host settings by default js-ai-sdk#135:
    • claude_agents() and claude_graph() take cwd / setting_sources keyword-only and build the handler with them. Neither reaches config() / graph().
    • to_claude_agents() reads them from opts and sends _host_query_options(...) to every node's query(), root and sub-agents. It already sent tools=[] with no native tools, and a test now pins that too.
    • All four entry points default to setting_sources=[] and no cwd.
  • The README has a migration note. [] also drops the settings-file env (ANTHROPIC_BASE_URL, CLAUDE_CODE_USE_BEDROCK / _VERTEX, proxy variables), apiKeyHelper and model. A deployment that reaches Bedrock, Vertex or a gateway through ~/.claude/settings.json needs setting_sources=["user"] or those variables in the process environment.

Breaking

Two changes in runtime behaviour:

  • A run has no Claude Code built-ins unless its config attaches them.
  • A run loads no host Claude Code settings unless the application passes setting_sources.

This needs to land before the 1.0 freeze, because changing either default after 1.0 would be breaking.

Tests

  • TestBuiltinsSurviveAnEmptyToolList is replaced. Its "omits the key" test asserted the behaviour this PR reverses.
  • TestHostOptionsAndBuiltinToolSet adds nine tests, each run on both the invoke and stream paths.
  • TestToClaudeAgentsHostIsolation adds four, including root and leaf for the default and for given values.
  • TestConvenienceExportHostOptions and TestClaudeGraphHostOptions add two each: the default, and given values reaching query() but not config() / graph().
  • The API report is regenerated for the three new signatures.

Checks:

  • uv run pytest: every claude-agents test passes. The one failure in the full suite is opencode-agents test_a_fake_opencode_is_started_spoken_to_and_stopped, a macOS /private/var vs /var temp-path mismatch already on main.
  • make lint, make format-check and make typecheck are clean.

🤖 Generated with Claude Code


Note

Overview
Breaking: Python Claude agent runs no longer inherit the Agent SDK’s implicit defaults for built-in tools or host Claude Code settings.

Runs now pass tools=[] when the flag config attaches no NativeTool sentinel, which turns off Claude Code built-ins (Read, Bash, etc.) unless you explicitly enable them via ClaudeRead, ClaudeBash, and similar in tool_handlers. User-defined tools still use the in-process MCP server unchanged.

Host isolation: create_claude_agents_handler, claude_agents, claude_graph, and to_claude_agents gain app-only cwd and setting_sources (via _host_query_options), forwarded on every query() (invoke, stream, and graph nodes). Default setting_sources=[] stops loading ~/.claude/settings.json, project CLAUDE.md, plugins, hooks, and settings-file env (Bedrock/Vertex gateways may break until you opt in or set process env). Flag model.parameters cannot override these options.

README/agents.md document migration; tests replace the old “omit tools” expectation and add coverage for host options and built-in tool sets across invoke/stream/graph paths.

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

…host settings

A run with no NativeTool attached now sends tools=[], so the Claude Code built-ins
(Read, Bash, Skill and the rest) are off unless a config attaches them. 50e82fd
omitted the key instead, which gave every run the full built-in set. TypeScript
has sent tools=[] since its first release. TESTING.md §2.x.2.

create_claude_agents_handler takes cwd and setting_sources, forwarded to every
query() on both paths. setting_sources not given sends [], because the Agent
SDK reads None as "load every source": the host's plugins, skills, hooks,
permission rules and CLAUDE.md reached every run (AIC-3584). cwd is sent only
when given. to_claude_agents also sends setting_sources=[]. §2.x.1.

BREAKING CHANGE: runs no longer get Claude Code built-ins unless the config
attaches them as NativeTool sentinels, and no longer load host Claude Code
settings unless setting_sources names them.

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

Copy link
Copy Markdown
Contributor

Two things so this matches js #135 (launchdarkly/js-ai-sdk#135), which is now approved. I'm asking spec #59 to make the same change.

1. The wrappers and the native adapter should take cwd and setting_sources.
At the moment claude_agents(), claude_graph() and to_claude_agents() hard-code setting_sources=[] (native_graph.py, _run_query), so a wrapper user can't opt back in to host settings. JS now takes both options on all four entry points, and these signatures freeze at 1.0. Please:

  • In claude_agents and claude_graph, take cwd / setting_sources keyword-only, build the handler with them, and don't pass them on to config() / graph().
  • In to_claude_agents, take both options and send _host_query_options(...) to every Anthropic node's query(), root and sub-agents, in place of the hard-coded [].
  • Add tests: [] and no cwd by default, and a given value reaching query(), for each entry point. For the graph, check root and leaf.
  • Update the README line "claude_agents(), claude_graph() and to_claude_agents() don't take cwd or setting_sources" and the matching sentence in agents.md §2a.
  • Run make api-report and commit the report diff, because the public signatures change.

2. Add a migration note to the README.
[] also drops the settings-file env (ANTHROPIC_BASE_URL, CLAUDE_CODE_USE_BEDROCK / _VERTEX, proxy variables), apiKeyHelper and model. A deployment that reaches Bedrock, Vertex or a gateway through ~/.claude/settings.json fails to authenticate after the upgrade, and nothing in the docs says why. The "Migrating from a version that loaded host settings" section in js #135's README can be copied over with setting_sources=["user"] examples.

@XieX
XieX requested a review from jeffdupont October 23, 2026 17:58
@XieX
XieX marked this pull request as ready for review October 23, 2026 17:58
XieX and others added 2 commits October 23, 2026 14:03
…t-options

# Conflicts:
#	packages/claude-agents/src/launchdarkly_ai_claude_agents/handler.py
…native adapter

Matches launchdarkly/js-ai-sdk#135. claude_agents(), claude_graph() and
to_claude_agents() hard-coded setting_sources=[], so a wrapper user could
not opt back in to host settings, and these signatures freeze at 1.0.

- claude_agents and claude_graph take cwd / setting_sources keyword-only,
  build the handler with them, and pass neither on to config() / graph().
- to_claude_agents reads both from opts and sends _host_query_options(...)
  to every node's query(), root and sub-agents, in place of the
  hard-coded [].
- The README gains a migration note: [] also drops the settings-file env
  (ANTHROPIC_BASE_URL, CLAUDE_CODE_USE_BEDROCK / _VERTEX, proxies),
  apiKeyHelper and model, so a Bedrock, Vertex or gateway deployment
  configured through ~/.claude/settings.json stops authenticating.
- agents.md §2a describes the model-parameter allowlist from main
  instead of "forwards none of model.parameters".
- API report regenerated for the new signatures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@XieX
XieX requested a review from a team as a code owner October 23, 2026 18:07

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

Approved after the GA 1.0 PR audit. Every earlier review point is addressed and the API report is regenerated. The CI failure is the flaky opencode boot-timeout test, so I'm rerunning it.

@jeffdupont
jeffdupont enabled auto-merge (squash) October 23, 2026 18:59
@jeffdupont
jeffdupont merged commit f99d538 into main Oct 23, 2026
14 of 15 checks passed
@jeffdupont
jeffdupont deleted the xie/claude-agents-host-options branch October 23, 2026 19:01
@github-actions github-actions Bot mentioned this pull request Oct 23, 2026
XieX added a commit that referenced this pull request Oct 23, 2026
…riation (#208)

**Stacked on #201 (host options and built-in
tool set).** Review that one first. This PR's base is its branch.

A skill attached to an agent-mode variation never reached a model run
through `create_claude_agents_handler`. #201's `tools=[]` removes the
`Skill` tool, and nothing hands the CLI the skill files.

`create_claude_agents_skills_handler(*, capture_content=None, cwd=None,
setting_sources=None, skills_temp_dir=None)`, from the new
`launchdarkly_ai_claude_agents.experimental.skills` module, fixes that.
On every invoke and stream it:
1. Writes `skill_refs(config)` with `write_skills`, through the store
configured with `set_skill_store`, into
`mkdtemp(<skills_temp_dir>/ld-skills-)/launchdarkly/skills`. Pinned
versions are honored.
2. Passes `plugins=[{"type": "local", "path": <dir>/launchdarkly}]` and
`skills=["launchdarkly:<key>", …]`, and appends `Skill` to `tools` (not
to `allowed_tools`).
3. Removes the directory when the run ends, however it ends.

- **One directory per run.** There's no shared root, no watcher and no
prune. Detaching a skill from a variation takes effect on the next run,
and concurrent runs never share files.
- **Fail closed.** If the reconcile isn't `ok` (no store, store not yet
initialized, a skill withheld by verification, or an absent pin), the
run raises before `query()` and names each skill.
- **No-skills path.** A variation with no skills sends exactly the core
handler's options and needs no store.
- **`setting_sources` is never `None`.** With `skills` set and
`setting_sources=None`, `claude-agent-sdk` sends
`--setting-sources=user,project`. The core handler already always sends
it (`[]` unless given), and this handler inherits that.
- **Telemetry.** The tool hooks are installed whenever `Skill` is in
`tools`, so a `Skill` call gets an `execute_tool Skill` span, with no
`$ld:ai:tool_call` event.
- **Stage boundary.** Core reaches this code only through a private
`_prepare` keyword argument on `_create_claude_agents_handler`. It is
not an option of the public factory. The package root doesn't import the
experimental module, and a test checks that in a fresh interpreter.
- **Not covered:** `claude_agents()`, `claude_graph()` and
`to_claude_agents()`. The README says so.

## Differences from TypeScript

- **No `skipMcpDiscovery`.** `claude-agent-sdk`'s `SdkPluginConfig` has
no such key, so the plugin entry carries only `type` and `path`. The
directory holds no MCP config, so there is nothing to discover.
- **Synchronous removal.** The directory is removed with a synchronous
`shutil.rmtree`, so a cancellation during teardown can't interrupt it. A
failed removal is logged and doesn't replace the run's outcome.

## Tests

- A new `test_skills.py` (49 tests) uses a mocked `query()` with a real
`InMemorySkillStore`, a real filesystem (`tmp_path` as
`skills_temp_dir`) and a real OTel provider. Each spec bullet runs on
both invoke and stream where it applies.
- `test_sdk_usage.py` adds the
`experimental.createClaudeAgentsSkillsHandler` row.
- Mutation-checked: making the cleanup a no-op fails 9 tests. Dropping
`Skill` from the hook-install condition fails the span tests.

Checks:
- `uv run pytest`: everything passes except `opencode-agents`
`test_a_fake_opencode_is_started_spoken_to_and_stopped`, the macOS
`/private/var` temp-path failure already on `main`.
- `make lint`, `make format-check` and `make typecheck` are clean.

## Follow-ups in the monorepo

- The A.18 Python rows and an `EXPERIMENTAL.md` row for Python.
-
`api-surface/python/launchdarkly_ai_claude_agents.experimental.skills.txt`.
- `python` on `experimental.createClaudeAgentsSkillsHandler` in
`usage-ids.txt`.

**Not verified yet:** a live run where a model actually loads the skill.

**Spec:** TESTING.md §2.x "Claude Agents Skills Integration" and A.18,
in launchdarkly/ai-sdks-monorepo#59. The TypeScript side is
launchdarkly/js-ai-sdk#136.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> Adds **experimental** `create_claude_agents_skills_handler()` under
`launchdarkly_ai_claude_agents.experimental.skills` so Claude Agent runs
get the **skills attached to their AI Config variation**. The core
`create_claude_agents_handler()` path is unchanged and still ignores
variation skills.
> 
> On each invoke/stream with skills, the handler writes skill files from
`set_skill_store` into a **per-run temp directory**, passes it to
`query()` as a local `launchdarkly` plugin with qualified
`launchdarkly:<key>` skill names, enables the **`Skill`** built-in (via
new internal `_prepare` / `QueryPreparation` on
`_create_claude_agents_handler`), and **removes the directory** in a
final cleanup on success, failure, cancel, or stream abandonment.
Reconcile failures **fail closed** before `query()`; empty skills behave
like the core handler.
> 
> Docs, API report, usage tracking for
`experimental.createClaudeAgentsSkillsHandler`, and a large
`test_skills.py` suite cover materialization, concurrency, telemetry
(`execute_tool Skill`), and that `claude_agents` / graph helpers are
**not** wired yet.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
ac743e0. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Jeff Dupont <jdupont@launchdarkly.com>
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