fix: respect disabled builtin extensions (developer) at session start - #10223
Conversation
The ACP server unconditionally pushed every builtin (e.g. developer) into each new session before consulting the extensions config. A user who disabled developer in Settings still got shell/edit/write/tree/read_image handed to the model, because the config-derived list only *adds* enabled extensions and never removes the force-added builtin. Skip a builtin when the config has an explicit enabled: false entry for it. Missing entries still load (fresh installs / default-on bundled extensions behave as before), so this only changes the explicitly-disabled case. Adds config::extensions::configured_enabled_state / is_extension_explicitly_disabled and unit tests.
The tool table listed text_editor/analyze/screen_capture/image_processor, which no longer match the tools the platform Developer extension ships. Update to the actual tools: shell, write, edit, tree, read_image. Also note that codebase analysis is now the separate Analyze extension.
|
Documentation preview deployed: https://pr-10223.goose-pr-previews-poc.pages.dev |
Extract builtin selection into selected_builtin_extensions() and add tests that drive it with a real config.yaml reproducing issue #10221: - empty config -> developer loads (default-on preserved) - enabled: true -> developer loads - enabled: false -> developer NOT loaded Verified the disabled-case test fails when the fix is reverted, so it actually guards the regression.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 082b9f2d20
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
CI (test_prompt_codemode) exposed a flaw in the first fix. run_read_migrations synthesizes a config entry for every platform extension using its default_enabled value, so a default-off extension (e.g. code_execution, chatrecall) ends up with enabled: false in config even when the user never touched it. The previous is_extension_explicitly_disabled treated that as a user disable and skipped code_execution even when the ACP server explicitly requested it via builtins (as code mode does). Replace with is_builtin_disabled_by_user, which only skips a builtin when it is disabled AND default_enabled. This still fixes the developer/desktop bug (#10221, developer is default-on) while letting explicit builtins requests load default-off extensions. Add regression tests: default-off builtin loads when explicitly requested.
Port audited changes from aaif-goose/goose#10223. Upstream commits: 01965c2b8, 082b9f2d2, 205c72368, a2f9658c1. Local audit: patch applied cleanly; selection now skips only user-disabled default-on builtins, preserving explicit requests for default-off builtins. Gate: source bin/activate-hermit && cargo fmt && cargo test -p goose builtin_developer && cargo test -p goose default_off_builtin && cargo test -p goose configured_enabled_state && cargo test -p goose default_on_extension && cargo test -p goose default_off_extension && cargo test -p goose unknown_builtin_disabled
…start initial_session_extensions() unconditionally loaded every configured builtin regardless of its enabled flag in config.yaml, so disabling Developer in Settings had no effect - every new chat still got shell, edit, write, tree, and read_image tools. Gate builtin selection on a new is_builtin_disabled_by_user() check that treats "enabled: false" as a real opt-out only for extensions that are on by default, so an explicit builtins request (e.g. code mode's code_execution) still loads default-off extensions like chatrecall. Ports aaif-goose/goose#10223 (fixes #10221).
|
Thanks for addressing the default-off case. I still think the current shape has a design smell though.
I think the distinction we actually care about is the source of the builtin request: builtins goose adds by default for ACP/serve should respect config disabled state, while builtins explicitly requested by a caller should be loaded because they were explicit. Could we model that distinction directly, for example with separate default vs explicit builtin lists or a small enum, instead of trying to recover it from platform defaults after config migration? That would make the selection policy more direct and avoid a helper whose behavior is defined by unrelated migration details. |
|
@DOsinga yeah I am not sure what we want as end state, what expectation is. Is the idea that we have a config yaml same as today where people say something is on or off, and if it is off, it stays off? as in an ACP world that seems less clear to me. |
|
Hi @michaelneale, Thanks for the fix! With the current fix, it has different behaviour for the goose serve from the other cli. For example,
if the developer is disabled, the session won't have developer extension. This fixes the bug, but the behaviour is opposite to our So I think @DOsinga's suggestion makes sense. Instead of pass
For goose serve:
For goose acp:
and replace
We can remove this fallback logic in cli, I think this is the original confusing and smell part prior to your fix. Happy to have a sync chat for more clarification! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 18a23e1e47
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
|
||
| for builtin in &builtin_selection.defaults { | ||
| let builtin_config = builtin_to_extension_config(builtin); | ||
| if configured_enabled_state(config, builtin_config.name()) != Some(false) { |
There was a problem hiding this comment.
Borrow the extension name for the config check
This passes the String returned by ExtensionConfig::name() to configured_enabled_state, whose second parameter is &str, so the goose crate no longer compiles when this helper is built. Borrow the returned name (or bind it first) before doing the disabled-state comparison.
Useful? React with 👍 / 👎.
# Conflicts: # crates/goose/src/acp/server.rs
Pass &str to configured_enabled_state instead of the owned String returned by ExtensionConfig::name(), fixing the E0308 build break that was failing CI (clippy, build, schema check all cascaded from it).
|
Maybe in closer to desired state now ready for review again |
* main: (103 commits) fix: parse PyPI requirements for OSV checks (#10510) fix(telegram): contain voice file extensions (#10456) Revert "feat(security): chunk command-classifier input with overlapping windows" (#10416) (#10870) docs: update Discord invite (#10863) fix(security): preserve denied tool request precedence (#10612) fix(hints): contain subdirectory hint discovery (#10545) chore(deps): bump pem from 3.0.6 to 4.0.0 (#10853) chore(deps): bump base64 from 0.22.1 to 0.23.0 (#10851) chore(deps): bump jsonwebtoken from 10.4.0 to 11.0.0 (#10850) chore(deps): bump astral-sh/setup-uv from 8.3.2 to 9.0.0 (#10847) chore(deps): bump docker/login-action from 4.5.1 to 4.5.2 (#10846) chore(deps): bump github/codeql-action from 4 to 4.37.3 (#10845) chore(deps): bump actions/stale from 10.4.0 to 11.0.0 (#10844) feat(dictation): add LOCAL_WHISPER_LANGUAGE for multilingual local transcription (#10634) fix(desktop): clear stale validation error when reopening the schedule modal (#10627) fix(docs): resolve CVE-2026-13149 in both brace-expansion copies (#10842) feat(provider): add Friendli as declarative provider (#10762) fix: sanitize shell/subprocess call in linux.rs (#10748) fix(desktop): unlink destination before copying binaries (#10705) feat(otel): enrich root span with gen_ai attributes and improve output format (#10816) ...
* origin/main: (42 commits) chore (codex-acp): migrate to @agentclientprotocol/codex-acp (#10923) style: add dark mode inline code styling for message bubbles (#10861) docs: clarify macOS sandbox feature was removed in post-v1.25.0 cleanup (#10900) fall back to static model list only for non-models payloads (#10189) Remove CLI project support (#10838) fix(anthropic): add claude-sonnet-5 and claude-fable-5 to known models list (#10865) fix: stdio extensions silently skipped when name missing or env: used in config (#10773) feat(desktop): show session metadata on sidebar chat hover (#10663) fix(serve): start scheduler at startup instead of first ACP connection (#10766) fix: respect disabled builtin extensions (developer) at session start (#10223) fix: parse PyPI requirements for OSV checks (#10510) fix(telegram): contain voice file extensions (#10456) Revert "feat(security): chunk command-classifier input with overlapping windows" (#10416) (#10870) docs: update Discord invite (#10863) fix(security): preserve denied tool request precedence (#10612) fix(hints): contain subdirectory hint discovery (#10545) chore(deps): bump pem from 3.0.6 to 4.0.0 (#10853) chore(deps): bump base64 from 0.22.1 to 0.23.0 (#10851) chore(deps): bump jsonwebtoken from 10.4.0 to 11.0.0 (#10850) chore(deps): bump astral-sh/setup-uv from 8.3.2 to 9.0.0 (#10847) ...
* origin/main: (58 commits) Handle MCP tool list change notifications fix: restore final_output_tool when agent is recreated after LRU eviction (#10793) feat(ui): bring back make sidebar resizable with drag handle and persisted width (#10795) docs: document auto-injected GOOSE_SHELL flags (#10759) feat: surface output-token-limit info (#10831) fix(desktop): avoid O(n²) cloning during session load (#10665) feat: add interactive menu for single select elicitations (#10327) fix(acp): prefix child stderr log target so it passes the default goose=info filter (#10901) chore (codex-acp): migrate to @agentclientprotocol/codex-acp (#10923) style: add dark mode inline code styling for message bubbles (#10861) docs: clarify macOS sandbox feature was removed in post-v1.25.0 cleanup (#10900) fall back to static model list only for non-models payloads (#10189) Remove CLI project support (#10838) fix(anthropic): add claude-sonnet-5 and claude-fable-5 to known models list (#10865) fix: stdio extensions silently skipped when name missing or env: used in config (#10773) feat(desktop): show session metadata on sidebar chat hover (#10663) fix(serve): start scheduler at startup instead of first ACP connection (#10766) fix: respect disabled builtin extensions (developer) at session start (#10223) fix: parse PyPI requirements for OSV checks (#10510) fix(telegram): contain voice file extensions (#10456) ... # Conflicts: # Cargo.lock # crates/goose/src/agents/extension_manager.rs # crates/goose/tests/mcp_replays/npx-y@modelcontextprotocol_server-everything@2026.1.14
Summary
Fixes #10221. A user who disables the Developer extension (toggle off in Settings,
enabled: falseinconfig.yaml) still gets its tools (shell,edit,write,tree,read_image) loaded into every new chat, and shell commands execute. Any mitigation built on disabling Developer is silently void.Fix
Skip a builtin when the config has an explicit
enabled: falseentry for it:Docs
Also corrects the stale Developer tool table in
documentation/docs/mcp/developer-mcp.md(it listedtext_editor/analyze/screen_capture/image_processor; the platform extension actually shipsshell,write,edit,tree,read_image, and codebase analysis is now the separate Analyze extension).Testing
cargo test -p goose --lib config::extensions— added unit tests for the new helpers (missing entry, default-on platform ext, explicit disable/enable)cargo build -p goose,cargo clippy -p goose --lib,cargo fmt— clean