Gemini CLI 配置不生效:四层 settings 的优先级链
Gemini CLI 里「我明明改了却没生效」,九成不是 bug,是你改的那一层被更高的一层盖住了。 它的配置不是一个文件,而是一条由低到高的覆盖链:四个 settings.json 位置在下面,环境变量在上面,命令行参数在最上面。你要做的不是反复重启、反复改,而是先弄明白自己改的那个值站在链条的第几级。
这篇只讲 Gemini CLI 这一条优先级链本身:每一级在哪、顺序是什么、密钥怎么放。配置怎么从本机带到另一台机器、带进 CI,看 配置在不同环境之间怎么传;环境变量在终端、IDE、子进程之间莫名其妙丢掉,看 环境变量丢失的排查;团队里多个规则文件互相打架,看 团队规则文件冲突。三篇讲的是配置的传递、丢失和团队协作,这篇讲的是单机上同一个值被谁覆盖。
下面提到的字段名、路径和顺序,都是截至 2026-08-07 官方文档配置页上的记录,请以官方文档最新版为准。
一、四个 settings.json 分别在哪
先把「层」这个词落到具体路径上。官方配置页列出 settings.json 的四处位置:
- 系统默认:
/etc/gemini-cli/system-defaults.json(Linux) - 用户级:
~/.gemini/settings.json - 项目级:项目根目录下的
.gemini/settings.json - 系统覆盖:
/etc/gemini-cli/settings.json(Linux)
有两点值得先记住。
第一,两个 /etc 下的文件是不同的东西,名字也不一样:一个叫 system-defaults.json,一个叫 settings.json。它们在优先级链上的位置一个在最底、一个在最顶,等一下会看到。把它们当成同一个文件的两个写法,是这套结构最容易被误读的地方。
第二,文档给这两条 /etc 路径标注的是 Linux。别的操作系统上系统级文件放哪,本篇依据的这一页记录里没有写,以官方文档为准——不要凭直觉在 Windows 上拼一个类似路径出来找不到就下结论。
还有一条结构上的约定:设置按类别组织成顶层对象(general、ui、tools、model、context 等),每个设置要放进对应的类别里。也就是说这个 JSON 不是一层平铺的键值表,你要改的项得放在它归属的那个顶层对象下面。如果你把一个属于 model 类别的项直接写在最外层,它属于「写在了不该在的位置」,具体会被怎么处理,本篇依据的这一页没有展开,实际以官方文档和你运行时看到的结果为准。
二、完整的优先级是七级,文件只占其中四级
官方配置页给出的优先级顺序(低 → 高)是:硬编码默认 → system defaults 文件 → user settings → project settings → system settings → 环境变量 → 命令行参数。
这条链子有三处反直觉的地方,值得逐条盯住。
其一,项目级不是最高的。 很多工具的心智模型是「越靠近项目越优先」,所以你在项目里的 .gemini/settings.json 改了值,就默认它一定说了算。但在这条链上,项目级之上还有 system settings(/etc/gemini-cli/settings.json)。换句话说,如果那个系统文件里写了同一项,你在项目里改什么都会被盖掉。这是顺序本身直接推出的结论:至于这个文件被设计出来是给谁用的,文档在本篇依据的这一页没有说明,我不替它下定论。
其二,文件永远盖不过环境变量。 环境变量整体排在四个文件之上。这意味着一个常见现象有了解释:你把某项写进 settings.json 保存好了,运行起来却是另一个值——因为 shell 里还留着一个同名的环境变量。它不是没读你的文件,是读了之后又被上面一级覆盖。
其三,命令行参数在最顶上。 这是一次性覆盖的入口。它的好处是排查时可以拿来做对照实验:同一个值,用参数显式传一次,如果行为变了,说明程序确实认这个配置项,问题出在你改的那一层被盖住;如果传了参数行为还是没变,那方向就得换,往拼写、类别归属或者别的机制上找。
| 优先级(低 → 高) | 这一级是什么 | 具体位置 / 形式 | 出处 |
|---|---|---|---|
| 1 | 硬编码默认 | 程序内置,无文件 | Gemini CLI 配置页 |
| 2 | system defaults 文件 | /etc/gemini-cli/system-defaults.json(Linux) | 同上 |
| 3 | user settings | ~/.gemini/settings.json | 同上 |
| 4 | project settings | 项目根的 .gemini/settings.json | 同上 |
| 5 | system settings | /etc/gemini-cli/settings.json(Linux) | 同上 |
| 6 | 环境变量 | 进程环境,含 .env 加载进来的 | 同上 |
| 7 | 命令行参数 | 启动时显式传入 | 同上 |
表里的层级名称与路径逐字来自该页记录。你可以把这张表贴在排查清单最前面:定位「谁盖了谁」的时候,只需要从第 7 行往下读,第一个写了这个值的位置就是实际生效的那一级。
三、密钥和模型:插值、.env 的查找顺序
配置文件要进版本库,密钥不能进版本库——这两件事在 Gemini CLI 里靠环境变量插值调和。
先解释一下什么是环境变量插值。 它指的是配置文件里写一个占位符引用外部环境里的变量名,而不是把值本身写死;程序读取配置时再把占位符换成运行环境里的实际值。Gemini CLI 的 settings.json 支持这种写法,形式是 $VAR_NAME 或 ${VAR_NAME},在加载时自动解析。这样一来配置文件里留下的是变量名,真正的密钥留在环境里,文件本身可以放心提交。
环境变量从哪来?除了 shell 里 export 的,还有 .env 文件。官方配置页记录的查找顺序是:当前工作目录 → 逐级向上找父目录(到项目根或 home 为止)→ 用户 home 的 ~/.env。这个顺序解释了一类怪事:你在仓库根放了 .env,但从某个子目录启动,命中的可能是那个子目录里另一个 .env。同理,~/.env 是兜底,你很久以前随手写的一行可能还在那里生效。
页面上列出的相关项有这么几个:
| 项 | 形态 | 本篇依据的那一页怎么记的 | 出处 |
|---|---|---|---|
GEMINI_API_KEY | 环境变量 | 记为 Gemini API 密钥 | Gemini CLI 配置页 |
GEMINI_MODEL | 环境变量 | 记为默认模型 | 同上 |
model.name | settings.json 内的设置项 | 只列出了名字 | 同上 |
security.auth.selectedType | settings.json 内的设置项 | 只列出了名字 | 同上 |
后两项要特别说明:该页把它们列为相关设置项,但本篇依据的记录里没有逐条说明它们的取值范围和客户端内部拿它们做什么。从名字所在的类别看,model.name 归 model 类、security.auth.selectedType 归 security 类——这是按字段名和前面那条「设置按类别组织」的约定推出来的判断,不是文档的行为陈述,实际含义以官方文档为准。别根据名字去猜它接受什么字符串然后写死在配置里,猜错了你还得回头再排一遍。
顺带提一句常被混在一起的两件事:接入方式和模型能力是两回事。上面这些项属于接入方式——程序去哪儿拿密钥、默认用哪个模型标识。模型本身能做什么(能不能调工具——也就是模型按结构化格式返回参数、由客户端代为执行的那种原生工具调用 / function calling;以及上下文窗口有多大——一次请求里模型能容纳的最大 token 量),是另一码事,跟你把哪个键写在哪一层没有关系。要看这个产品本身怎么接入,另见 Gemini API 接入。
四、横过来看一眼:别家的分层长什么样
Gemini CLI 这套「多个同名文件按顺序覆盖」并不是独一份,但每家的形态差别很大,横着看一眼有助于你不把 A 家的经验套到 B 家。
配置形态大致分四类:图形界面表单(Cline、Roo Code、Kilo Code)、YAML 配置文件(Continue 的 config.yaml、aider 的 .aider.model.settings.yml)、JSON settings(Zed 的 settings.json、Gemini CLI 的 settings.json)、环境变量与 CLI 子命令(goose 的 GOOSE_PROVIDER 加 goose configure,Crush 的 crushrc 加 provider add)。
同样是「多处文件按顺序加载」,aider 的 .aider.model.settings.yml 可以放四处,按顺序加载、后加载的优先,依次是 home 目录、git 仓库根目录、启动 aider 的当前目录、以及 --model-settings-file <filename> 指定的自定义路径。Crush 的 crushrc 则只有两级:项目级 ./.crushrc 优先于全局的 ~/.config/crush/crushrc。三家都叫「分层」,但层数、顺序、谁在顶上,各不相同——记住其中一家的顺序去推另一家,就是本节要劝阻的事。
密钥存放上也各走各的:Zed 的文档明确写了 “Do not put API keys in settings.json.”(这是 Zed 官方文档的说法),凭据走 provider 设置界面或环境变量,命名规则是 <PROVIDER_NAME>_API_KEY;它的 configuration 页还写了 “Provider keys saved through Zed are stored in the system keychain, not in settings.json.”——这里的 keychain 指的是操作系统自带的凭据保管服务。goose 走环境变量或 config.yaml。Gemini CLI 走的是前面说的插值:配置文件里引用变量名,值留在环境里。三条路解决的是同一个问题,写法完全不通用。
五、边界与代价:这套分层不管什么
分层配置换来的是灵活,代价也很实在,值得写清楚。
它放弃了「一个文件说了算」的确定性。 七级链条意味着任何一个值的最终来源都需要推理才能确定。单人单机时这是负担;只有在你确实需要区分「这台机器的默认」「这个项目的口径」「这一次运行的临时值」时,分层才划得得来。如果你只是自己一台机器上跑,把配置集中在一处、别铺开,排查成本会低很多。
它不负责告诉你值是从哪来的。 优先级规则本身只定义了覆盖顺序。运行时会不会给你一个「当前生效值来自第几层」的提示,本篇依据的这一页没有记录这类能力,这不等于该产品没有——想确认请查官方文档。在你确认之前,最稳的排查手段还是自己按层次逐个看文件、看环境。
它管不了拼写和归属。 配置层级解决的是「同一个键出现在多处时谁赢」,不解决「你写的键名是不是它认识的那个」,也不解决「这个键该放在哪个顶层类别下」。名字写歪了,它在任何一层都不会赢,因为它压根不参与这场比较。这类问题的表现和「被高层覆盖」几乎一样,都是「改了没反应」,但排查方向完全相反。
它不管跨机器的一致性。 你本机的 ~/.gemini/settings.json 不会跟着仓库走,同事那台机器上这一层是空的或者是别的值。这正是本文开头那三篇要处理的问题域。
本篇也明确不管这些: 价格、免费额度、限速数字、完整模型清单,以及任何界面长什么样。界面以你实际看到的为准。
六、避坑清单
坑一:只改项目级,忘了上面还有 system settings。
为什么会踩:绝大多数工具的直觉是项目级最贴近、最优先,很少有人预期系统文件排在项目之上。怎么避:改项目级之前先确认 /etc/gemini-cli/settings.json(Linux)里有没有同名项;如果有,你在项目里怎么改都白改,得往上一级处理。
坑二:把两个 /etc 文件当成一个。
为什么会踩:路径同在 /etc/gemini-cli/ 下,名字只差一个词,system-defaults.json 在优先级最底、settings.json 在文件层最顶。怎么避:写之前先看文件名,问自己一句「我是想给一个可被覆盖的默认,还是想压住下面所有层」,两个意图对应两个不同文件,别搞反。
坑三:shell 里残留的环境变量盖住了文件。
为什么会踩:环境变量整体排在四个文件之上,而它常常是你上次调试时临时 export 的,或者从某个 .env 里自动进来的,看不见摸不着。怎么避:改文件不生效时,第一步先在当前 shell 里查一遍同名变量在不在、值是什么,再往下查文件。
坑四:.env 命中了你没预料到的那一个。
为什么会踩:查找顺序是当前工作目录起,逐级向上到项目根或 home,最后兜底 ~/.env。从子目录启动、或者 home 下有个陈年 .env,都会让你以为在用仓库根那份。怎么避:按这个顺序自己走一遍,把路上每个 .env 都列出来,先确定命中的是哪一个再动手改。
坑五:把密钥直接写进 settings.json 然后提交了。
为什么会踩:JSON 配置写起来顺手,一行就填进去了,等想起来时已经在版本历史里。怎么避:用 $VAR_NAME 或 ${VAR_NAME} 插值引用,值放环境里。密钥的通用管理办法见 API Key 安全管理。
坑六:设置项没放进对应的顶层类别。 为什么会踩:从别处抄来的片段常常只给了一行键值,没带它所属的那层对象结构。怎么避:对照文档确认这一项归 general、ui、tools、model、context 里的哪一类,把结构补全再写;只贴一行进去,你可能会以为是优先级问题,其实是位置问题。
坑七:拿 aider 或 Crush 的层级顺序去推 Gemini CLI。 为什么会踩:三家都叫分层配置,但 aider 是四处按加载顺序后来居上、Crush 只有项目级和全局两级、Gemini CLI 是七级链且系统文件在项目之上。怎么避:每换一个工具就重新看一遍它自己的顺序,别复用记忆。
最后给一个能照着走的排查顺序。 值不对时,按下面这几步走,通常两三步就能定位:
- 先看命令行帮助输出里有没有对应这一项的参数(在帮助文本里搜这个配置项的关键词;具体参数名以官方文档和帮助输出为准),有就在启动时显式传一次这个值。行为变了,说明程序认这个配置项,问题是覆盖;没变,去查名字和类别归属。
- 查当前 shell 里有没有同名环境变量:类 Unix 下跑
env | grep <变量名>,Windows PowerShell 下看$Env:<变量名>。能打印出值,就是它赢了。 - 沿
.env的查找顺序把路上所有.env列出来:从当前工作目录开始ls -a看有没有.env,然后逐级cd ..重复到项目根或 home,最后看一眼~/.env。把找到的文件按这个顺序排出来,第一个命中的就是进环境的那份。 - 按 system settings → project settings → user settings → system defaults 的顺序,自上而下看四个文件里谁写了这一项,第一个写了的就是生效的那层。
- 都没有,回头检查键名拼写和它是否放进了对应的顶层类别。
这个顺序是照着优先级链从高到低倒推的,不需要记忆,只要手边有第二节那张表就能复现。
数据来源与核对日期
本篇的产品事实来自以下官方文档页面,核对日期均为 2026-08-07。文中提到的路径、字段名与优先级顺序都是该日期文档页面上的记录,请以官方文档最新版为准。
- Gemini CLI 配置页(四处
settings.json位置、七级优先级顺序、$VAR_NAME/${VAR_NAME}插值、.env查找顺序、GEMINI_API_KEY、GEMINI_MODEL、model.name、security.auth.selectedType、设置按类别组织):https://google-gemini.github.io/gemini-cli/docs/get-started/configuration.html - aider(
.aider.model.settings.yml的四处加载位置与「后加载优先」):https://aider.chat/docs/config/adv-model-settings.html - Crush(
crushrc的项目级与全局两级优先级):https://raw.githubusercontent.com/charmbracelet/crush/main/README.md - Zed(“Do not put API keys in
settings.json.”、<PROVIDER_NAME>_API_KEY、系统 keychain 的两句原话):https://zed.dev/docs/ai/use-api-access、https://zed.dev/docs/ai/configuration - goose(凭据走环境变量或
config.yaml,GOOSE_PROVIDER/GOOSE_MODEL与goose configure):https://goose-docs.ai/docs/getting-started/providers/ - Continue、Cline、Roo Code、Kilo Code(第四节配置形态分类中提到的
config.yaml与图形界面表单):https://docs.continue.dev/reference、https://docs.cline.bot/provider-config/openai-compatible、https://roocodeinc.github.io/Roo-Code/providers/openai-compatible、https://kilo.ai/docs/providers/openai-compatible
本篇没有写什么,以及为什么:
- 不写价格、免费额度、订阅档位、限速数字、版本号和完整模型清单。这类内容变动频繁,本次也未核实,写进来只会误导,请以各产品官方文档为准。
- 不写任何产品的界面样子——菜单在哪、提示文案是什么、什么时候弹出校验。本篇依据的是配置层的文档记录,界面以你实际看到的为准。
- 不写
model.name和security.auth.selectedType的取值与内部行为。依据的那一页只列出了名字,本篇不替它补充用途。 - 不写各家文档谁写得更清楚,也不给这些工具排座次。本篇只做「同一个机制各家做法不同」的客观对照,差异都能在上面列出的来源页里复核。
- 「本篇依据的那一页没有出现某项」不等于「该产品没有这项能力」,需要确认请直接查官方文档。
延伸阅读:同一组里的 Crush 加自定义 provider、Cline 接自定义 OpenAI 兼容 API;接完之后照 接完自定义模型别急着干活 逐项过一遍,才算真接通。