Skip to content

fix(cli): drop the deleted apps/cloud path from the publish help example - #15390

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-14806-publish-help-example-app-path
Sep 4, 2026
Merged

fix(cli): drop the deleted apps/cloud path from the publish help example#15390
os-litant merged 1 commit into
mainfrom
claude/issue-14806-publish-help-example-app-path

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes #14806

The last remaining site on that card, and the only one its earlier PR deliberately held back.

What was wrong

packages/cli/src/commands/package/publish.ts ended its static override examples block with:

$ OS_CLOUD_URL=http://localhost:4000 os package publish    # local dev (apps/cloud)

apps/cloud was deleted from this repository; the reference cloud host now lives in objectstack-ai/cloud. The parenthetical told a reader to run something against a directory absent from the tree they cloned.

Premise re-verified at the base commit, carrying the card's own positive control — the claim is a negative, so a non-empty result is what makes the absence a reading rather than a broken query:

$ git ls-tree origin/main -- apps/
040000 tree 9339b4c890d633fa853fc4814a3ff506bad63c78	apps/docs      # exactly one entry

Why this one was held back, and why it is a different review surface

The six sites that landed earlier were source comments — prose no one outside this repo reads. This is user-facing CLI output: oclif prints examples verbatim under EXAMPLES, so it reaches every user who runs --help. Two concrete consequences, both handled here rather than assumed away:

  1. It ships. @objectstack/cli is a published package, so this carries a changeset. The earlier comment-only PR correctly carried none; that reasoning does not transfer.
  2. A test pins it. Establishing that before editing was the first job, and the answer is not "no pin" — see below.

The rendered before/after — the evidence that matters for a help string

Rendered from the published entry point (packages/cli/bin/run.js, which loads dist/), the whole 117-line help output differs by exactly one line:

   $ os package publish dist/objectstack.json --visibility org --note "first cut"
 
-  $ OS_CLOUD_URL=http://localhost:4000 os package publish    # local dev (apps/cloud)
+  $ OS_CLOUD_URL=http://localhost:4000 os package publish    # local dev

The source render (bin/run-dev.js, through src/) is byte-identical to the dist/ render, so what a user gets from the published binary is what the diff shows.

Why the parenthetical is dropped rather than marked out-of-repo

The house style the six landed sites used — naming the referent and marking it as living in the cloud repo — is right for a source comment, where a contributor may genuinely want to open that file. It is wrong here. A --help reader is a user of the CLI, not a contributor to this monorepo; telling them which repository an internal file lives in is noise in user-facing output.

What the example teaches is that OS_CLOUD_URL overrides the control-plane URL — and the same output documents that a few lines above, under the -s, --server flag with [default: https://cloud.objectos.ai, env: OS_CLOUD_URL]. Which directory happens to serve localhost:4000 was never part of it.

Two things already on the tree spell it that way, and both were found before the choice was made:

  • content/docs/deployment/cli.mdx:1614 publishes this same example as # against a local control plane — no directory. The docs already carried the directory-free spelling; the help string was the outlier.
  • The sibling packages/cli/src/commands/plugin/publish.ts:51 gives the identical env-override example with no trailing comment at all.

Neither is byte-parity-gated against the help string, so these are precedent rather than obligation.

The pin: it exists, and it covers this exact line

packages/cli/src/commands/environments/environments.test.ts — the #10967 pin: examples resolve to a real command id block — walks every command source under packages/cli/src/commands/ by TypeScript AST and asserts each examples entry resolves to a registered command id. It pins the invocation shape, not the bytes, so the trailing prose is free to change; the pin does not move with the string, and its own hand-written control literal never carried the parenthetical.

That was reasoned from the source, then measured, because a pin assumed to cover a line is worth nothing. On the committed implementation the example was mutated to name an unregistered id and the pin was re-run:

on-disk proof: good_text=0  bad_text=1        (the mutation reached the file)
hash now: 06c6aabb...  (differs from HEAD blob 172145c1...)

× package/publish.ts: every examples entry names a registered command
  AssertionError: package/publish.ts has an examples entry that does not resolve
  to a command this CLI registers ...
Test Files  1 failed (1)      Tests  1 failed | 73 passed (74)

Restored with git checkout HEAD -- ABSOLUTE_PATH (never the bare form, which restores from the index — i.e. the mutation), verified byte-identical to the HEAD blob with an empty git diff HEAD, and re-run green: 74 passed (74), with the verbose reporter naming package/publish.ts explicitly. No rebuild leg is involved: the pin reads src/ text via the AST and walks src/commands/ for ids, so it never resolves this package through exports/dist.

⚠️ One claim I made in the claim comment is falsified, and the correction matters. I wrote that scripts/check-cli-command-ids.mjs also reads this text. It does not. Its own verdict line says 341 command-id literal(s) across 118 file(s) **outside packages/cli**, and its header states the design: "every oclif package is excluded from its own scan -- so the two never touch the same line." That gate is green here by exclusion, not on merit; the #10967 vitest pin is the half that actually covers this file.

Scope

One line of one file, plus the changeset. Nothing else was touched:

  • serve.ts (the keep the two in sync cross-repo obligation, now at :144) is byte-unchanged, as ruled — it is carded separately.
  • ⛔ The two sites on the sibling follow-up card are not touched.
  • ⛔ No apps/cloud directory was added to satisfy the example.
  • ⛔ The paths apps/cloud / apps/objectos were grepped; the bare tokens objectos and cloud never were. objectos.ai, cloud.objectos.ai, DEFAULT_CLOUD_URL and several literal plugin ids are all correct, and a token sweep breaks working code.

After-grep of the path across the repo: packages/cli/src/commands/package/publish.ts no longer appears at all. Every remaining row is accounted for — the two serve.ts rows above, the sibling card's objectql/src/plugin.ts:142, ROADMAP.md and docs/adr/** (historical records, never rewritten), observability/README.md:39 (already says "see that repo's exporter"), spec/prompts/architecture.md:26 (a product-strategy inventory), .gitignore:119 (dead config, recorded and deliberately uncarded), and eight _console/apps/cloud_control / apps/cloud-control rows that are a different path entirely.

Verification

Everything below ran at the final commit 568b5341 on a clean tree (git status --porcelain empty, local and remote at the same sha), so the green union describes the tree that is pushed.

Gate union — derived, never hand-written. node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, exit 0, 38 commands. Its provenance line confirms the answer is about this repo at this commit, and it took the change set from git itself (2 paths vs the merge base, three-dot).

All 38 ran with the exit code captured before any pipe (cmd > log 2>&1; ec=$?), never cmd | tail then $?. Result: 38/38 EXIT=0.

Two of them first came back EXIT=3, read as NOT MEASURED and NOT as a pass, exactly as each says of itself:

  • pnpm check:dual-build-cjs-loads — "PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/ ... Run pnpm build first. ⛔ This is NOT a pass: nothing was measured." (11 packages listed).
  • pnpm check:i18n-coverage — "Nothing was compared: 12 config(s) did lint, but a partial round cannot judge the ratchet."

Both were cleared by a full pnpm build at the same commit through the shared lock (VERDICT command-exit 0, 72/72 turbo tasks, 56 cached) and re-run: both EXIT=0.

Six gates run BEYOND the derived union, because the derivation is a clue and not a specification:

  • pnpm check:cli-command-ids — a real CI gate (.github/workflows/lint.yml:2339) that the derivation did not name, added because it looked like a pin on this text. EXIT=0 — and its verdict is what falsified that guess: 341 command-id literal(s) across 118 file(s) outside packages/cli. It excludes every oclif package from its own scan by design, so it is green here by exclusion, not on merit.
  • The five roster gates the derivation itself flaggedcheck-changeset-fixed, check:authz-resolver, check:error-code-casing, check:filter-alias-parity, check:swallow-census-controls — whose rosters live under .changeset or packages, directories my paths are in, where the tool states "the silence is not evidence in EITHER direction". All five EXIT=0.

Affected package. pnpm --filter @objectstack/cli typecheck EXIT=0 (tsc --noEmit plus check:test-typecheck: OK — @objectstack/cli's test layer compiles, so the test layer is genuinely covered rather than excluded). Full suite pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 EXIT=0 — 245 test files passed, 2854 passed | 6 expected fail (2860), 1090s.

Reverse verification of the pin — mutation and restore both proved on disk, quoted in full above. Predicted direction was RED and RED is what happened, naming this file. The restore leg was given the same treatment as the mutation leg: git checkout HEAD -- ABSOLUTE_PATH, an empty git diff HEAD, and a git hash-object comparison against the HEAD blob with an empty hash treated as failure rather than as "nothing to compare".

ESLint — a declared, proven narrowing rather than a repo-wide run. Linted the changed paths only, with the three things that make a narrowing a measurement instead of a gap: (1) the receiving population comes from eslint's own config, not my guess — it reports the changeset .md as "File ignored because no matching configuration was supplied", so publish.ts is the only file of the two in scope; (2) the file count is read from --format json — 2 entries; (3) the config is invariant for untouched files: this repo runs one eslint.config.mjs which never enables type-aware linting for any file (no parserOptions.project, no typed rules) — stated in that config and backed there by its own planted positive control — so a one-line string change inside one file cannot move any untouched file's verdict. Result: publish.ts 0 errors, 0 warnings, EXIT=0. The repo-wide pnpm lint is CI's run, not one I owe.

⚠️ Shared-box note. The last verify hold ran 18m43s with a sibling agent queued behind it, which is longer than a holder should sit on that lock; flagging it rather than leaving it to be found. Every wall-clock number here is a shared-box reading, as the lock's own VERDICT line states.

🤖 Generated with Claude Code

https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N


Generated by Claude Code

`os package publish --help` printed its local-dev example as:

    $ OS_CLOUD_URL=http://localhost:4000 os package publish    # local dev (apps/cloud)

`apps/cloud` was deleted from this repository -- the reference cloud host
now lives in `objectstack-ai/cloud` -- so the parenthetical told a user to
run something against a directory absent from the tree they cloned. Unlike
the source comments swept alongside it, this string is user-facing output:
`static override examples` is printed verbatim by oclif under EXAMPLES.

The parenthetical is dropped rather than re-pointed at the other repo. The
example teaches that `OS_CLOUD_URL` overrides the control-plane URL, which
the `--server` flag documents a few lines above in the same output; which
directory happens to serve `localhost:4000` was never part of that, and a
`--help` reader is not looking for a file in a monorepo. Two things on the
tree already spell it that way: `content/docs/deployment/cli.mdx` publishes
this same example with no directory, and the sibling `os plugin publish`
gives the identical env-override example with no trailing comment at all.

No behaviour moves: no flag, argument, default or exit code changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tooling labels Sep 4, 2026
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 1 documentable anchor(s).

10 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/declarative-endpoints.mdx (via os package publish (command, read off packages/cli/src/commands/package/publish.ts))
  • content/docs/concepts/metadata-lifecycle.mdx (via os package publish (command, read off packages/cli/src/commands/package/publish.ts))
  • content/docs/deployment/cli.mdx (via os package publish (command, read off packages/cli/src/commands/package/publish.ts))
  • content/docs/deployment/index.mdx (via os package publish (command, read off packages/cli/src/commands/package/publish.ts))
  • content/docs/deployment/publish-and-preview.mdx (via os package publish (command, read off packages/cli/src/commands/package/publish.ts))
  • content/docs/protocol/kernel/index.mdx (via os package publish (command, read off packages/cli/src/commands/package/publish.ts))
  • content/docs/protocol/kernel/lifecycle.mdx (via os package publish (command, read off packages/cli/src/commands/package/publish.ts))
  • content/docs/protocol/kernel/metadata-service.mdx (via os package publish (command, read off packages/cli/src/commands/package/publish.ts))
  • content/docs/protocol/objectql/index.mdx (via os package publish (command, read off packages/cli/src/commands/package/publish.ts))
  • content/docs/protocol/objectql/schema.mdx (via os package publish (command, read off packages/cli/src/commands/package/publish.ts))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v9.mdx (via os package publish (command, read off packages/cli/src/commands/package/publish.ts))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 22 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 5b2ad1b41af24a9dd4bf159802dcee3d527b7db7packageMentionDocs.

Which tree this was computed on

This run read content/docs from 3895ec18f04d17f5e87451ce00e78e42f97a9e0d — the merge of head 568b5341b5f3151e4275df89d7d0bbc0e2e19177 into base 5b2ad1b41af24a9dd4bf159802dcee3d527b7db7, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3895ec18f04d17f5e87451ce00e78e42f97a9e0d && git checkout 3895ec18f04d17f5e87451ce00e78e42f97a9e0d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5b2ad1b41af24a9dd4bf159802dcee3d527b7db7 568b5341b5f3151e4275df89d7d0bbc0e2e19177 && git checkout -B drift-repro 5b2ad1b41af24a9dd4bf159802dcee3d527b7db7 && git merge --no-ff 568b5341b5f3151e4275df89d7d0bbc0e2e19177

node scripts/docs-audit/affected-docs.mjs --json 5b2ad1b41af24a9dd4bf159802dcee3d527b7db7

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 5b2ad1b41af24a9dd4bf159802dcee3d527b7db7 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

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

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

2 participants