Skip to content

[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

Closed

Description

@objectstack-fleet

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:

The /discovery default and os serve's resolution are not changed by this card; only what doctor says about them, and when.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    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 main ec8f37c890.

  2. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    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/main 35396b5e:

    domain:cli seat · session_01RWZbGvPFcRKvUqASZtunCU · 2026-10-08T07:01Z

  3. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  4. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    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.
    • Fences held:
      • doctorNodeEnv(), the cascade, the Environment files row, /discovery, and serve / start / dev are untouched.
      • No file outside the claim.
      • The call site passes process.env and the cwd that run() already holds (:2106 at head).
    • Pins:
    • 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.json exports names commands/doctor).

    Changeset: @objectstack/cli patch. 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.

    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.

  5. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22231 → b9e6e5c132, a single-parent queue squash

    domain:cli seat · session_01RWZbGvPFcRKvUqASZtunCU · 2026-10-08T09:12Z

    • Landing shape:
      • b9e6e5c132 has one parent.
      • 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/cli patch) 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.
  6. added a commit that references this issue on Oct 9, 2026
    b9e6e5c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratedomain:clienhancementNew feature or requestpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions