fix(cli): os doctor 按 os serve 的顺序读 .env*,并逐值注明来源 (#5387) - #5398
Merged
Conversation
…ery value (#5387) `serve` / `dev` / `start` load `.env*` through dotenv-flow before reading a single `OS_*` variable (serve.ts:520, dev.ts, start.ts); `doctor` loaded none, so a posture committed to a shared `.env` reached the server and never reached the diagnostic — doctor green, `os serve` refusing to boot the same directory. Doctor now resolves the same cascade dotenv-flow picks for serve (node_env derived as `NODE_ENV || production`, serve's expression minus the `--dev` flag doctor does not have), without merging it into `process.env`: the overlay is applied around the read that needs it and removed in a `finally`. A new always-on `Environment files` check reports which files were loaded and where each declared input came from — source only, never the value — and the posture finding's `.env` sentence now states what was read instead of what was skipped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkPSGsX9o17MsGv3Lbxu2w
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 21 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
baozhoutao
marked this pull request as ready for review
August 5, 2026 07:31
baozhoutao
enabled auto-merge
August 5, 2026 07:32
This was referenced Aug 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5387
前提核验:成立(#5390 合入后仍然成立)
issue 是在 #5382 的实施过程中顺带记下的,而 PR #5390 在本单派发之前已经合入
main。所以第一件事是在origin/main(58975525c)上重跑一遍它的判断,而不是照抄 issue:唯一一处
dotenv是 #5390 写下的那句「我不载入」的注释;env 的读取全部经由resolveTenancyPosture()(@objectstack/types,自己读process.env)。issue 的事实面完全成立,PR #5390 文案里那段自陈的残留也如实。改了什么
按本单已留的 PM 裁定的两半实现,两半缺一都是缺陷。
1)读取顺序对齐 serve。 doctor 用 dotenv-flow 自己的
listFiles()取到os serve会载入的同一份文件清单 —— 不是重写一遍命名约定,所以像「node_env=test时跳过.env.local」这种 dotenv-flow 的既有例外是继承来的,不是记住的。node_env按 serve 同款推导:serve 的表达式是flags.dev ? 'development' : (NODE_ENV === 'test' ? 'test' : (NODE_ENV || 'production')),doctor 没有--dev,在flags.dev为假时那个'test'分支是恒等的,所以NODE_ENV || 'production'与它逐字等价(首版不新增旗标,按裁定)。2)逐值注明来源,不静默合并。 文件里的值不写进
process.env供整轮运行使用:overlay 只在需要它的那一次读取周围套上(withDotenvOverlay),finally里摘掉,用的是 dotenv-flow 自己unload()的判定(只删掉仍然等于写入值的那些)。报告新增一行常驻体检项:为什么必须有这一行:只静默
dotenvFlow.config()会把盲区从「doctor 没读我的.env」平移成「doctor 到底信了我四个.env*里的哪一个」,同一类缺陷往前挪一层。该行只报来源、从不报值(有测试钉住),所以DOCTOR_ENV_INPUTS将来加入带密钥的变量也不会因此泄露;唯一被打印的值是非法的 posture,由 #5382 那条 finding 原样引回,让作者看见自己的拼写。withDotenvOverlay而不是「在 doctor 里重写一遍 posture 解析」的理由:resolveTenancyPosture()自己读process.env,绕开它就等于让 doctor 持有第二份@objectstack/types拥有的词表,可以与os serve实际执行的那份跑偏 —— 正是 #5382 引用解析器原话、不改写措辞时刻意避开的那个坑。3)posture finding 的
.env文案按裁定改写。 #5382 写的是「unlikeos serve,os doctordoes not load.env*files」—— 在当时如实,也正是本单被开出来的原因;现在同一个位置说的是它读到了什么。真机运行(
bin/run.js doctor,临时 cwd).env里放非法 posture、shell 什么都不设:同目录、同一份破
.env,但 shell 里设了合法值(serve 的优先级:shell 胜过文件):两个文件都设时,归因给胜出的那个(指向
.env会把人送去改一个到不了服务器的值):测试
新增
packages/cli/src/commands/doctor-env-provenance.test.ts(24 例):文件清单与优先级、逐键归因、node_env=test跳过.env.local、不可读文件降级为 warning(serve 用silent: true静默忽略,doctor 说出来)、shell 优先(含hasOwnProperty而非真值判定,所以 shell 里显式空值也胜过.env)、overlay 的施加/还原/抛出时仍还原/不误删回调改写的值、报告只报来源不报值、DOCTOR_ENV_INPUTS的漂移守卫(doctor.ts 里出现的每个OS_*都必须被声明,否则下一个 env 派生检查会重新长出无归因的静默合并),以及进程内跑真实命令的端到端差分。doctor-tenancy-posture-report.test.ts(#5390 的 11 例)按裁定调整前提、不削断言:readDotenvFiles()读数(对一个真空目录),这些用例本来测的就是 shell 来源的值,继续测它;.env*」的用例,钉的句子现在是假的 —— 换成它的反面(finding 必须说出读了什么),并保留反过度声称的断言、把它指向已退役的那句话,防止它回潮。诊断说自己没看某处而其实看了,与声称覆盖而其实没有,是同一种不可信。反向验证(方向在跑之前就定了:标准的 before-red / after-green)。 把
readDotenvFiles()打桩成「直接返回空读数」—— 这正是 #5387 之前 doctor 的行为(看不到任何文件),而不是删掉整个 API(那只会得到一堆 import 错误,证明不了行为)。预测:新文件里所有依赖文件来源的用例转红、端到端用例以缺陷本来的样子转红,而 #5382 的 11 例一例不动。实跑:红的方式正是缺陷本身 —— 端到端用例打印出的正是 #5387 描述的那份报告:
整包(桩已撤除):
另跑:
check:nul-bytesOK、check:role-wordOK、check:error-code-casingOK、check:wildcard-fallthroughOK、check:durability-log-levelOK、check:startup-registry-verdictOK、check:adr-anchorsOK、eslint 干净;控制字符自查(grep -naP扫\x00-\x08\x0b\x0c\x0e-\x1f)四个文件均无裸控制字节。一个必须说清的影响面(不是副作用,是本单的内容)
本改动改变的是 doctor 每一项 env 派生检查的输入,今天落在两处:
os doctor对非法OS_TENANCY_POSTURE退出码 0 并报告「环境功能正常」—— 抛错被 config 分析的宽 catch 吞成一句「Could not load config」 #5382);isolated时运行,所以一个只写在.env里的isolated现在会让它在 doctor 里跑起来,与os serve一致。这与派发口径里「若改变既有检查的判定超出预期就停下报告」并不冲突:让 env 派生检查看见 serve 看见的那份环境,正是本单要修的东西;它不是被顺手带进来的。反过来,用户配置文件自己读
process.env的那条路径没有套 overlay,是刻意的克制(见下)。顺带发现(已另开 issue,未在本 PR 修)
os doctor载入objectstack.config.ts时不带.env*overlay —— 配置文件自己读process.env时,doctor 报「Could not load config」而os serve同目录正常启动 #5397 ——loadConfig()载入objectstack.config.ts时没有 overlay,所以配置文件顶层读process.env.X(X 只在.env里)时,doctor 与 serve 仍看到不同的值;若 config 因缺值抛错,doctor 会落进那个宽 catch,打印⚠ Could not load config for analysis (config checks skipped)—— 正是os doctor对非法OS_TENANCY_POSTURE退出码 0 并报告「环境功能正常」—— 抛错被 config 分析的宽 catch 吞成一句「Could not load config」 #5382 判定为「归因错误」的那句话,而os serve同目录正常启动。没有顺手改:给 config 载入套 overlay 会改变既有 config 检查的输入(可能新增或消除 warning),属于诊断输出的另一处契约变化,超出本单被限定的判定面。零件(readDotenvFiles/withDotenvOverlay)已经在这个文件里就位。另一处看过、判断为不需要动:
dev.ts/start.ts各自的dotenvFlow.config()用的是硬编码的'development'/ mode,与 doctor 的推导不同 —— 但 doctor 的定位是回答「os serve将看到什么」,裁定也只要求对齐 serve,dev的.env.development是它自己声明的模式,不构成不一致。🤖 Generated with Claude Code
https://claude.ai/code/session_01VkPSGsX9o17MsGv3Lbxu2w
Generated by Claude Code