Repository navigation
fix(cli): an unreadable extension-enablement config re-enables every extension - #29481
lets-order-some-fries wants to merge 1 commit into
Conversation
…hing
readConfig() collapsed a parse or schema failure into the same {} it uses
for ENOENT. Extensions default to enabled, so an unreadable file turns every
deliberate disable back on - and extensions can carry MCP servers, tools,
commands and hooks, so that is a trust event, not a preference reset.
writeConfig() then serialises the whole map unconditionally, so the next
enable/disable/remove persists that {} plus one key and drops every other
entry.
Reproduced on main: with alpha and beta disabled, truncating three bytes of
extension-enablement.json flips both to enabled, and `extensions disable
gamma` afterwards leaves a file containing only gamma. The read error was
also printed four times in one command.
readConfigResult() now discriminates ok/missing/unreadable; only missing
yields an empty config. isEnabled fails closed, enable() and remove() throw
ExtensionEnablementConfigError rather than writing, and the error is
reported once per stretch of failures with the path and the remedy. Valid
JSON of the wrong shape is unreadable too - it fails open by the same route.
readConfig() keeps its signature for existing callers; every production path
inside the class now goes through readConfigResult().
This is the same defect google-gemini#29445 fixes for mcp-server-enablement.json, in the
sibling file, and the two are worth reading together.
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses a critical defect where an unreadable or corrupted Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
|
📊 PR Size: size/M
|
There was a problem hiding this comment.
Code Review
This pull request improves the robustness of the extension enablement configuration handling in the Gemini CLI. It introduces a safer mechanism for reading the enablement configuration file, distinguishing between a missing file and an unreadable/corrupted file. When the file is unreadable, the manager now fails closed (treating extensions as disabled) and prevents any write operations (such as enabling or removing extensions) to avoid overwriting and losing existing configuration data. It also limits error reporting to once per stretch of failures to avoid spamming logs. Comprehensive unit tests have been added to verify these new behaviors. I have no feedback to provide as there are no review comments and the implementation is solid.
|
Hi there! Thank you for your interest in contributing to Gemini CLI. To ensure we maintain high code quality and focus on our prioritized roadmap, we only guarantee review and consideration of pull requests for issues that are explicitly labeled as 'help wanted'. This PR will be closed in 7 days if it remains without that designation. We encourage you to find and contribute to existing 'help wanted' issues in our backlog! Thank you for your understanding. |
Summary
An unreadable
extension-enablement.jsonsilently re-enables every extension the userdisabled, and the next enable/disable/remove then erases the rest of the file.
Extensions can contribute MCP servers, tools, commands and hooks, so re-enabling one the
user switched off is a trust event, not a preference reset.
This is the same defect #29445 fixes for
mcp-server-enablement.json, in the siblingfile. The two are worth reading together.
Details
readConfig()collapses aJSON.parsefailure or a zod schema failure into the same{}it returns for
ENOENT:Downstream,
isEnabled()starts fromlet enabled = true("Extensions are enabled bydefault"), so an empty config means everything is on. Then
writeConfig()serialisesthe whole map unconditionally —
— so
enable()(anddisable(), which delegates to it) andremove()persist that{}plus one key, dropping every other entry.
What changed
readConfigResult()returns a result discriminated onok/missing/unreadable.Only
missingyields an empty config.isEnabled()fails closed onunreadable.enable()andremove()refuse to write, throwingExtensionEnablementConfigError,so the file survives for the user to repair. Both call sites in
extension-manager.tsalready sit behind the
tryinhandleDisable/handleEnable, which is how"Extension with name X does not exist." is already surfaced.
{"alpha": "disabled"}parsesfine and fails open by the same route.
now names the path and the remedy.
isEnabledruns per extension per startup; onmaina single
extensions disableprinted the message four times.readConfig()keeps its signature for existing callers; every production path insidethe class now goes through
readConfigResult().Related Issues
Same defect class as #29445 (
mcp-server-enablement.json).How to Validate
7 cases added; 6 fail without the source change. The seventh is a control asserting a
missing file still behaves exactly as before.
End to end, against a scratch home:
On
main: the truncation flipsalphaandbetatoEnabled (User): true, and thedisable gammaafterwards leaves a file containing onlygamma— both disables gone.On this branch: both stay
false, one error line names the file, and the write is refusedwith the file byte-identical (verified by
md5).Suites on this branch:
src/config/+src/commands/extensions/→ 47 files, 1006passed. Full
npm run test -w packages/cli→ 465 of 466 files pass; the one failureis
src/gemini.test.tsx, which fails the same 9 tests on cleanmainin this checkoutwith the identical
FatalUntrustedWorkspaceError(my local checkout is not a trustedfolder). I compared the failure reason against a
mainrun rather than just the count,since two of those nine are hooks-and-trust tests and extensions contribute hooks.
npm run typecheck -w packages/cliandeslint packages/cli/src/config/extensions/bothclean.
Pre-Merge Checklist
changes;
extension-enablement.jsonis not described in/docsnow disables extensions rather than silently enabling them, and enable/disable/remove
refuse to write until it is repaired. A missing config behaves exactly as before.
Filesystem-and-JSON only; no platform-specific paths beyond
ExtensionStorage.getUserExtensionsDir(), which is untouched.🤖 Generated with Claude Code