Repository navigation
os doctor 不加载 .env*,读到的环境与 serve/dev/start 实际运行的不是同一份 —— 写在 .env 里的配置错误 doctor 一律看不见 #5387
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on Aug 5, 2026 分诊(cli 车道 PM,session_01VkPSGsX9o17MsGv3Lbxu2w,2026-08-05):入队
pm:queue,并附 PM 裁定(留否决窗口 —— 维护者可否决,不等批准)。方向裁定:doctor 模拟 serve 的读取顺序,且按值注明来源,不静默 merge。 依据:
- 恢复既有不变量,非新决策 —— 「诊断面与运行时读同一份现实」是本仓付过两次账的规范(CLI 启动 banner 的
Tenancy:读布尔 multiTenant,与 ADR-0105 D1 的权威 knobOS_TENANCY_POSTURE对不上——isolated 会显示成 single-tenant #4801、cloud#1020),os doctor对非法OS_TENANCY_POSTURE退出码 0 并报告「环境功能正常」—— 抛错被 config 分析的宽 catch 吞成一句「Could not load config」 #5382 修的正是它在 posture 上的一半;.env这半边同属恢复一致性,方向已被规范决定。 - 两轴:长远轴 —— doctor 的价值就是回答「serve 将看到什么」,读取顺序对齐 serve(dotenv-flow,
node_env按 serve 同款推导:NODE_ENV || production,首版不新增旗标);防 AI 轴 —— **按值注明来源(shell / 哪个.env文件)**优于静默合并:静默 merge 正是 issue 第 3 点指出的「分不清来源」的温床,而带来源的报告让配置错误可归因。 - 实现机制(读文件不污染
process.env,还是先快照后对比)由 dev 按代码实况定;若发现必须引入新旗标或改变既有检查的判定结果超出预期面,按 needs_decision 停下报告。
非 v17 窗口件,排本车道 v17 工作集之后;派发时范本引 PR #5381(serve 的 dotenv 时序论证)与 PR #5390(doctor 的 verdict 形态)。
Generated by Claude Code
- 恢复既有不变量,非新决策 —— 「诊断面与运行时读同一份现实」是本仓付过两次账的规范(CLI 启动 banner 的
🔒 认领:PM 循环第 4 轮(cli 车道)
会话:session_01VkPSGsX9o17MsGv3Lbxu2w
分支:claude/issue-5387-doctor-dotenv-parity
Worktree:objectstack-issue-5387
域:domain:cli
文件面:packages/cli/src/commands/doctor.ts+ 测试 + changeset。⛔ 不动 serve.ts / dev.ts / start.ts / env.ts。与同批 #4633(packages/rest)、#5244(examples)不相交。执行口径按本单已留的 PM 裁定:模拟 serve 的读取顺序(dotenv-flow,
node_env按 serve 同款推导NODE_ENV || production,首版不新增旗标),按值注明来源(shell / 哪个 .env 文件),不静默 merge 进 process.env;实现机制(独立读文件或先快照后对比)按代码实况定;若必须引入新旗标或改变既有检查判定超出预期,needs_decision 停下。范本:PR #5381(dotenv 时序论证)、PR #5390(doctor 的 verdict 形态,resolveTenancyPostureOrFinding就在你要改的文件里 —— posture 检查的输入随本单变化,注意保持其测试绿)。in-process 测试一律显式}, 60_000)。
Generated by Claude Code
✅ 验收通过 —— PR #5398,转 ready 挂合并队列
对实况复核(cli 车道 PM,session_01VkPSGsX9o17MsGv3Lbxu2w):
- 裁定两半齐全:①读取顺序经 dotenv-flow 自己的
listFiles()对齐 serve(node_env推导与 serve.ts:517-519 逐字等价的化简,论证成立;继承而非记住 dotenv-flow 的例外);②逐值来源、零process.env污染 ——withDotenvOverlay只包住单次读取,finally用 dotenv-flow 自己的unload()判定还原;常驻Environment files体检行只报来源不报值(测试钉住,DOCTOR_ENV_INPUTS未来放入密钥变量也不泄露)。归因给胜出来源(双文件场景指向.env.production)—— 防把人送去改一个到不了服务器的值。 - 不绕开
resolveTenancyPosture()的理由成立(避免 doctor 持有 types 词表的第二份)。 - 影响面如实申报:ADR-0120 D5e 建议随 env 输入变化在 doctor 下运行起来 —— 这正是本单要修的「与 serve 同见」,非顺手扩面;
loadConfig()刻意不套 overlay,立为os doctor载入objectstack.config.ts时不带.env*overlay —— 配置文件自己读process.env时,doctor 报「Could not load config」而os serve同目录正常启动 #5397。 - 测试:24 新例(含 shell 显式空值胜出的
hasOwnProperty语义、overlay 抛出仍还原、漂移守卫「doctor.ts 出现未声明 OS_* 即红」)+ fix(cli):os doctor指名道姓报告非法OS_TENANCY_POSTURE并非零退出 (#5382) #5390 的 11 例按裁定调整前提不削断言(反过度声称断言保留并重指向已退役句);反向验证以「打桩空读数」还原缺陷原形(13 红且 e2e 打印的正是os doctor不加载.env*,读到的环境与serve/dev/start实际运行的不是同一份 —— 写在.env里的配置错误 doctor 一律看不见 #5387 描述的报告),fix(cli):os doctor指名道姓报告非法OS_TENANCY_POSTURE并非零退出 (#5382) #5390 例一例未动;整包 75 文件 / 705 用例绿;七道门 + eslint + 控制字符自查干净;真机三场景对照(.env独有非法值:exit 0 无提示 → exit 1 指名 + 来源)。
转 ready 挂 auto-merge,跟到 MERGED。
Generated by Claude Code
- 裁定两半齐全:①读取顺序经 dotenv-flow 自己的
- added 4 commits that reference this issue
on Aug 6, 2026 - added a commit that references this issue
on Oct 9, 2026
发现于 #5382(doctor 的 posture 报告,PR 见下)实施过程中的顺带核验;本单只记录,不在该 PR 里改(那单的文件面被限定为
packages/cli/src/commands/doctor.ts的 posture 读取,而本单的修法会改变 doctor 每一项 env 派生检查所看到的输入,属于另一个决定)。事实
serve/dev/start三条命令都在run()早期用 dotenv-flow 载入.env*:packages/cli/src/commands/serve.ts:516—dotenvFlow.config({ node_env: mode, silent: true })packages/cli/src/commands/dev.ts:132—dotenvFlow.config({ node_env: 'development', silent: true })packages/cli/src/commands/start.ts:121—dotenvFlow.config({ node_env: mode, silent: true })doctor.ts没有任何 dotenv 载入(grep -n dotenv packages/cli/src/commands/doctor.ts无匹配),只看process.env。后果
诊断面读到的环境,和被诊断的那条命令实际运行时的环境,不是同一份。具体到 #5382 修完之后的那项检查:
也就是说 #5382 修好的那条「doctor 与 serve 不一致」,在
.env这条来源上仍然成立 —— 只是从「归因错误」缩小成了「完全看不见」。PR 里的文案已经如实写明这一点(「os doctordoes not load.env*files, so a value set in one is not visible here」),不冒充检查过,但残留本身值得单独记一笔。.env恰恰是这个变量最常见的来源:PR #5381 把 serve 的闸门刻意放在 dotenv 载入之后,理由正是「该变量常常来自提交进仓库的.env*而不是 shell」。为什么现在才成为问题
在 #5382 之前,
doctor.ts里process.env的直接读取是 0 处 —— 它此前根本没有 env 派生的检查,所以「不加载 .env」不产生可观察差异。#5382 给它加了第一项(posture),这条不一致才有了落点。任何后续想在 doctor 里检查OS_*配置的工作都会撞上同一堵墙。需要决定的地方(所以没有顺手改)
在 doctor 里加一行
dotenvFlow.config()表面上是一行,但它:node_env—— serve 从--dev/NODE_ENV推导,start用 mode,doctor 没有对应的旗标,选错会读到另一套.env.<mode>;.env里的值写进当前进程的process.env,doctor 之后若还要报告「你的 shell 里设了什么」就分不清来源了 —— 可能需要把「从哪读到的」也报出来,而不是静默合并。倾向:doctor 应当模拟 serve 的读取顺序并在报告里注明每个值的来源(shell / 哪个
.env文件),而不是静默 merge —— 但这是诊断输出的契约变化,交维护者裁定。现状证据
packages/cli下没有任何测试钉 doctor 与.env的关系。Context: #5382 / #5359 / PR #5381。与 #4801、cloud#1020 同属「诊断面与运行时不一致」家族。