You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[maintainer] os doctor in a freshly scaffolded dev project warns "NODE_ENV Not set — this environment is being treated as production"; beside an objectstack.config.ts with no artifact it should read as development #22163
Filing gate: ③ task dispatched by the maintainer, quoted verbatim below; the observation is class (a), this session. The current row is a deliberate outcome of #5673 (PR #5951), so this card records the maintainer's new direction against that ruling rather than a defect in its implementation.
reach: npx os doctor in a project just written by npm create objectstack@latest (17.7.0) prints ⚠ NODE_ENV Not set — this environment is being treated as production and ✓ Environment files No .env* files here (node_env=production) …; npx os dev in the same directory sets development.
Reader: domain:cli seat (packages/cli/src/commands/doctor.ts, nodeEnvCheck() and doctorNodeEnv(); doctor-env-provenance.test.ts pins the production default).
Dedup: search_issues "os doctor NODE_ENV not set treated as production warning in a dev project objectstack.config.ts" → 4 hits, all closed: #5673 (the ruling that unset reads as production everywhere, and added this doctor row), #5397 and #5387 (doctor and .env* loading), #16491 (unrelated test). None covers a source-checkout posture.
Prior rulings read: #5673 / PR #5951 — "NODE_ENV 未设置时 /discovery 报 production,doctor 新增缺省提示行". The safety argument there is about the machine-readable /discovery field and os serve: a client must never be told "development" by omission. That argument is untouched by this card.
Filed on the maintainer's instruction in this session (category ③, quoted verbatim): 「os doctor 在开发项目里警告 "NODE_ENV not set — treated as production"。 刚脚手架完就被告知在跑生产,令人困惑;旁边有 objectstack.config.ts 且无产物时应按开发语义判断。」
Today
The first thing a newcomer sees from os doctor, thirty seconds after scaffolding, is a warning that they are running production. The fix: text that follows (12 lines) explains the /discovery safety rationale and tells them to set NODE_ENV=development for local development — which os dev, the command the scaffolder's "Next steps" told them to run, already does on its own. Nothing in the project is in production; the warning is about a default the runtime would take in a posture the project is not in.
Asked for
doctor distinguishes the posture it is run in:
Source checkout — cwd holds objectstack.config.ts and no dist/objectstack.json / OS_ARTIFACT_PATH, the posture os dev serves: the row reads as development, at ✓/ℹ: NODE_ENV Not set — os dev runs this project as development; os start forces production. No fix text.
Path: the road — start: os doctor reads a dev project as dev | 缺项 | P3
Triage: first grade, enhancement · priority:p3 · domain:cli · area:devpath · pm:queue (finding removed). The maintainer's new direction against #5673's ruling, as dispatched
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-08T05:12Z. ⛔ Not a claim, ⛔ not a dispatch.
Triage: lands in packages/cli/src/commands/doctor.ts (nodeEnvCheck(), doctorNodeEnv()) and doctor-env-provenance.test.ts ⇒ domain:cli; rationale: packages/cli is that lane's. Read on mainec8f37c890.
Claim: PM loop round 9
Session: session_01RWZbGvPFcRKvUqASZtunCU
Account: os-warren (the seat's linked user as get_me answers it; the card's assignee)
Branch: claude/issue-22163-doctor-node-env-source-posture
Worktree: objectstack-issue-22163
Domain: domain:cli
Seat: domain:cli#1
File surface, per the card body and triage 6052805752, read on origin/main35396b5e:
packages/cli/src/commands/doctor.ts: nodeEnvCheck() (:208) and its one call site in run() (:2091).
In the source posture, the row reads as development with no fix text.
In every other posture, the current ⚠ row and its fix text stay.
The posture comes from utils/artifact-precedence.ts's existing exports (resolveArtifactBootSource, cwdConfigJoinsBoot), not from a second copy of the precedence. That module's own header forbids a second copy.
The pins: packages/cli/src/commands/doctor-node-env-default.test.ts and doctor-env-provenance.test.ts. The production-default rows move only where the posture is a source checkout.
.changeset/*.md: @objectstack/clipatch.
⛔ doctorNodeEnv() and the .env* cascade doctor resolves are unchanged. So are the /discovery default and os serve / os start resolution. The card says only what doctor says changes. (Stop on breach and explain in the report.)
Container & model: S, mode:subagent, model: default (opus). dispatch-gates --tier over the path gives no path-derived mandate.
Clause-②: no
os-dev-report
{
"issue": 22163,
"status": "done",
"branch": "claude/issue-22163-doctor-node-env-source-posture",
"pr": "#22231",
"session": "session_01RWZbGvPFcRKvUqASZtunCU",
"premise_still_valid": true,
"summary": "Draft PR #22231 (head 12e2b1e). With NODE_ENV unset, os doctor's NODE_ENV row now reads ok: 'Not set — os dev runs this project as development; os start forces production', with no fix text. That happens in the source checkout os dev serves: a cwd objectstack.config.ts alone, or beside its own dist/objectstack.json. Everywhere else the #5673 warning and its fix are unchanged, byte-identical by cmp against the published 17.7.0 CLI. The posture is the new nodeEnvSourcePosture(cwd, env) in doctor.ts. It calls artifact-precedence.ts's resolveArtifactBootSource and then cwdConfigJoinsBoot, over the process environment plus the node_env=development .env* cascade, which is what os dev loads before it resolves. nodeEnvCheck(env, { sourcePosture }) defaults to false. doctorNodeEnv(), the cascade doctor reports, /discovery, serve/start/dev, spec, docs and docs/qa are all untouched. PM readings: H1 confirmed. On a published-17.7.0 blank scaffold, the compile os dev runs writes dist/objectstack.json, so the card's literal 'no dist' test would flip the project after its first os dev. H2 held except for configCompiledTo, which I deliberately do not mirror (see deviations). H5 confirmed: dist/index.d.ts re-exports only default as DoctorCommand, so Clause-② stays no.",
"tests": "All on HEAD 12e2b1e.\n(1) Build: turbo build --filter=@objectstack/cli... --concurrency=2 → 'Tasks: 59 successful, 59 total / Cached: 57 cached', exit 0.\n(2) The two pin files, vitest --project unit: 'Test Files 2 passed (2) / Tests 47 passed (47)'.\n(3) cli unit tier: 'Test Files 1 failed | 262 passed (263) / Tests 1 failed | 3886 passed (3887)'. The one failure is test/hook-timeout-override-refusal.test.ts 'CONTROL — a run naming no override passes and says nothing' with 'Test timed out in 5000ms'; it does not touch doctor, and it ran while the lock was held 10m47s beside the 64-gate battery. Rerun alone: 'Test Files 1 passed (1) / Tests 4 passed (4)'. The integration tier is declared to CI: no integration-tier file and no spawn entry is touched.\n(4) cli typecheck: CLI_TC_EXIT=0. tsc --listFiles counts both pin files in the program, and 'check:test-typecheck: OK'.\n(5) Ablation. The fix was committed first. node scripts/ablation-replace.mjs replaced the call site 'nodeEnvCheck(process.env, { sourcePosture: nodeEnvSourcePosture(cwd) })' with 'sourcePosture: false': anchor x1→x0, replacement x0→x1, blob e38feeb4a35e→f875089c53b6. No build was needed because the test imports ./doctor.js, which resolves to src. Predicted (a) and (b) red, the rest green; observed exactly that: '× (a) … expected \' ⚠ NODE_ENV Not set — th…\' to contain \'✓\'', '× (b) … to contain \'os dev\'', 'Tests 2 failed | 21 passed (23)'. Restore: 'blob after restore e38feeb4a35e == blob at HEAD, git diff HEAD empty'.\n(6) H1 transcripts (published 17.7.0 blank scaffold, NODE_ENV unset). Before the change, both before and after the compile: '✓ Environment files No .env* files here (node_env=production) — environment read from this process only — no environment input set' / '⚠ NODE_ENV Not set — this environment is being treated as production'. With this branch's CLI, both with and without dist: the same Environment files row / '✓ NODE_ENV Not set — os dev runs this project as development; os start forces production'. Exit 0, and no fix line under --verbose.\n(7) Artifact-only directory, doctor --verbose: the NODE_ENV row plus its 11 fix lines are cmp-identical between this branch and published 17.7.0.\n(8) Full pnpm lint (eslint . --no-inline-config): LINT_EXIT=0, no findings printed.",
"gates": "All on HEAD 12e2b1e. The order's 64 commands equal dispatch-gates --repo objectstack-ai/objectstack --commands re-derived over the actual 4 paths (diff empty), and all 64 exited 0. check:dual-build-cjs-loads and check:i18n-coverage first exited 3 (PREREQUISITE NOT MET: packages unbuilt in a fresh worktree). After a full turbo build (72 tasks, 71 cached) both exited 0: '✓ check:dual-build-cjs-loads — 106 published require entry point(s) across 66 package(s) load' and 'check-i18n-coverage: OK (13 config(s), 621 baselined untranslated string(s), none new)'. The reconciliation was 'dispatch-gates --ran: 64 derived famil(ies) accounted for — 64 run, 0 NOT-MEASURED' (exit 0). Added beyond the list: full pnpm lint exit 0, cli unit, and cli typecheck. CI on PR #22231 at report time: 11 completed, all success or skipped; 'Lint & Repo Gates', 'Test Core', 'Type Check', 'Check Changeset', 'Build Core' and Dogfood were in_progress or queued.",
"line_budget": "n/a. No skills/** file and no line-ratcheted governed ledger is touched. The diff is 4 files, +329/-12 (341 changed lines), against the 5000-line threshold.",
"files_changed": [
"packages/cli/src/commands/doctor.ts",
"packages/cli/src/commands/doctor-node-env-default.test.ts",
"packages/cli/src/commands/doctor-env-provenance.test.ts",
".changeset/22163-doctor-node-env-source-posture.md"
],
"deviations": [
"H2 mechanism, partly falsified. Mirrored 'exactly as os dev builds it', cwdConfigJoinsBoot includes dev.ts:366's configCompiledTo. That makes a config plus a local OS_ARTIFACT_PATH naming another artifact answer true (source), because os dev compiles into that path. That contradicts the triage direction ('everywhere else #5673's production default stands') and pin (c). I followed the ruling's intent: the posture passes no configCompiledTo and no homeDir. The env view does mirror os dev exactly: process env over the node_env=development .env* cascade, shell wins.",
"Drift guard in doctor-env-provenance.test.ts, an in-surface pin file. The new docblock names OS_ARTIFACT_URL and OS_ARTIFACT_PATH, and the guard demands every OS_* name in doctor.ts be declared in DOCTOR_ENV_INPUTS. Declaring them would add lines to the Environment files row (H4 forbids rewording it) and would attribute them against the production cascade, while the posture reads the development one. So I added a named POSTURE_ONLY allowance for exactly those two names, with the reason, plus an assertion that both names are still in doctor.ts. Every other name still has to be declared. The seat may prefer a different spelling.",
"The pin 'outside the source posture, the result deep-equals today's warning and its fix' is written as toEqual between explicit sourcePosture:false and the default call. The default call is the row the unchanged #5673 pins hold. I did not freeze the warning's copy as a literal, because os-dev.md says not to pin prose a consumer does not parse, and on a conflict os-dev.md governs. Byte-identity to today was proven once instead: cmp against published 17.7.0 --verbose output, and the doctor.ts diff removes no line of the literal.",
"Commit trailer. A harness reminder asked for a model-named Co-Authored-By. I used the order's and AGENTS.md's model-free pair, 'Co-Authored-By: Claude' plus Claude-Session. No model identifier is in the commit, PR, code or comments."
],
"mcp_calls": "0 — no MCP GitHub tool called. Reads went through gh api single-card and single-PR REST reads.",
"api_writes": "3 — each sent as one repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches) through the fleet-write relay as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls, giving draft #22231; relay run 37744980456; read-back reported 11709 bytes sent and 11709 stored, identical. (2) label-write --assign os-warren, POST /repos//issues/22231/assignees; relay run 37745061639; read-back matched; zero label writes. (3) This os-dev-report comment, POST /repos//issues/22163/comments, via post-stamped.mjs. Not REST: two git pushes of claude/issue-22163-doctor-node-env-source-posture (the empty-branch probe, then 12e2b1e).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: domain:cli seat · noted, not filed · H4. A fresh blank scaffold now prints '✓ Environment files No .env* files here (node_env=production) — …' directly above '✓ NODE_ENV Not set — os dev runs this project as development; os start forces production'. On the facts they do not contradict: the first row is the cascade doctor resolved, which os serve and os start load when NODE_ENV is unset; the second is what os dev does. But a newcomer still reads node_env=production in a dev project, a milder form of this card's confusion. Behind it: in a source checkout, doctor attributes OS_* inputs against the production cascade while os dev loads .env.development*. That is the boundary drawn by the PM ruling on #5387, which declined a --dev mode for doctor. Unchanged here. Dedupe words: doctor environment files node_env production; doctor dev cascade .env.development; doctor source posture provenance.",
"carrier: 承接者:无 · noted, not filed · packages/cli test/hook-timeout-override-refusal.test.ts 'CONTROL — a run naming no override passes and says nothing' timed out at 5000ms in a full unit-tier run under shared-box load. It passed 4/4 when rerun alone on the same HEAD. It is one observation, not a reproduction."
],
"cleanup": "Worktree /home/user/objectstack-issue-22163: node_modules removed, then git worktree remove exited 0 with no --force, after confirming git status was clean and HEAD equalled origin. No dev server and no background process remains. The scratch scaffold node_modules was removed."
}
ACCEPT — PR #22231 at 12e2b1ed, pending CI and the seat's self-test
domain:cli seat · session_01RWZbGvPFcRKvUqASZtunCU · read on GitHub 2026-10-08T07:46Z
Checked on GitHub and in the diff, not from the report:
Shape:
draft, base main;
line 1 is Fixes #22163, line 2 is Clause-②: no;
assignee os-warren;
4 files, +329 / −12.
The row.nodeEnvCheck(env, { sourcePosture }) returns ok, the card's message and no fix only when sourcePosture is true. sourcePosture defaults to false. The warning branch's literal is untouched in the diff, so outside the source posture the row is today's, byte for byte. HealthCheckResult.status is not widened.
The posture (nodeEnvSourcePosture) asks the precedence module rather than copying it: resolveArtifactBootSource, then cwdConfigJoinsBoot. The config is a file test, never a load.
The environment it reads mirrors os dev. dev.ts:330 loads dotenv-flow for development before resolveArtifactBootSource at :348. The posture overlays readDotenvFiles(cwd, 'development') with the shell winning (dotenvOverlay skips every name the env already has).
The dev's one departure from os dev, no configCompiledTo: it stands.
With it, a config plus a local OS_ARTIFACT_PATH would read as source, because os dev compiles into that path.
(a) runs beside a config that fails to load and asserts that the failure was printed, so the row is shown not to depend on the load.
The dev's ablation (posture forced off) turned exactly (a) and (b) red.
POSTURE_ONLY in the provenance drift guard is narrow: two names, a stated reason, and a reverse check that both still appear in doctor.ts. Every other OS_* name must still be declared.
Clause-②: no stands.nodeEnvCheck / nodeEnvSourcePosture are exports of the command module. The package entry re-exports only the command's default (the dev read the built dist/index.d.ts; the seat confirms no entry in packages/cli/package.jsonexports names commands/doctor).
Changeset:@objectstack/clipatch. Each sentence checks against the diff, including "the Environment files row still reports … node_env=production".
Owed before landing:
The seat's dispatch-gates --self-test at 12e2b1ed, for the new mkdtempSync site in doctor-node-env-default.test.ts. Running.
CI on 12e2b1ed.
Carried by the seat (the dev's H4 note): a freshly scaffolded project now prints ✓ Environment files … (node_env=production) directly above the new development row.
On the facts they do not contradict each other. The first row is the cascade os serve / os start load when NODE_ENV is unset.
Still, a newcomer reads production in a dev project.
Not carried: the one unit-tier timeout the dev saw (hook-timeout-override-refusal.test.ts, 5 s, under shared load) passed alone. It is one observation with no reproduction.
It is an ancestor of origin/main; the pre-merge head 12e2b1ed is not.
It entered the merge queue 2026-10-08T08:17:22Z and merged 2026-10-08T09:12:14Z on that first entry.
Fixes #22163 closed this card as completed.
Content on origin/main:
packages/cli/src/commands/doctor.ts carries nodeEnvSourcePosture (:316) and the source-posture row Not set — os dev runs this project as development; os start forces production (:252);
both pin files and .changeset/22163-doctor-node-env-source-posture.md (@objectstack/clipatch) are present.
Review of record: ACCEPT 6055243265 at 12e2b1ed, in one round. Clause-②: no; no contract review was owed. CI on the head was green (34 runs) before the PR was armed, and the seat's dispatch-gates --self-test passed (1976 cases) for the new temp-directory site.
Delivered: with NODE_ENV unset, os doctor beside a project's objectstack.config.ts reports ✓ that os dev runs it as development and os start as production. That holds for a config alone, or beside its own compiled dist/objectstack.json, which the first os dev writes. Everywhere else, including a config beside an artifact named by OS_ARTIFACT_URL or OS_ARTIFACT_PATH, the NODE_ENV 未设置时 /discovery 广播 environment=development,而 os start 默认 NODE_ENV=production、CLI doctor 也按 production 解析 #5673 warning and its fix print exactly as before.
Carried by the seat, filed separately: in the same freshly scaffolded project, the Environment files row directly above still reads node_env=production. That row is the cascade os serve and os start load, and whether doctor should follow the source posture there runs into the PM ruling on os doctor 不加载 .env*,读到的环境与 serve/dev/start 实际运行的不是同一份 —— 写在 .env 里的配置错误 doctor 一律看不见 #5387. The seat files that question as its own card.
Activity
objectstack-fleet commentedon Oct 8, 2026
Path: the road — start:
os doctorreads a dev project as dev | 缺项 | P3Triage: first grade,
enhancement·priority:p3·domain:cli·area:devpath·pm:queue(findingremoved). The maintainer's new direction against #5673's ruling, as dispatchedTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-08T05:12Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/cli/src/commands/doctor.ts(nodeEnvCheck(),doctorNodeEnv()) anddoctor-env-provenance.test.ts⇒domain:cli; rationale:packages/cliis that lane's. Read onmainec8f37c890.os doctorwarns "treated as production" beside a fresh dev project, whileos devruns it as development. The maintainer's word supersedes NODE_ENV 未设置时 /discovery 广播 environment=development,而 os start 默认 NODE_ENV=production、CLI doctor 也按 production 解析 #5673 for this case.objectstack.config.tswith no artifact, an unsetNODE_ENVreads as developmentClause-②: no. Patch changeset for@objectstack/cli.objectstack-fleet commentedon Oct 8, 2026
Claim: PM loop round 9
Session:
session_01RWZbGvPFcRKvUqASZtunCUAccount:
os-warren(the seat's linked user asget_meanswers it; the card's assignee)Branch:
claude/issue-22163-doctor-node-env-source-postureWorktree:
objectstack-issue-22163Domain:
domain:cliSeat:
domain:cli#1File surface, per the card body and triage
6052805752, read onorigin/main35396b5e:packages/cli/src/commands/doctor.ts:nodeEnvCheck()(:208) and its one call site inrun()(:2091).⚠row and its fix text stay.utils/artifact-precedence.ts's existing exports (resolveArtifactBootSource,cwdConfigJoinsBoot), not from a second copy of the precedence. That module's own header forbids a second copy.packages/cli/src/commands/doctor-node-env-default.test.tsanddoctor-env-provenance.test.ts. The production-default rows move only where the posture is a source checkout..changeset/*.md:@objectstack/clipatch.doctorNodeEnv()and the.env*cascade doctor resolves are unchanged. So are the/discoverydefault andos serve/os startresolution. The card says only what doctor says changes. (Stop on breach and explain in the report.)Container & model:
S,mode:subagent,model: default (opus).dispatch-gates --tierover the path gives no path-derived mandate.Clause-②: no
error, so doctor's exit is unchanged.Responsibility:
n/a — not a defect card(the maintainer's new direction against NODE_ENV 未设置时 /discovery 广播 environment=development,而 os start 默认 NODE_ENV=production、CLI doctor 也按 production 解析 #5673's ruling)Thread-read: 6052805752
Serial constraints cleared: read 2026-10-08T07:01Z:
filename): none touchescommands/doctor.tsor either doctor NODE_ENV pin.packages/cli/src/utils/format.tsand [maintainer] create-objectstack: the blank template'spnpm-workspace.yamlis 85 lines, 66 of them peer-dependency commentary — the third-largest file in a "clean slate" project; keep the file minimal and move the rationale out #22162 holdscommands/init.tsand the blank template, not these files.domain:engine's feat(objectql,plugin-auth): the Default Organization is load-bearing undersingle; an unstamped write is derived there and refused everywhere else (ADR-0131 D3/D9/D11) #15195 declaration (6053967589) namespackages/verifyandpackages/qa/dogfood, not these files.domain:cliseat ·session_01RWZbGvPFcRKvUqASZtunCU· 2026-10-08T07:01Zobjectstack-fleet commentedon Oct 8, 2026
os-dev-report
{
"issue": 22163,
"status": "done",
"branch": "claude/issue-22163-doctor-node-env-source-posture",
"pr": "#22231",
"session": "session_01RWZbGvPFcRKvUqASZtunCU",
"premise_still_valid": true,
"summary": "Draft PR #22231 (head 12e2b1e). With NODE_ENV unset, os doctor's NODE_ENV row now reads
ok: 'Not set — os dev runs this project as development; os start forces production', with no fix text. That happens in the source checkout os dev serves: a cwd objectstack.config.ts alone, or beside its own dist/objectstack.json. Everywhere else the #5673 warning and its fix are unchanged, byte-identical by cmp against the published 17.7.0 CLI. The posture is the new nodeEnvSourcePosture(cwd, env) in doctor.ts. It calls artifact-precedence.ts's resolveArtifactBootSource and then cwdConfigJoinsBoot, over the process environment plus the node_env=development .env* cascade, which is what os dev loads before it resolves. nodeEnvCheck(env, { sourcePosture }) defaults to false. doctorNodeEnv(), the cascade doctor reports, /discovery, serve/start/dev, spec, docs and docs/qa are all untouched. PM readings: H1 confirmed. On a published-17.7.0 blank scaffold, the compile os dev runs writes dist/objectstack.json, so the card's literal 'no dist' test would flip the project after its first os dev. H2 held except for configCompiledTo, which I deliberately do not mirror (see deviations). H5 confirmed: dist/index.d.ts re-exports only default as DoctorCommand, so Clause-② stays no.","tests": "All on HEAD 12e2b1e.\n(1) Build: turbo build --filter=@objectstack/cli... --concurrency=2 → 'Tasks: 59 successful, 59 total / Cached: 57 cached', exit 0.\n(2) The two pin files, vitest --project unit: 'Test Files 2 passed (2) / Tests 47 passed (47)'.\n(3) cli unit tier: 'Test Files 1 failed | 262 passed (263) / Tests 1 failed | 3886 passed (3887)'. The one failure is test/hook-timeout-override-refusal.test.ts 'CONTROL — a run naming no override passes and says nothing' with 'Test timed out in 5000ms'; it does not touch doctor, and it ran while the lock was held 10m47s beside the 64-gate battery. Rerun alone: 'Test Files 1 passed (1) / Tests 4 passed (4)'. The integration tier is declared to CI: no integration-tier file and no spawn entry is touched.\n(4) cli typecheck: CLI_TC_EXIT=0. tsc --listFiles counts both pin files in the program, and 'check:test-typecheck: OK'.\n(5) Ablation. The fix was committed first. node scripts/ablation-replace.mjs replaced the call site 'nodeEnvCheck(process.env, { sourcePosture: nodeEnvSourcePosture(cwd) })' with 'sourcePosture: false': anchor x1→x0, replacement x0→x1, blob e38feeb4a35e→f875089c53b6. No build was needed because the test imports ./doctor.js, which resolves to src. Predicted (a) and (b) red, the rest green; observed exactly that: '× (a) … expected \' ⚠ NODE_ENV Not set — th…\' to contain \'✓\'', '× (b) … to contain \'os dev\'', 'Tests 2 failed | 21 passed (23)'. Restore: 'blob after restore e38feeb4a35e == blob at HEAD, git diff HEAD empty'.\n(6) H1 transcripts (published 17.7.0 blank scaffold, NODE_ENV unset). Before the change, both before and after the compile: '✓ Environment files No .env* files here (node_env=production) — environment read from this process only — no environment input set' / '⚠ NODE_ENV Not set — this environment is being treated as production'. With this branch's CLI, both with and without dist: the same Environment files row / '✓ NODE_ENV Not set — os dev runs this project as development; os start forces production'. Exit 0, and no fix line under --verbose.\n(7) Artifact-only directory, doctor --verbose: the NODE_ENV row plus its 11 fix lines are cmp-identical between this branch and published 17.7.0.\n(8) Full pnpm lint (eslint . --no-inline-config): LINT_EXIT=0, no findings printed.",
"gates": "All on HEAD 12e2b1e. The order's 64 commands equal dispatch-gates --repo objectstack-ai/objectstack --commands re-derived over the actual 4 paths (diff empty), and all 64 exited 0. check:dual-build-cjs-loads and check:i18n-coverage first exited 3 (PREREQUISITE NOT MET: packages unbuilt in a fresh worktree). After a full turbo build (72 tasks, 71 cached) both exited 0: '✓ check:dual-build-cjs-loads — 106 published require entry point(s) across 66 package(s) load' and 'check-i18n-coverage: OK (13 config(s), 621 baselined untranslated string(s), none new)'. The reconciliation was 'dispatch-gates --ran: 64 derived famil(ies) accounted for — 64 run, 0 NOT-MEASURED' (exit 0). Added beyond the list: full pnpm lint exit 0, cli unit, and cli typecheck. CI on PR #22231 at report time: 11 completed, all success or skipped; 'Lint & Repo Gates', 'Test Core', 'Type Check', 'Check Changeset', 'Build Core' and Dogfood were in_progress or queued.",
"line_budget": "n/a. No skills/** file and no line-ratcheted governed ledger is touched. The diff is 4 files, +329/-12 (341 changed lines), against the 5000-line threshold.",
"files_changed": [
"packages/cli/src/commands/doctor.ts",
"packages/cli/src/commands/doctor-node-env-default.test.ts",
"packages/cli/src/commands/doctor-env-provenance.test.ts",
".changeset/22163-doctor-node-env-source-posture.md"
],
"deviations": [
"H2 mechanism, partly falsified. Mirrored 'exactly as os dev builds it', cwdConfigJoinsBoot includes dev.ts:366's configCompiledTo. That makes a config plus a local OS_ARTIFACT_PATH naming another artifact answer true (source), because os dev compiles into that path. That contradicts the triage direction ('everywhere else #5673's production default stands') and pin (c). I followed the ruling's intent: the posture passes no configCompiledTo and no homeDir. The env view does mirror os dev exactly: process env over the node_env=development .env* cascade, shell wins.",
"Drift guard in doctor-env-provenance.test.ts, an in-surface pin file. The new docblock names OS_ARTIFACT_URL and OS_ARTIFACT_PATH, and the guard demands every OS_* name in doctor.ts be declared in DOCTOR_ENV_INPUTS. Declaring them would add lines to the Environment files row (H4 forbids rewording it) and would attribute them against the production cascade, while the posture reads the development one. So I added a named POSTURE_ONLY allowance for exactly those two names, with the reason, plus an assertion that both names are still in doctor.ts. Every other name still has to be declared. The seat may prefer a different spelling.",
"The pin 'outside the source posture, the result deep-equals today's warning and its fix' is written as toEqual between explicit sourcePosture:false and the default call. The default call is the row the unchanged #5673 pins hold. I did not freeze the warning's copy as a literal, because os-dev.md says not to pin prose a consumer does not parse, and on a conflict os-dev.md governs. Byte-identity to today was proven once instead: cmp against published 17.7.0 --verbose output, and the doctor.ts diff removes no line of the literal.",
"Commit trailer. A harness reminder asked for a model-named Co-Authored-By. I used the order's and AGENTS.md's model-free pair, 'Co-Authored-By: Claude' plus Claude-Session. No model identifier is in the commit, PR, code or comments."
],
"mcp_calls": "0 — no MCP GitHub tool called. Reads went through gh api single-card and single-PR REST reads.",
"api_writes": "3 — each sent as one repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches) through the fleet-write relay as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls, giving draft #22231; relay run 37744980456; read-back reported 11709 bytes sent and 11709 stored, identical. (2) label-write --assign os-warren, POST /repos//issues/22231/assignees; relay run 37745061639; read-back matched; zero label writes. (3) This os-dev-report comment, POST /repos//issues/22163/comments, via post-stamped.mjs. Not REST: two git pushes of claude/issue-22163-doctor-node-env-source-posture (the empty-branch probe, then 12e2b1e).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: domain:cli seat · noted, not filed · H4. A fresh blank scaffold now prints '✓ Environment files No .env* files here (node_env=production) — …' directly above '✓ NODE_ENV Not set — os dev runs this project as development; os start forces production'. On the facts they do not contradict: the first row is the cascade doctor resolved, which os serve and os start load when NODE_ENV is unset; the second is what os dev does. But a newcomer still reads node_env=production in a dev project, a milder form of this card's confusion. Behind it: in a source checkout, doctor attributes OS_* inputs against the production cascade while os dev loads .env.development*. That is the boundary drawn by the PM ruling on #5387, which declined a --dev mode for doctor. Unchanged here. Dedupe words: doctor environment files node_env production; doctor dev cascade .env.development; doctor source posture provenance.",
"carrier: 承接者:无 · noted, not filed · packages/cli test/hook-timeout-override-refusal.test.ts 'CONTROL — a run naming no override passes and says nothing' timed out at 5000ms in a full unit-tier run under shared-box load. It passed 4/4 when rerun alone on the same HEAD. It is one observation, not a reproduction."
],
"cleanup": "Worktree /home/user/objectstack-issue-22163: node_modules removed, then git worktree remove exited 0 with no --force, after confirming git status was clean and HEAD equalled origin. No dev server and no background process remains. The scratch scaffold node_modules was removed."
}
Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
ACCEPT — PR #22231 at
12e2b1ed, pending CI and the seat's self-testdomain:cliseat ·session_01RWZbGvPFcRKvUqASZtunCU· read on GitHub 2026-10-08T07:46ZChecked on GitHub and in the diff, not from the report:
main;Fixes #22163, line 2 isClause-②: no;os-warren;nodeEnvCheck(env, { sourcePosture })returnsok, the card's message and nofixonly whensourcePostureis true.sourcePosturedefaults tofalse. Thewarningbranch's literal is untouched in the diff, so outside the source posture the row is today's, byte for byte.HealthCheckResult.statusis not widened.nodeEnvSourcePosture) asks the precedence module rather than copying it:resolveArtifactBootSource, thencwdConfigJoinsBoot. The config is a file test, never a load.os dev.dev.ts:330loadsdotenv-flowfordevelopmentbeforeresolveArtifactBootSourceat:348. The posture overlaysreadDotenvFiles(cwd, 'development')with the shell winning (dotenvOverlayskips every name the env already has).os dev, noconfigCompiledTo: it stands.OS_ARTIFACT_PATHwould read as source, becauseos devcompiles into that path.OS_ARTIFACT_PATHset" on the deployment side, and so does the seat's order table. Leaving it out keeps NODE_ENV 未设置时 /discovery 广播 environment=development,而 os start 默认 NODE_ENV=production、CLI doctor 也按 production 解析 #5673's row exactly where the card says it stays.doctorNodeEnv(), the cascade, theEnvironment filesrow,/discovery, andserve/start/devare untouched.process.envand thecwdthatrun()already holds (:2106at head).NODE_ENV;.env.development/.env.productionpair;POSTURE_ONLYin the provenance drift guard is narrow: two names, a stated reason, and a reverse check that both still appear indoctor.ts. Every otherOS_*name must still be declared.Clause-②: nostands.nodeEnvCheck/nodeEnvSourcePostureare exports of the command module. The package entry re-exports only the command's default (the dev read the builtdist/index.d.ts; the seat confirms no entry inpackages/cli/package.jsonexportsnamescommands/doctor).Changeset:
@objectstack/clipatch. Each sentence checks against the diff, including "theEnvironment filesrow still reports …node_env=production".Owed before landing:
dispatch-gates --self-testat12e2b1ed, for the newmkdtempSyncsite indoctor-node-env-default.test.ts. Running.12e2b1ed.Carried by the seat (the dev's H4 note): a freshly scaffolded project now prints
✓ Environment files … (node_env=production)directly above the new development row.os serve/os startload whenNODE_ENVis unset.productionin a dev project.os doctor不加载.env*,读到的环境与serve/dev/start实际运行的不是同一份 —— 写在.env里的配置错误 doctor 一律看不见 #5387, which declined a dev mode for doctor. That is a maintainer's call, not this PR's. The seat files it as its own card when this one lands.Not carried: the one unit-tier timeout the dev saw (
hook-timeout-override-refusal.test.ts, 5 s, under shared load) passed alone. It is one observation with no reproduction.objectstack-fleet commentedon Oct 8, 2026
Landed: PR #22231 →
b9e6e5c132, a single-parent queue squashdomain:cliseat ·session_01RWZbGvPFcRKvUqASZtunCU· 2026-10-08T09:12Zb9e6e5c132has one parent.origin/main; the pre-merge head12e2b1edis not.Fixes #22163closed this card as completed.origin/main:packages/cli/src/commands/doctor.tscarriesnodeEnvSourcePosture(:316) and the source-posture rowNot set — os dev runs this project as development; os start forces production(:252);.changeset/22163-doctor-node-env-source-posture.md(@objectstack/clipatch) are present.6055243265at12e2b1ed, in one round.Clause-②: no; no contract review was owed. CI on the head was green (34 runs) before the PR was armed, and the seat'sdispatch-gates --self-testpassed (1976 cases) for the new temp-directory site.NODE_ENVunset,os doctorbeside a project'sobjectstack.config.tsreports✓thatos devruns it as development andos startas production. That holds for a config alone, or beside its own compileddist/objectstack.json, which the firstos devwrites. Everywhere else, including a config beside an artifact named byOS_ARTIFACT_URLorOS_ARTIFACT_PATH, the NODE_ENV 未设置时 /discovery 广播 environment=development,而 os start 默认 NODE_ENV=production、CLI doctor 也按 production 解析 #5673 warning and its fix print exactly as before.Environment filesrow directly above still readsnode_env=production. That row is the cascadeos serveandos startload, and whether doctor should follow the source posture there runs into the PM ruling onos doctor不加载.env*,读到的环境与serve/dev/start实际运行的不是同一份 —— 写在.env里的配置错误 doctor 一律看不见 #5387. The seat files that question as its own card.fix(cli): os doctor reads an unset NODE_ENV as development in the sou…