Skip to content

fix: respect disabled builtin extensions (developer) at session start - #10223

Merged
lifeizhou-ap merged 12 commits into
mainfrom
micn/developer-enable
Aug 3, 2026
Merged

fix: respect disabled builtin extensions (developer) at session start#10223
lifeizhou-ap merged 12 commits into
mainfrom
micn/developer-enable

Conversation

@michaelneale

@michaelneale michaelneale commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #10221. A user who disables the Developer extension (toggle off in Settings, enabled: false in config.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: false entry for it:

for builtin in &self.builtins {
    let builtin_config = builtin_to_extension_config(builtin);
    if is_extension_explicitly_disabled(config, &builtin_config.name()) {
        continue;
    }
    push_or_replace_extension(&mut extensions, builtin_config);
}

Docs

Also corrects the stale Developer tool table in documentation/docs/mcp/developer-mcp.md (it listed text_editor/analyze/screen_capture/image_processor; the platform extension actually ships shell, 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

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

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread crates/goose/src/acp/server.rs Outdated
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.
e3742526 added a commit to cephalopod-ai/gosling that referenced this pull request Jul 3, 2026
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
88plug added a commit to 88plug/goose-plus that referenced this pull request Jul 4, 2026
…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).
@DOsinga

DOsinga commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Thanks for addressing the default-off case. I still think the current shape has a design smell though.

is_builtin_disabled_by_user is named in terms of user intent, but it cannot really know that intent because the config has already gone through read migrations that synthesize entries for platform extensions. The implementation then has to infer intent from enabled: false plus PLATFORM_EXTENSIONS[name].default_enabled, which is why the doc comment needs to explain this in terms of migrations, default_enabled, code_execution, and developer. That feels like the abstraction is leaking rather than just needing a shorter comment.

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.

@michaelneale

Copy link
Copy Markdown
Collaborator Author

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

@lifeizhou-ap

lifeizhou-ap commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Hi @michaelneale,

Thanks for the fix!

With the current fix, it has different behaviour for the goose serve from the other cli. For example,

goose serve --with-builtin developer

if the developer is disabled, the session won't have developer extension. This fixes the bug, but the behaviour is opposite to our --with-builtin argument. In cli, when we pass --with-builtin the extension will be included in the session no matter of it is enable or disabled in the config file.

So I think @DOsinga's suggestion makes sense. Instead of pass builtin to goose serve, we can pass

#[derive(Clone, Debug, Default)]
pub struct AcpBuiltinSelection {
pub defaults: Vec,
pub explicit: Vec,
}

For goose serve:

let builtin_selection = if builtins.is_empty() {
AcpBuiltinSelection {
defaults: vec!["developer".to_string()],
explicit: vec![],
}
} else {
AcpBuiltinSelection {
defaults: vec![],
explicit: builtins,
}
};

For goose acp:

let builtin_selection = AcpBuiltinSelection {
defaults: vec![],
explicit: builtins,
};

and replace selected_builtin_extensions with something like below

fn selected_builtin_extensions(
config: &Config,
default_builtins: &[String],
explicit_builtins: &[String],
) -> Vec {
let mut extensions = Vec::new();

for builtin in default_builtins {
    if configured_enabled_state(config, builtin) != Some(false) {
        push_or_replace_extension(
            &mut extensions,
            builtin_to_extension_config(builtin),
        );
    }
}

for builtin in explicit_builtins {
    push_or_replace_extension(
        &mut extensions,
        builtin_to_extension_config(builtin),
    );
}

extensions

}

We can remove this fallback logic in cli, I think this is the original confusing and smell part prior to your fix.

let builtins = if builtins.is_empty() {
        vec!["developer".to_string()]
    } else {
        builtins
    };

Happy to have a sync chat for more clarification!

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread crates/goose/src/acp/server.rs Outdated

for builtin in &builtin_selection.defaults {
let builtin_config = builtin_to_extension_config(builtin);
if configured_enabled_state(config, builtin_config.name()) != Some(false) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

michaelneale and others added 2 commits July 22, 2026 20:10
# 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).
@michaelneale

Copy link
Copy Markdown
Collaborator Author

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)
  ...
@lifeizhou-ap
lifeizhou-ap added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit 750d95e Aug 3, 2026
27 checks passed
@lifeizhou-ap
lifeizhou-ap deleted the micn/developer-enable branch August 3, 2026 01:11
michaelneale added a commit that referenced this pull request Aug 4, 2026
* 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)
  ...
alexhancock added a commit that referenced this pull request Aug 4, 2026
* 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
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.

Disabled Developer extension still loads in every new desktop chat

3 participants