Skip to content

[finding] packages/cli: four more documented os … invocations that do not resolve — two stale, two deliberate, and no gate over the class #11465

Description

@claude

Found while implementing #11313, whose own "worth checking in the same pass" paragraph asked for this sweep and whose dispatch fenced the result to a finding rather than edits. Recording the measurement so the gate question can be decided on numbers instead of on the impression that this class is everywhere. No assignee.

What was measured

Every `os …` span (backticked, plus single/double-quoted spans starting os or $ os ) in the 107 non-test TypeScript sources under packages/cli/src/**, checked against the ids the built CLI actually registers. The id set is not transcribed: it is read from the built oclif Config (packages/cli/bin/run.js's package, after building the package and its dependency closure), whose canonical ids use : and are converted to the user-typed form with oclif.topicSeparator.

scanned files: 107 | invocations found: 536 | unresolved: 6

Two of the six were in #11313's own declared file surface and are fixed by its PR (utils/auth-config.ts:36 and :60). The other four are listed below, and they do not all want the same treatment — which is the useful part of this finding.

The four remaining, triaged

Genuinely stale — a real, mechanical spelling defect:

  1. src/utils/api-client.ts:20`~/.objectstack/credentials.json` (written by `os projects switch`) . projects became environments in v5.0 with no aliases (ADR-0006); the registered id is environments switch, and the enumeration shows no projects topic and no id containing project. This is the exact twin of the line fixed in auth-config.ts under packages/cli: auth-config.ts tells a stuck user to run os auth login — that command does not resolve #11313 — same defect, one file over, left alone only because it is outside that card's surface.
  2. src/commands/environments/create.ts:39 — "Starter content is installed from the App Marketplace instead (os packages install, …)". Registered ids under that topic are package install and package publish, singular. A plural typo, never a rename.

True negatives for a naive gate — the reason this is not just "N spelling swaps":

  1. src/commands/package/publish.ts:16 — "(The legacy direct-to-environment os publish / os rollback commands, which wrote sys_environment_revision, have been removed.)" The sentence is about commands that no longer exist. A gate that flags it is asking an author to delete accurate history.
  2. src/utils/response-envelope.ts:14 — "The three os datasource subcommands each carried their own transcription of …". datasource is a real topic (datasource introspect / list-tables / validate), just not a runnable id on its own. The prose names the topic, correctly.

The gate question

There is no gate over this class today.#11221's pin (src/commands/environments/environments.test.ts, #10967 pin: examples resolve to a real command id) reads static override examples via the TypeScript AST — it structurally cannot see a thrown-error string or a doc comment, and it only walks src/commands/**, so src/utils/** is outside its population entirely. #11313's new pin (src/utils/auth-config.test.ts) scans one file. Between them, four of the six findings above sit in no pin's population.

What the numbers argue, though, is that the error-path guidance class — the one #11313 was graded on, where a user is already stuck and the recovery instruction also fails — has exactly one instance repo-wide, and it is fixed. Everything else the sweep found is a doc comment: an IDE-hover and code-reading concern, not a "the tool is broken" concern. So the honest options are not "gate or N swaps":

  • A. Fix items 1 and 2 as a small card, no gate. Cheapest; leaves the class ungated, so it returns.
  • B. Fix 1 and 2, and widen the existing #10967 pin's population from examples arrays to all backticked invocations across packages/cli/src/**, with the self-retiring EXCLUDED map it already implements carrying items 3 and 4. That map is already built for exactly this — an exemption that asserts its own cause still holds — so the two true negatives would be declared rather than silently skipped, and an author who later deletes the legacy sentence is told to drop the exclusion.
  • C. As B, but teach the predicate to accept a bare topic name (item 4) and to recognise a prose-about-a-retired-command shape (item 3). Item 4 is mechanical (the topic list is right there in the Config); item 3 is not — "have been removed" is not a machine-readable marker, and inventing one is worse than an exclusion entry.

Recommendation: B. It reuses a mechanism this package already has, already documents, and has already retired once, and it declares the two true negatives instead of narrowing the population until they disappear.

Reproducing

The sweep is ~40 lines: load Config from packages/cli, walk src/** skipping *.test.ts variants, regex `(?:\\?\$ )?(os [^`\n]*)` plus the quoted forms, drop a trailing backslash (an escaped closing backtick inside a template literal), and accept an invocation when the remainder equals a registered id or starts with one plus a space. Requires the CLI and its dependency closure built, since oclif's pattern strategy scans dist/commands.


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions