Stop Claude runs inheriting the host's user settings via --setting-sources - #2
Merged
Merged
Conversation
…urces Claude-Session: https://claude.ai/code/session_014gKfZvjihGgFrEVCr3NJJb Claude-Session-Id: 2eb2d69e-c564-4401-84fb-d22d217b5ff7
yasyf
force-pushed
the
claude-setting-sources
branch
from
September 2, 2026 21:45
4c121e0 to
aa72110
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ClaudeConfighad no way to reach theclaudeCLI's--setting-sources, so every spawn withuse_host_config: trueinherited the host's user-level settings, hooks included, and nothing could turn them off. On a machine with a real hook stack the hooks dominate the call.Measured on macOS with an identical one-word prompt to
claude-haiku-4-5-20251001, MCP already disabled via--strict-mcp-config --mcp-config '{"mcpServers":{}}':SessionEnd hook … failed: Hook cancelled--bareNot logged in: it skips keychain reads, so subscription auth cannot use it--setting-sources project--settings '{"hooks":{}}'The downstream victim was slop-cop, whose per-chunk lint calls each paid that cost and blew their timeouts.
The change
setting_sources: Option<Vec<String>>joinsClaudeConfig, resolved at one site inrust/spawnllm-core/src/plan/claude.rs:isolatedkeeps emitting--setting-sources "", so every isolated invocation is argv-identical to before;--setting-sources project. This is the new default and the point of the change.It is a list passthrough rather than a boolean on purpose.
CodexConfig'senable_hooks/enable_mcpare booleans because codex's knob is one (-c features.hooks=false);--setting-sourcesis a list that governs more than hooks, and the nil-vs-emptyOption<Vec<String>>shape is the onetoolsanddisallowed_toolsalready use.Every layer carries it, by hand (the bindings have no generator): the core wire type and plan, the Rust crate's mirrored spec, Go (
SettingSources []stringonClaudeConfig, the wire struct, andcoreClaudeOf), and Python (setting_sourcesonClaudeConfigplus a newTSettingSourceliteral alias). The Python SDK backend previously hardcoded["user", "project", "local"]for non-isolated runs; it now follows the same resolution as the CLI plan.Conformance: 16 existing claude vectors change, but only two change argv,
claude-isolated-falseandclaude-strict-mcp, the two non-isolated cases, which both gain--setting-sources project. The other 14 are isolated and only gain"setting_sources": nullin their input. Three new vectors cover the explicit restore (user,project,local), an empty list resolving to"", and an explicit list beating isolation.Breaking change
Anyone relying on
use_host_config: trueto pick up user-level settings loses them. MCP servers declared in~/.claude/settings.json, user-level permissions, and hooks all stop loading for non-isolated runs.To restore the previous behavior, name the sources explicitly:
ClaudeConfig(setting_sources=("user", "project", "local"))ClaudeConfig{SettingSources: []string{"user", "project", "local"}}setting_sources: Some(vec!["user".into(), "project".into(), "local".into()])--settingsis not a workaround. Settings merge rather than replace, so--settings '{"hooks":{}}'leaves the inherited hooks in place; that invocation still measured 44.7s above.The CHANGELOG entry under
[Unreleased]/Changeddescribes the same default change and restore path.Test plan
Green:
cargo fmt --check;clippy -D warnings;cargo test -p spawnllm --no-default-featuresgo vet,go build,go test -race ./...ruff checkandruff format;tyat its 21-diagnostic baselineNot green, and shown pre-existing by running the same commands against an unmodified HEAD in a separate worktree:
rust/spawnllm/tests/integration.rscases,call_returns_text_via_auto_selected_backendandrun_on_times_out_and_kills_the_child, fail at baseline too. They want real authenticated CLIs and hold 10s wall-clock deadlines.TestSelectBackendSpecialtyPromotesflaked once under-raceon a loaded machine and passes on an idle one.golangci-lintis not installed on this machine, sotask go:lintdid not run.https://claude.ai/code/session_014gKfZvjihGgFrEVCr3NJJb