发现于 #5387 (doctor 对齐 serve 的 .env* 读取顺序)实施过程中的顺带核验;本单只记录,不在该 PR 里改 —— 原因写在下面。
事实
#5387 之后,doctor 会按 os serve 的顺序读 .env*,但读到的值只在需要它的那一次读取周围临时生效 (withDotenvOverlay),刻意不合并进整轮运行的 process.env。今天套上 overlay 的只有 env 派生检查(posture)那一处;loadConfig() 没有套。
两条命令的顺序因此仍然不同:
serve:dotenvFlow.config()(packages/cli/src/commands/serve.ts:520)→ 之后才 bundleRequire 载入用户的 objectstack.config.ts。配置文件顶层读到的 process.env 含 .env* 的值。
doctor:readDotenvFiles()(不写 process.env)→ loadConfig()(packages/cli/src/commands/doctor.ts 的 config 分析块)。配置文件顶层读到的 process.env 不含 .env* 的值。
后果
配置文件在顶层读环境变量是常见写法(数据源 URL、开关)。当变量只写在 .env 里时:
值不同 —— 依赖该值的结构(条件声明的 object / datasource)在 doctor 与 serve 眼里不是同一份,doctor 的 config 检查判定的是一份服务器不会运行的配置;
更响的一种 —— 配置在缺值时抛错(throw new Error('OS_DATABASE_URL is required') 之类),doctor 会落进 config 分析那个很宽的 try,打印
⚠ Could not load config for analysis (config checks skipped)
记一个 warning、exit 0,而 os serve 在同一个目录正常启动 。这句话正是 os doctor 对非法 OS_TENANCY_POSTURE 退出码 0 并报告「环境功能正常」—— 抛错被 config 分析的宽 catch 吞成一句「Could not load config」 #5382 判定为「归因错误」的那一句:配置本身没问题,问题是 doctor 没把 .env 交给它。
为什么没有顺手改
给 loadConfig() 套上 overlay 会改变既有 config 检查(circular deps / unused objects / orphan views / dashboard integrity / spec 版本)的输入,可能新增或消除 warning —— 属于诊断输出的另一处契约变化,超出 #5387 派发时被限定的判定面(该单的口径明确要求:若改变既有检查的判定超出预期就停下报告)。
需要决定的是同一个问题的另一半:doctor 的 config 分析应当在哪一份 环境下进行 —— serve 的那份(overlay 覆盖 loadConfig(),与运行时一致,但既有检查的判定面会变),还是当前这份(只覆盖显式声明的 env 输入,判定面不变,但 config 那条路径与 serve 仍不一致)。
现状
零件已经就位,不需要新机制:readDotenvFiles() / withDotenvOverlay() 就在同一个文件里(#5387 引入,packages/cli/src/commands/doctor.ts),把 loadConfig() 的调用包进去即可。packages/cli 下没有任何测试钉 doctor 的 config 载入与 .env 的关系。
Depends-on: #5387 (其 PR 引入的两个零件是本单的前置)。与 #4801 、cloud#1020 同属「诊断面与运行时不一致」家族。
Generated by Claude Code
发现于 #5387(doctor 对齐 serve 的
.env*读取顺序)实施过程中的顺带核验;本单只记录,不在该 PR 里改 —— 原因写在下面。事实
#5387 之后,doctor 会按
os serve的顺序读.env*,但读到的值只在需要它的那一次读取周围临时生效(withDotenvOverlay),刻意不合并进整轮运行的process.env。今天套上 overlay 的只有 env 派生检查(posture)那一处;loadConfig()没有套。两条命令的顺序因此仍然不同:
serve:dotenvFlow.config()(packages/cli/src/commands/serve.ts:520)→ 之后才bundleRequire载入用户的objectstack.config.ts。配置文件顶层读到的process.env含.env*的值。doctor:readDotenvFiles()(不写process.env)→loadConfig()(packages/cli/src/commands/doctor.ts的 config 分析块)。配置文件顶层读到的process.env不含.env*的值。后果
配置文件在顶层读环境变量是常见写法(数据源 URL、开关)。当变量只写在
.env里时:值不同 —— 依赖该值的结构(条件声明的 object / datasource)在 doctor 与 serve 眼里不是同一份,doctor 的 config 检查判定的是一份服务器不会运行的配置;
更响的一种 —— 配置在缺值时抛错(
throw new Error('OS_DATABASE_URL is required')之类),doctor 会落进 config 分析那个很宽的try,打印记一个 warning、exit 0,而
os serve在同一个目录正常启动。这句话正是os doctor对非法OS_TENANCY_POSTURE退出码 0 并报告「环境功能正常」—— 抛错被 config 分析的宽 catch 吞成一句「Could not load config」 #5382 判定为「归因错误」的那一句:配置本身没问题,问题是 doctor 没把.env交给它。为什么没有顺手改
给
loadConfig()套上 overlay 会改变既有 config 检查(circular deps / unused objects / orphan views / dashboard integrity / spec 版本)的输入,可能新增或消除 warning —— 属于诊断输出的另一处契约变化,超出 #5387 派发时被限定的判定面(该单的口径明确要求:若改变既有检查的判定超出预期就停下报告)。需要决定的是同一个问题的另一半:doctor 的 config 分析应当在哪一份环境下进行 —— serve 的那份(overlay 覆盖
loadConfig(),与运行时一致,但既有检查的判定面会变),还是当前这份(只覆盖显式声明的 env 输入,判定面不变,但 config 那条路径与 serve 仍不一致)。现状
零件已经就位,不需要新机制:
readDotenvFiles()/withDotenvOverlay()就在同一个文件里(#5387 引入,packages/cli/src/commands/doctor.ts),把loadConfig()的调用包进去即可。packages/cli下没有任何测试钉 doctor 的 config 载入与.env的关系。Depends-on: #5387(其 PR 引入的两个零件是本单的前置)。与 #4801、cloud#1020 同属「诊断面与运行时不一致」家族。
Generated by Claude Code