明明改了配置却没生效:多层配置文件与环境变量的覆盖顺序怎么查

2026-08-07

你改了配置却没生效,绝大多数情况下不是值填错了,而是你改的那一层根本不是最终生效的那一层。 这类工具的配置几乎都是「多处同名文件 + 环境变量 + 命令行参数」叠在一起的,每一层都能覆盖下面一层。只要你不知道自己动的是第几层,改一百遍也白改。这篇讲的就是怎么把这个层级链条摊开来看。

站内已有三篇和这个话题挨着:AI 帮你改配置文件 讲的是让 AI 动手改配置内容本身,配置传环境变量 讲的是怎么把配置项通过环境变量传进程序,运行环境不一致 讲的是同一份代码在不同机器上跑出不同结果。本篇不重复这些,只盯一件事:当同一个设置在多个位置都出现时,官方文档记载的加载顺序是什么,你该按什么次序去排查。

先约定两个词,后面反复用。环境变量是操作系统给进程的一组键值对,程序启动时能读到,改了它不用改任何文件;OpenAI 兼容端点是指某个服务把接口做成和 OpenAI 那套请求格式一样,于是客户端只要换个地址和密钥就能连上去,不必为它单独写适配。

一、先把问题定性:症状一样,成因分三类

「改了没生效」这句话底下其实藏着三种完全不同的成因,分开看才好查。

第一类是层级覆盖:你改的文件确实被读了,但另一处优先级更高的位置也写了同一项,结果以那边为准。这是本篇的主角。

第二类是位置没读到:你改的那个文件压根不在工具查找的路径里,比如放到了工具不看的目录,或者文件名差一个字符。

第三类是值本身没被接受:格式、层级、类型不对,或者这一项要放在某个类别对象里而你放在了顶层。以 Gemini CLI 为例,截至 2026-08-07 其官方文档说明,设置按类别组织成顶层对象(general、ui、tools、model、context 等),每个设置要放进对应的类别里。按这个组织方式推断,把某一项写在类别之外,工具多半就不会在那儿读到它——这是从文档描述的结构推的判断,官方文档并未逐条说明写错位置的具体后果,以官方文档为准。

排查时不要一上来就怀疑值。先确认层级,再确认路径,最后才是格式。理由很简单:前两类问题你看文件内容是看不出来的,而绝大多数人第一反应恰恰是盯着文件内容反复看。

二、把优先级写成一条链的:Gemini CLI

Gemini CLI 的配置文档把加载顺序逐级列了出来,正好拿来当参照物。截至 2026-08-07 其官方配置文档记载:

settings.json 有四处位置——系统默认 /etc/gemini-cli/system-defaults.json(Linux)、用户级 ~/.gemini/settings.json、项目级即项目根的 .gemini/settings.json、系统覆盖 /etc/gemini-cli/settings.json(Linux)。

而配置优先级从低到高是:硬编码默认 → system defaults 文件 → user settings → project settings → system settings → 环境变量 → 命令行参数。

这条链有两个地方值得停一下。

一是 user settings 在 project settings 下面。也就是说,你在自己 home 目录里精心调好的默认模型,只要当前项目根下的 .gemini/settings.json 也写了同一项,进到这个项目就以项目的为准。团队仓库里带着一份提交进版本库的项目级配置,是这类「在别的项目好好的,一到这个项目就变了」的常见来源。

二是 /etc/gemini-cli/settings.json 这一档排在项目级之上。管理员放在系统路径下的设置,能盖住项目里的写法。如果你的机器是别人装好交给你的,这个位置值得看一眼——不是每个人都知道它存在。

再往上还有两层,跟文件完全无关:环境变量高于所有 settings 文件,命令行参数高于环境变量。文档还列出了相关项 GEMINI_API_KEY(Gemini API 密钥)、GEMINI_MODEL(默认模型),以及 settings.json 里的 model.namesecurity.auth.selectedType

这里就出现了一个非常典型的翻车现场:你在 settings.json 里改了 model.name(文档把它与 GEMINI_MODEL 一并列在模型相关项里,两者各自的确切语义以官方文档为准),但你的 shell 启动脚本里早就 export 过 GEMINI_MODEL。按上面这条链,环境变量整体排在 settings 文件之上——同一项两边都设过,你改文件就是改不动它。

环境变量插值:让配置文件能进版本库

同一份文档还记载了两个跟环境变量有关的机制。

一个是 settings.json 内支持环境变量插值,写法是 $VAR_NAME${VAR_NAME},加载时自动解析。所谓插值,就是文件里写的是变量名这个占位符,真正的值在加载那一刻从环境变量里取。这个机制的实际价值是:配置文件可以放心提交进版本库,因为里面写的是变量名而不是密钥本身。

另一个是 .env 文件的查找顺序:当前工作目录 → 逐级父目录(到项目根或 home 为止)→ 用户 home 的 ~/.env。这条顺序意味着,你在深层子目录里跑命令时,可能被上面某一级目录里遗留的 .env 命中——而那个文件你可能已经忘了它的存在。

三、同名文件放多处:aider 与 Crush 的两种排法

不是每家都把优先级写成一条七级长链,更常见的做法是「同名文件可以放在几个位置,按某个顺序加载」。

aider 的做法:.aider.model.settings.yml 可放四处,按顺序加载、后加载的优先——home 目录、git 仓库根目录、启动 aider 的当前目录、以及 --model-settings-file <filename> 指定的自定义路径。

「后加载的优先」这五个字要读进去。它等价于:越靠近你当下这次调用的位置,话语权越大。命令行上显式指定的那个文件排在最后,所以它说了算;home 目录里那份最像「默认值」,谁都能盖它。这跟 Gemini CLI 的方向是一致的——个人默认在下,项目和当次调用在上。

aider 文档里另有一项跟本篇直接相关:对 aider 不认识的模型,用 .aider.model.metadata.json 注册上下文上限与价格。文档给出的示例结构包含 max_tokensmax_input_tokensmax_output_tokensinput_cost_per_tokenoutput_cost_per_tokenlitellm_providermode 这些键(示例里的数值是官方文档的示例值,不是任何真实模型的报价)。这里要提醒的是:这是两个不同的文件,一个管模型设置,一个管模型元数据。按文档给这两份文件划的分工推断,把该写进 metadata 的东西写进 settings、或者反过来,就会落进第一节说的第二类问题——文件是有的,但工具大概率不在那儿找这一项;文档没有逐条列举这种写法的后果,以官方文档为准。

顺带说一句上下文窗口。上下文窗口指模型单次调用能容纳的输入加输出的 token 上限;对那些工具端不认识的模型,这个数往往需要你自己填。aider 这边由 .aider.model.metadata.jsonmax_input_tokens / max_output_tokens 承担。

另外 aider 还有一个特殊模型名 aider/extra_params,可让设置对所有模型全局生效;extra_params 本身可把任意参数透传给 litellm.completion(),包括 extra_headers。当你发现某个参数对所有模型都莫名其妙地生效,先去看看有没有人写过这个全局条目。

Crush 的排法更短。它的配置文件是 crushrc(bash 风格加 Crush 内建命令,不是 JSON),README 列出的优先级是:./.crushrc(项目级)在前,~/.config/crush/crushrc(全局,Unix 类系统)在后。按 README 的这个排列,同样是项目在全局之前。

Crush 的配置形态本身也值得留意:它不是一份结构化数据,而是 bash 风格的脚本加内建命令,自定义 provider 用的是命令,README 给出的示例是这样一行:

provider add deepseek --type openai-compat \
  --base-url "https://api.deepseek.com/v1"

对排查来说,这个差别很实际——结构化配置文件里同一个键写两遍,通常后一遍覆盖前一遍;而命令式配置里,你得关心的是这些命令被执行了几次、以什么顺序执行。这一段是按两种配置形态的一般规律讲的,不是 README 的记载。README 还写明,自定义 provider 必须是 OpenAI 兼容或 Anthropic 兼容 API。

四、环境变量与密钥:goose 和 Zed 的两个提醒

goose 把接入点大量放在了环境变量上。其官方文档记载:GOOSE_PROVIDERGOOSE_MODEL 设默认 provider 与模型;各家密钥用各自的变量(文档举例 ANTHROPIC_API_KEYOPENAI_API_KEY);对接自建或企业内部的 OpenAI 兼容端点,设 OPENAI_HOST,可选 OPENAI_BASE_PATH;配置本身持久化在 config.yaml。CLI 侧的配置流程是跑 goose configure → 选 Configure Providers → 从列表选 provider → 填 API key 与附加参数 → 选模型。

注意 OPENAI_HOSTOPENAI_BASE_PATH拆成两段的设计,跟别家把整个地址塞进一个字段不一样。这也是本篇要专门强调的一点:各家的地址字段完全不是同一个东西——Continue 的配置文件里叫 apiBase,Zed 的 settings.json 里叫 api_url,goose 这边是 OPENAI_HOST(另有 OPENAI_BASE_PATH),Crush 是命令行参数 --base-url。看着都是「填地址」,字段名、所在层级、是否分段都不同,照搬另一家的写法就是白改。

需要如实说明的是:goose 的环境变量与 config.yaml 里的同名配置冲突时谁优先,本篇依据的那一页记录里没有出现相关说明——这不等于官方文档别处没写,只是本篇不据此下结论,请以官方文档为准。实操上更稳的做法是:改之前先看当前 shell 里这些变量有没有被设过。

Zed 提供的是另一个方向的提醒:密钥可能根本不在你翻的那个文件里。其文档原话是 “Do not put API keys in settings.json.”,另一页写的是 “Provider keys saved through Zed are stored in the system keychain, not in settings.json.”。keychain(系统钥匙串)是操作系统提供的凭据存储,密钥存在里面,程序按名字去取,明文不落在配置文件里。文档也给出了走环境变量的路径,命名规则是 <PROVIDER_NAME>_API_KEY——provider 名为 my-provider 时对应 MY_PROVIDER_API_KEY

这意味着,密钥类问题不能靠 grep 配置文件来定位:你在文件里搜不到当前生效的那把 key,是设计如此,不是配置丢了。关于密钥本身的管理,可以另看 API 密钥安全管理

位置对照表

下面这张表把上面提到的位置排在一起。第三列是按各家文档记载的顺序推出的结论,不是文档逐条列举的故障现象。

配置位置 / 字段它是什么谁排在它上面(按文档记载的顺序推出)出处
/etc/gemini-cli/system-defaults.json(Linux)Gemini CLI 系统默认设置文件除硬编码默认外的所有层Gemini CLI 官方文档
~/.gemini/settings.jsonGemini CLI 用户级设置项目级、系统覆盖、环境变量、命令行参数同上
.gemini/settings.json(项目根)Gemini CLI 项目级设置/etc/gemini-cli/settings.json、环境变量、命令行参数同上
/etc/gemini-cli/settings.json(Linux)Gemini CLI 系统覆盖设置环境变量、命令行参数同上
GEMINI_MODELGemini CLI 默认模型的环境变量命令行参数同上
$VAR_NAME / ${VAR_NAME}settings.json 内的环境变量插值写法取决于该变量当时的取值同上
.aider.model.settings.ymlaider 模型设置文件,可放 home、git 仓库根、当前目录加载顺序在它之后的那几处aider 官方文档
--model-settings-file <filename>aider 指定模型设置文件路径的参数文档列出的四处里它最后加载同上
.aider.model.metadata.json给 aider 不认识的模型注册上下文上限与价格文档把它与模型设置文件列为用途不同的两份文件,不在同一条加载链上同上
./.crushrcCrush 项目级配置文件README 把它列在全局之前Crush README
~/.config/crush/crushrcCrush 全局配置(Unix 类系统)项目级 ./.crushrc同上
GOOSE_PROVIDER / GOOSE_MODELgoose 默认 provider 与模型的环境变量config.yaml 的优先关系,本篇依据的那一页未记载goose 官方文档
OPENAI_HOST / OPENAI_BASE_PATHgoose 指向自建 OpenAI 兼容端点的两段式设置同上同上

五、边界与代价:这套查法不管什么

按层级链条从上往下查,是成本最低的排查法,但它有明确的适用边界,讲清楚才不会用错地方。

它只管「同一项在多处出现」。 如果某个设置在整台机器上只写了一处,那这套方法给不出任何信息,问题多半在格式、路径或者值本身。

它不解释模型侧的行为。 接入方式和模型能力是两件事。地址、密钥、模型 ID 都配对了,请求也发出去了,但输出不理想、工具调不起来、长上下文被截断——这些属于模型能力和协议层面的事,跟配置在第几层没关系。举个协议层的例子:OpenAI 兼容协议下,最大输出设得过小、或者请求的窗口超出服务端实际支持的上限,都会表现为截断或报错——这是通用机制的推理,不是上述任何一家文档的记载。

它不替你判断某个字段该怎么填。 上面说过,各家地址字段名不同、是否分段不同。文档里只对某一家写明了填法的,就只对那一家成立,搬到另一家就是编造。想看各家接入方式本身的差别,可以另看 编辑器接入自定义 API

它对图形界面表单帮助有限。 配置形态大致分四类:图形界面表单、YAML 配置文件、JSON settings、以及环境变量与 CLI 子命令。本篇讲的层级链条主要落在后三类上;界面表单里填的东西存在哪一层,各家做法不同,以你实际看到的界面和官方文档为准。

代价方面,把设置分散到多层是有实际成本的:层数越多,「当前生效值到底是多少」这个问题越难当场回答。多层设计换来的是灵活性——个人默认、项目约定、临时覆盖各归其位。你得接受的代价是:每次排查都要多问一句「这一项还在别处写过吗」。

六、避坑清单:为什么会踩,怎么避

一、只改了自己 home 里那份,忘了项目根下还有一份。 会踩,是因为 home 目录那份是你亲手配的、印象最深,而项目里那份往往是别人提交进版本库的,你从没打开过。避法:改之前先在项目根下找一遍同名文件或同名目录,Gemini CLI 是 .gemini/settings.json,Crush 是 ./.crushrc,aider 是 git 仓库根目录下的 .aider.model.settings.yml

二、shell 里 export 过的变量把文件改动全盖住了。 会踩,是因为环境变量是「无形」的——它不在任何你会打开的文件里,可能来自几个月前加进 shell 启动脚本的一行。按 Gemini CLI 文档记载的顺序,环境变量排在所有 settings 文件之上。避法:改文件前,先在你实际运行工具的那个终端里把相关变量打印一遍——逐个 echo $GEMINI_MODEL 这样看,或者用 env 列出全部变量再按名字筛一遍。打印结果为空,才说明文件那一层有机会生效;打印出有值,就先决定是清掉这个变量,还是干脆改变量而不是改文件。

三、命令行参数临时加过一次,之后忘了它还在别名或脚本里。 会踩,是因为参数排在最顶层,而它常常藏在 shell 别名、Makefile、CI 脚本里,不在你的视线内。避法:确认你实际执行的那条命令的完整形态,而不是你以为的那条——在终端里用 type <命令名> 看看它有没有被 alias 或 shell 函数包过一层,再翻一遍 Makefile、npm scripts、CI 配置里调用它的那几行,把参数抄出来逐个对。

四、把 aider 的两个文件搞混。 会踩,是因为 .aider.model.settings.yml.aider.model.metadata.json 名字太像,且都跟模型有关。避法:记住分工——模型设置走前者,不认识的模型的上下文上限与价格注册走后者。

五、拿另一家的字段名往这家配置里填。 会踩,是因为它们功能相近、看着像同义词。但 apiBaseapi_urlOPENAI_HOST--base-url 各属各家,串台就是配了个根本不存在的键。避法:每次只照着当前这一家的官方文档抄字段名,别凭印象写。

六、地址里 /v1 加不加,靠猜。 会踩,是因为不同服务商的端点结构本来就不统一。避法:以你要接的那家服务商官方文档给出的地址为准,同时看清楚客户端这一侧要的是完整地址还是分段填写——goose 的 OPENAI_HOSTOPENAI_BASE_PATH 就是分成两段的。

七、在配置文件里搜密钥,搜不到就以为配置丢了。 会踩,是因为默认假设「所有配置都在文件里」。Zed 文档明确说凭据存在系统 keychain 而不是 settings.json。避法:把密钥当成独立的一层来查——它可能在 keychain、可能在环境变量、可能通过插值引用,就是不在明文配置里。

八、改完不复现就下结论。 会踩,是因为改完之后往往还得重开终端或重启进程,环境变量才会重新读取。避法:改一处、验一次,别一口气改三个地方然后猜是哪个起了作用。

以上做法与 编辑器接入自定义 API 里的接入步骤是互补的:那边讲怎么配通,这边讲配了没通时按什么顺序拆。

数据来源与核对日期

以下 URL 为本篇引用事实的官方文档来源,核对日期均为 2026-08-07。文档会更新,正文提到的字段名与加载顺序都是截至该日期的情况,请以各产品官方文档最新版为准。

本篇没有写什么,以及为什么。 价格、免费额度、订阅档位、限速数字、版本号、完整模型清单一律没写——这类信息变动频繁,且本次没有逐项核实,写进来只会误导;需要时请查各产品官方文档与服务商的定价页。各家界面长什么样、菜单在哪一级、报错文案是什么,本篇也没有描写,因为依据的是配置层文档而非界面记录,以你实际看到的界面为准。文中凡标注「本篇依据的那一页记录里没有出现」的地方,只表示本篇引用的那一页没写,不代表该产品没有这项能力或官方文档别处没写。此外,Zed 那两句英文是官方文档的原话,本篇如实引用,不代表本篇对任何产品的背书或评价,也不对各家文档做优劣比较。

延伸阅读:同一组里的 团队统一编辑器模型配置编辑器接自定义模型后这笔钱怎么算;接完之后照 接完自定义模型别急着干活 逐项过一遍,才算真接通。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。