Skip to content

os doctor 不加载 .env*,读到的环境与 serve/dev/start 实际运行的不是同一份 —— 写在 .env 里的配置错误 doctor 一律看不见 #5387

Description

@baozhoutao

发现于 #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 修完之后的那项检查:

# .env(提交进仓库,团队共享)
OS_TENANCY_POSTURE=isolatd

$ os doctor      # 看不到 .env,posture 解析成 single,报绿,exit 0
$ os serve       # 载入 .env,读到 isolatd,FATAL 拒绝启动(PR #5381 的闸门)

也就是说 #5382 修好的那条「doctor 与 serve 不一致」,在 .env 这条来源上仍然成立 —— 只是从「归因错误」缩小成了「完全看不见」。PR 里的文案已经如实写明这一点(「os doctor does 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() 表面上是一行,但它:

  1. 改变 doctor 每一项(现在的和将来的)env 派生检查的输入;
  2. 需要选一个 node_env —— serve 从 --dev / NODE_ENV 推导,start 用 mode,doctor 没有对应的旗标,选错会读到另一套 .env.<mode>;
  3. 会把 .env 里的值写进当前进程的 process.env,doctor 之后若还要报告「你的 shell 里设了什么」就分不清来源了 —— 可能需要把「从哪读到的」也报出来,而不是静默合并。

倾向:doctor 应当模拟 serve 的读取顺序并在报告里注明每个值的来源(shell / 哪个 .env 文件),而不是静默 merge —— 但这是诊断输出的契约变化,交维护者裁定。

现状证据

packages/cli 下没有任何测试钉 doctor 与 .env 的关系。


Context: #5382 / #5359 / PR #5381。与 #4801、cloud#1020 同属「诊断面与运行时不一致」家族。

Activity

  1. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    ContributorAuthor

    分诊(cli 车道 PM,session_01VkPSGsX9o17MsGv3Lbxu2w,2026-08-05):入队 pm:queue,并附 PM 裁定(留否决窗口 —— 维护者可否决,不等批准)。

    方向裁定:doctor 模拟 serve 的读取顺序,且按值注明来源,不静默 merge。 依据:

    1. 恢复既有不变量,非新决策 —— 「诊断面与运行时读同一份现实」是本仓付过两次账的规范(CLI 启动 banner 的 Tenancy: 读布尔 multiTenant,与 ADR-0105 D1 的权威 knob OS_TENANCY_POSTURE 对不上——isolated 会显示成 single-tenant #4801、cloud#1020),os doctor 对非法 OS_TENANCY_POSTURE 退出码 0 并报告「环境功能正常」—— 抛错被 config 分析的宽 catch 吞成一句「Could not load config」 #5382 修的正是它在 posture 上的一半;.env 这半边同属恢复一致性,方向已被规范决定。
    2. 两轴:长远轴 —— doctor 的价值就是回答「serve 将看到什么」,读取顺序对齐 serve(dotenv-flow,node_env 按 serve 同款推导:NODE_ENV || production,首版不新增旗标);防 AI 轴 —— **按值注明来源(shell / 哪个 .env 文件)**优于静默合并:静默 merge 正是 issue 第 3 点指出的「分不清来源」的温床,而带来源的报告让配置错误可归因。
    3. 实现机制(读文件不污染 process.env,还是先快照后对比)由 dev 按代码实况定;若发现必须引入新旗标或改变既有检查的判定结果超出预期面,按 needs_decision 停下报告。

    非 v17 窗口件,排本车道 v17 工作集之后;派发时范本引 PR #5381(serve 的 dotenv 时序论证)与 PR #5390(doctor 的 verdict 形态)。


    Generated by Claude Code

  2. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    ContributorAuthor

    🔒 认领: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

  3. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    ContributorAuthor

    ✅ 验收通过 —— PR #5398,转 ready 挂合并队列

    对实况复核(cli 车道 PM,session_01VkPSGsX9o17MsGv3Lbxu2w):

    转 ready 挂 auto-merge,跟到 MERGED。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions