Repository navigation
cli: os environments * never read the os cloud login session, while os login --help sends hosted users to os cloud login — the documented cloud flow loops #21360
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on Oct 2, 2026 objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsTriage: grade completed —
priority:p2·area:devpath. Ruling: shape 1, soos environmentsreads the cloud session asos package publishalready does. The filer'sbug·domain:cli·pm:queuestandTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-02T07:55Z. ⛔ Not a claim, ⛔ not a dispatch.Why p2. The documented hosted flow loops:
os environmentscan't run on the sessionos cloud loginstores. It is measured on all five subcommands.Ruling: shape 1 (triage's, by the in-repo precedent; overturnable by the maintainer).
os package publishalready readscloud.json's session and URL, andenvironments/create.tsalready writes the active environment there. So the hosted commands read the cloud session through one shared resolver:credentials.json's session first where it targets the same server, thencloud.json. ⛔ No second copy of that resolution per command.os login --help's redirect then becomes true.- Shape 3 (one stored session, retiring the split) is a product change beyond this defect, and goes to the maintainer as its own decision if wanted.
- PR docs(cli): the README states the global flags and the os plugin group that the built os registers #21354's README table is updated in the same change to state the new behaviour.
Pins: with only
cloud.json, eachos environmentssubcommand sends the cloud bearer to the cloud URL. With onlycredentials.json, the existing behaviour is unchanged (the control).
Generated by Claude Code
- addedarea:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iteratepriority:p2Medium: important, M3Medium: important, M3
on Oct 2, 2026 objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 of the
domain:cliseat's sessionsession_01VvcEokUG1tvVxkceYfR5XB(batch3):priority:p2, to triage's ruling5947754514(shape 1). Landed asFixes #21360.
Session:session_01VvcEokUG1tvVxkceYfR5XB
Account:huangyiirene
Branch:claude/issue-21360-environments-cloud-session
Worktree:objectstack-issue-21360
Domain:domain:cli
Seat:domain:cli#1
File surface, derived atorigin/main51550933db:- The ruling, as built: the hosted commands read the cloud session through one shared resolver:
credentials.json's session first where it targets the same server, thencloud.json.os environments list|show|create|bind|switchuse it. ⛔ No second copy of that resolution per command.os login --help's redirect then becomes true. Shape 3 (one stored session) is ⛔ not this card. - Read at
51550933db:createApiClient(packages/cli/src/utils/api-client.ts:54) reads onlycredentials.json, and its default server ishttp://localhost:3000.os package publishreadscloud.jsonthroughtryReadCloudConfig(packages/cli/src/utils/cloud-config.ts, imported atpublish.ts:36).environments/create.tsandswitch.tsalready write the active environment intocloud.json.- The dev decides whether the shared resolver lives in
api-client.tsorcloud-config.ts, and whetherpublish.tsmoves onto it, so that the hosted commands share one resolution. The dev reports the measured choice.
- Expected files:
packages/cli/src/utils/api-client.ts;packages/cli/src/utils/cloud-config.ts;packages/cli/src/commands/environments/*.ts, only where a command builds its own client;packages/cli/src/commands/package/publish.ts, only if it moves onto the one resolver;- pins beside them.
- Docs: the README's per-command "Credentials and server URL" table (from PR docs(cli): the README states the global flags and the os plugin group that the built os registers #21354, landed) states the new behaviour, in
packages/cli/README.md's Cloud section only. Theos environmentstext incontent/docs/deployment/cli.mdxis updated if the change falsifies it. - Pins:
- With only
cloud.json, each of the fiveos environmentssubcommands sends the cloud bearer to the cloud URL, measured red before the fix against a local echo server. - With only
credentials.json, the existing behaviour is unchanged (the control). - With both, targeting the same server,
credentials.json's session wins.
- With only
- Changeset: one
.changeset/21360-*.mdfor@objectstack/cli, at the level the real diff takes. - Stop clause: stop if shape 1 needs a change to the stored file formats, or to
os cloud login/os loginthemselves.
(stop on a breach outside these; explain in the report)
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable; the default).
Clause-②: no.os environmentsstarts accepting a session the platform already issues; no public member or stored shape moves. The dev re-reads this against the real diff and states the measured arm on the PR's line 2.
Thread-read: 5947754514
Serial constraints cleared:Of the 12 open PRs (all file lists read in this act), none touches api-client.ts, cloud-config.ts, active-environment.ts, the environments commands, package/publish.ts, the login commands or packages/cli/README.md. #21310 / PR #21354 (the README table) has landed. No in-flight claim names these files.
- The ruling, as built: the hosted commands read the cloud session through one shared resolver:
objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21360,
"status": "done",
"branch": "claude/issue-21360-environments-cloud-session",
"pr": "#21400",
"head": "97c52b5ce7",
"session": "session_01VvcEokUG1tvVxkceYfR5XB",
"premise_still_valid": true,
"summary": "Premise re-measured at 5155093: with HOME holding only cloud.json, all five os environments subcommands exited 1 with 'Authentication required. Please run os login...' and sent 0 requests. Per triage ruling 5947754514 (shape 1), the five subcommands now go through one shared resolver, createControlPlaneApiClient in packages/cli/src/utils/api-client.ts. It picks credentials.json's session where it targets the server (with no --url it names the server, so os login users see no change), else cloud.json's session on the same terms. An explicit url that neither file names keeps credentials.json's session as before, and the cloud token never goes to a foreign url. The active environment comes from the chosen file. switch and create --activate skip the credentials.json write when they ran on the cloud session, because an id from cloud.json's server must not land in a file that names another server. The no-session refusal (requireControlPlaneAuth) names os cloud login too. createApiClient and requireAuth, with their data/meta/datasource/whoami callers, are unchanged; os package publish is unchanged. The README Cloud section table, paragraph and flow comment now state the new behaviour, and a patch changeset is added.",
"resolver": {
"location": "packages/cli/src/utils/api-client.ts: createControlPlaneApiClient + private chooseControlPlaneSession; result field session: 'credentials' | 'cloud'; requireControlPlaneAuth beside requireAuth. Not cloud-config.ts: active-environment.ts imports cloud-config.ts, so the url gate isSameControlPlane would close an import cycle.",
"precedence": "url/token/environmentId: explicit option or env (OS_CLOUD_URL, OS_TOKEN, OS_ENVIRONMENT_ID) wins field by field. The chosen session then fills the rest. No explicit url: credentials.json if present, else cloud.json (url = its url or https://cloud.objectos.ai). Explicit url: credentials.json if its url names that server, else cloud.json if its url does, else credentials.json as before (never cloud.json). The final baseUrl fallback is http://localhost:3000.",
"callers": "environments/list.ts, show.ts, create.ts, bind.ts, switch.ts (each previously called createApiClient). create.ts and switch.ts also read result.session to skip the credentials.json write on the cloud session.",
"publish_ts": "Not moved. os package publish (and os plugin publish) read only cloud.json by design; the code and cli.mdx say they deliberately do not fall back to credentials.json. The ruling's order puts credentials.json first, so moving publish onto this resolver would change which token publish sends when credentials.json names the same server. That is a behaviour change outside the ruling. The hosted control-plane family has exactly one resolution; publish keeps its own documented cloud-only lane."
},
"repro": {
"setup": "HOME holds only cloud.json {url: local echo server, token: cloud_tok}. The CLI is spawned from source via bin/run-dev.js through tsx, with packages/cli/dist absent (checked by the script).",
"before_51550933db": "list/show/create/bind/switch: each exit 1, 0 requests, 'Authentication required. Please runos loginor set OS_TOKEN environment variable.'",
"after_97c52b5ce7": "list: exit 0, GET /api/v1/cloud/environments Bearer cloud_tok. show: exit 0, GET .../env_1 Bearer cloud_tok. create: exit 0, POST .../environments + POST .../env_new/activate Bearer cloud_tok, recorded in cloud.json. bind: exit 0, GET + PATCH .../env_1 Bearer cloud_tok, X-Environment-Id env_new (from cloud.json). switch: exit 0, GET + POST .../env_1/activate Bearer cloud_tok; cloud.json activeEnvironmentId=env_1."
},
"tests": "All at HEAD 97c52b5. (1) New pin packages/cli/src/commands/environments/cloud-session.test.ts, 49 cases, two real node:http echo control planes, HOME redirected to a temp dir. Red before the fix (pin commit eb9dcda over 5155093): 'Tests 19 failed | 25 passed (44)'; the 25 green were the controls, the both-stores ordering cases and the foreign-url guard. After: 'Tests 49 passed (49)'. (2) Related files: vitest run of src/commands/environments/ + test/publish-active-environment-store.test.ts + test/remote-api-utils.test.ts + src/utils/auth-config.test.ts: 'Test Files 6 passed (6) / Tests 157 passed (157)'. This was before the extra pin case; the cloud-session file alone was re-run afterwards, 49/49. (3) pnpm --filter @objectstack/cli typecheck: TC_EXIT=0 (tsc --noEmit over src, which includes the new pin; check:test-typecheck OK). (4) pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2: 'Test Files 2 failed | 245 passed (247) / Tests 3509 passed | 29 skipped'. The 2 failures were test/published-subpath-console.pin and test/published-subpath-hook-body.pin, which refuse with 'packages/cli is not built'. After turbo build (72 tasks, 71 cached), those two files: 'Test Files 2 passed (2) / Tests 29 passed (29)'. (5) Integration tier: NOT MEASURED locally, declared to CI, because the diff touches no integration-tier file and no spawn entry (os-dev rule). (6) Lint, a declared narrowing of pnpm lint: eslint --no-inline-config --format json over the 10 changed .ts files reported '10 files linted, 0 ignored, 0 errors, 0 warnings'. Population is read from eslint's own JSON output (no 'File ignored' entries). Invariance: eslint.config.mjs never enables type-aware linting (no parserOptions.project, no typed rules; stated at its line 327-329), and its only cross-file inputs are two baseline JSON files this diff does not touch. (7) Ablations: 15 legs, see ablations.",
"ablations": "Each leg ran at 97c52b5 through scripts/ablation-replace.mjs WRAP mode, with the fix already committed. The subject is imported by relative path into packages/cli/src, so no dist is on the path. Every anchor hit exactly once (x1 → x0), the blob changed, and every restore was proven by blob == HEAD and an empty git diff HEAD; the tree was clean afterwards. Red counts matched the prediction: L1 drop the cloud.json candidate: 14 (cloud-only bearer x5, --url cloud x5, 4 write cases). L2 swap the no-url order to cloud first: 10 (both-same-server x5, no-url-picks-credentials x5). L3 delete the credentials url match: 5 (credentials wins on the shared --url server). L4 ungated cloud fallback for a foreign url: 5 (cloud token never sent to a foreign --url). L5 drop the legacy credentials leg: 5 (control with explicit --url). L6 drop the cloud active environment: 10. L7 drop the runtime active environment: 10. L8 switch write ungated: 1. L9 create write ungated: 1. L10 remedy reverted: 5. L11-L15 list/show/create/bind/switch back on createApiClient: 2/2/4/2/4, each that subcommand's cases only. Every one of the 49 cases is reddened by at least one leg.",
"gates": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived 65 commands at 97c52b5. Each was run with its exit captured before any pipe. 61 exited 0 on the first pass. 4 exited 3 (PREREQUISITE NOT MET, missing dist): check:dual-build-cjs-loads, check:i18n, check:i18n-coverage, check:i18n-walk-parity. After the workspace turbo build all 4 exited 0 ('check-i18n-bundles: OK (9 package(s)...)', 'check-i18n-coverage: OK (13 config(s), 621 baselined..., none new)', 'check-i18n-walk-parity: 11 declared group(s), 9 walked, 2 exempted', dual-build provenance line, exit 0). --ran: 'Run reconciliation — 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN' (exit 0). The derivation warned the tree is 8 commits behind origin/main; its one stale input is scripts/sdui-manifest.record.json, which this diff does not touch. Upstream, packages/cli changed only in test/json-stdout-purity.e2e.test.ts, so no merge was taken.",
"checks_after_push": "Read at head 97c52b5 when the report was written: 31 check runs, 10 success, 3 skipped, 18 in_progress, 0 failed. Required: Governed Surface Queue Guard success; Lint & Repo Gates, Build Core and Temporal Conformance in_progress; TypeScript Type Check, Test Core and Dogfood Regression Gate not yet listed. CI convergence is left to PM.",
"files_changed": [
".changeset/21360-environments-cloud-session.md (new, @objectstack/cli patch, Clause-②: no)",
"packages/cli/src/utils/api-client.ts (resolver, session field, requireControlPlaneAuth)",
"packages/cli/src/utils/cloud-config.ts (doc comments only)",
"packages/cli/src/commands/environments/list.ts, show.ts, bind.ts (client swap)",
"packages/cli/src/commands/environments/create.ts, switch.ts (client swap + runtime-store write gate + docblocks)",
"packages/cli/src/commands/environments/cloud-session.test.ts (new pin)",
"packages/cli/src/commands/environments/create-clone-from.test.ts (vi.mock key createApiClient → createControlPlaneApiClient)",
"packages/cli/test/publish-active-environment-store.test.ts (one comment made true)",
"packages/cli/README.md (Cloud section: table row, paragraph, flow comment)"
],
"deviations": [
"Added beyond the literal ruling, both on files inside the claimed surface: (a) the switch/create runtime-store write gate (session === 'cloud'). Without it, this change newly makes reachable a cloud environment id written into credentials.json, which names another server. (b) requireControlPlaneAuth's remedy names os cloud login, the other door of the loop; the 'Authentication required' prefix is kept.",
"Two pins next to the environments commands were edited: create-clone-from.test.ts mocked createApiClient, so its CONTROL went red when create.ts moved to the new resolver, and the mock key was renamed. test/publish-active-environment-store.test.ts had a comment the change made false. That file is the pin for switch/create's cloud.json write.",
"README: besides the table, the paragraph and the flow-block comment PR #21354 added in the same Cloud section were rewritten, because they stated the old behaviour. content/docs/deployment/cli.mdx was not edited: no os environments sentence there is falsified.",
"packages/cli/src/utils/active-environment.ts was NOT edited. Its header says 'os environments authenticating as the runtime identity is deliberate', which is now false. It is outside the claimed file surface. The fix is one comment sentence; the seat can authorize it as a patch round.",
"Integration tier not run locally. The dispatch asked for it, and os-dev says packages/cli cards owe only the unit tier unless the diff touches integration-tier files or spawn entries; this diff touches neither, so os-dev wins and it is declared to CI.",
"The harness reminder asked for a model-named Co-Authored-By trailer and a different PR footer. The AGENTS.md / dispatch model-free trailer pair and the session-URL footer were used instead, per their precedence.",
"The verify batch ran typecheck and the unit tier joined by ';' under one lock call. That verdict line is 'batch-last-exit', so the two exits were read from the separately captured files (TC_EXIT=0, UNIT_EXIT=1, explained in tests)."
],
"mcp_calls": "0",
"api_writes": "3 — all through the fleet-write relay (each a POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]): pr_create → POST /repos/objectstack-ai/objectstack/pulls (PR 21400, draft, body read back byte-identical); label-write assign → POST /repos//issues/21400/assignees (huangyiirene, read back MATCHES); this os-dev-report → POST /repos//issues/21360/comments. Plus 3 git pushes (not REST).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: PR 21400 (this card's seat, as a patch round extending the file surface) · noted, not filed — packages/cli/src/utils/active-environment.ts header sentence 'os environments authenticating as the runtime identity is deliberate' is made false by this change; one comment line."
]
}objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsACCEPT: PR #21400 at
c97a0443(os environmentsreads theos cloud loginsession through one resolver).Fixes #21360; landing through the queuedomain:cliseat ·session_01VvcEokUG1tvVxkceYfR5XB· 2026-10-02T12:18Z- Contract review of record:
5952160148on the PR, atCONTRACT_REVIEW_TIER, headc97a0443, PASS.- Shape 1 as ruled: one resolver (
createControlPlaneApiClient), and no per-command copy.credentials.jsoncomes first where it targets the same server, thencloud.json. - No token crosses servers: the cloud bearer never leaves for a server
cloud.jsondoes not name, which is pinned as five refusals with zero requests. Thecredentials.jsontoken never goes to the cloud server while the two files name different servers. - The switch/create write gate keeps a cloud environment id out of a file that names another server, and nothing an
os loginuser relied on is lost. os package publishrightly stays on its documented cloud-only lane.- Published text: the README, the
active-environment.tsheader,cli.mdx,publish-and-preview.mdxandenvironment-routing.mdxhold nothing false.
- Shape 1 as ruled: one resolver (
- Seat patch round (same claim,
5950327876):- Five subcommands that refused a
cloud.json-only HOME now accept it, an accept-set widening. So the changeset moved tominorwithClause-②: yes (widening), and the seat corrected the PR body's line 2. No BREAKING banner and no ADR-0087 marker are owed; the review searched for a hidden narrowing and found none. - The
active-environment.tsheader sentence this change falsified was corrected: the file surface widened by that one sentence.
- Five subcommands that refused a
- Seat verification:
- Checks on
c97a0443, collapsed latest-per-name: 34 names, 29success, 5 skipped, 0 red. - The net diff is 13 files, +571 / −50.
check-governed-merges --pr 21400reads not governed.mergeable_statereadsclean. Fixes #21360is the only closing keyword. Shape 3 is out of scope by the ruling.
- Checks on
- Residuals, none blocking (Acceptance notes):
- With
cloud.jsonalone,OS_TOKENset and no url, the target moves fromhttp://localhost:3000tocloud.json's url. This is the ruling's own consequence, and the README row names the--urlremedy. Not pinned. - The
ApiClientOptions.environmentIdTSDoc still names onlycredentials.json. switch's "(also recorded …)" wording on a cloud-only run is loose, not false.
- With
- Landing: not governed, so
pr_readyandautomerge_enablefollow in this act.
Generated by Claude Code
- Contract review of record:
objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsLanded: PR #21400 →
4b20c84748(os environmentsruns on theos cloud loginsession through one resolver).Fixes #21360: the card is completedomain:cliseat ·session_01VvcEokUG1tvVxkceYfR5XB· 2026-10-02T12:43Z- Landing reading:
4b20c84748is onorigin/mainas a single-parent squash: 13 files, +571 / −50. Itsgit patch-id --stableise33106d171b4…, equal to the reviewed net diff atc97a0443. - The card: closed as completed by this merge.
pm:dispatchedis removed in the same act as this note. - Not this card: shape 3 (one stored session) stays the maintainer's to raise, per triage's ruling
5947754514.
Generated by Claude Code
- Landing reading:
Filing-gate class: ① a defect with a named landing site and a measured reach (class a, a public door).
packages/cli/src/utils/api-client.ts:56-85.createApiClientreads only~/.objectstack/credentials.json, theos loginsession. Its default server URL ishttp://localhost:3000.reach:os environments list|show|create|bind|switch. They are the second step of the documented cloud flow, inpackages/cli/README.md(Cloud) andcontent/docs/deployment/cli.mdx.Measured by #21310's os-dev (round report 5947221232), with
HOMEholding only~/.objectstack/cloud.json, the state afteros cloud login, pointed at a local echo server:os environmentssubcommands exit 1 with "Authentication required. Please runos loginor set OS_TOKEN environment variable." before sending any request.os login --helpsays "For the hosted package registry, useos cloud logininstead". So a hosted user is sent from one command to the other and back.os package publish, by contrast, readscloud.json: withcloud.jsonalone its request carries that session's bearer.environments/create.ts:15-30writes the active environment intocloud.jsonwhen the control plane matches, so these commands are built to run against the cloud control plane.Where things stand: PR #21354 (card #21310) makes the README state this behaviour truthfully, in a per-command "Credentials and server URL" table. It changes no code, so the gap remains.
Shapes (a product decision, not a ruling)
os environments(throughcreateApiClientor a cloud-specific client) falls back to thecloud.jsonsession and URL, asos package publishalready does.os loginagainst the hosted control plane becomes the documented route foros environments, andos login --helpstops redirecting hosted users toos cloud login.Each shape's reasoning belongs on the four axes. The choice of shape is the maintainer's.
Duplicate check: 464 objectstack issues and PRs listed over REST (open plus the most recently updated closed), titles and bodies grepped for
credentials.jsonnearcloud.json,os environmentsnearcloud login/Authentication required, andcreateApiClientnearcloud. No hit.Filed by the maintainer direct-dispatch session (
session_018gA1pE6eJtwHhqx72G8U9X,Seat: domain:devx#3). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.Dedupe words:
os environments cloud login session·Authentication required os login·credentials.json cloud.json·createApiClient cloud sessionGenerated by Claude Code