fleet.js: unhandled ENOENT on missing roster (+ sweep class); install.sh mangles @mosaicstack/mosaic heading #792

Closed
opened 2026-07-16 19:37:12 +00:00 by jason.woltje · 1 comment
Owner

Summary

Two robustness defects observed on a fresh install.sh run (framework v3, mosaic@0.0.48,
Node v24.13.1). Primary: mosaic fleet ps crashes with a raw unhandled ENOENT stack trace when
the fleet roster is absent. Secondary: the installer prints a mangled package heading. Both are
mechanical, capacity-fill fixes under normal gates.

Primary — unhandled ENOENT class in fleet.js (treat as a class, not one line)

Repro

bash <(curl -fsSL .../tools/install.sh)   # completes OK, framework v3
mosaic fleet ps

Actual

Error: ENOENT open '~/.config/mosaic/fleet/roster.json'
    at loadFleetRoster (dist/commands/fleet.js:71) ...   # raw Node stack trace

(Node v24.13.1, mosaic@0.0.48.)

Expected

Graceful, actionable error and a nonzero exit — no stack trace. e.g.:
No fleet roster found at <path>. Run \mosaic fleet init` (or see docs: ).`

Scope — sweep, don't spot-fix

loadFleetRoster is one instance. Sweep fleet.js and sibling command modules for the same
pattern: an expected-config/state file read (readFileSync/JSON.parse/open) with no ENOENT
(or parse-error) guard that lets a raw Node error escape to the user. Each such read of an
expected-but-optional config should produce an actionable message + nonzero exit, not a trace.
Consider a shared helper (e.g. readConfigOrExit(path, hint)) to standardize the pattern.

Secondary — installer heading escaping bug

install.sh output shows a corrupted heading: the raw text printed aicstack/mosaic (npm package) — a mangled @mosaicstack/mosaic. Looks like an escaping/echo bug in the installer
heading (leading @mos eaten — possibly a variable-expansion, printf format, or ANSI/@
handling issue). Fix the installer heading/echo so it prints @mosaicstack/mosaic intact.

Acceptance criteria

  • mosaic fleet ps with no roster prints an actionable message + nonzero exit, no stack trace.
  • fleet.js + siblings swept; every expected-config read has ENOENT/parse guards (shared
    helper preferred). List the sites fixed in the PR.
  • Installer heading prints @mosaicstack/mosaic correctly.
  • Tests-first, ≥85% coverage on changed logic (unit tests for the missing-roster path and any
    new shared helper); independent review.

Gates & scope

TDD tests-first ≥85%; trunk-based squash PR; independent review. Mechanical — GPT/Codex lane is
fine. Capacity-fill; below #790 and the agents-dir protection task in priority. May be delivered as
one PR (both fixes) or two; reviewer's call. Framework-PR firewall: operator-agnostic.

## Summary Two robustness defects observed on a fresh `install.sh` run (framework v3, `mosaic@0.0.48`, Node v24.13.1). Primary: `mosaic fleet ps` crashes with a raw unhandled `ENOENT` stack trace when the fleet roster is absent. Secondary: the installer prints a mangled package heading. Both are mechanical, capacity-fill fixes under normal gates. ## Primary — unhandled ENOENT class in fleet.js (treat as a class, not one line) ### Repro ``` bash <(curl -fsSL .../tools/install.sh) # completes OK, framework v3 mosaic fleet ps ``` ### Actual ``` Error: ENOENT open '~/.config/mosaic/fleet/roster.json' at loadFleetRoster (dist/commands/fleet.js:71) ... # raw Node stack trace ``` (Node v24.13.1, mosaic@0.0.48.) ### Expected Graceful, actionable error and a nonzero exit — no stack trace. e.g.: `No fleet roster found at <path>. Run \`mosaic fleet init\` (or see docs: <link>).` ### Scope — sweep, don't spot-fix `loadFleetRoster` is one instance. Sweep `fleet.js` **and sibling command modules** for the same pattern: an expected-config/state file read (`readFileSync`/`JSON.parse`/`open`) with no ENOENT (or parse-error) guard that lets a raw Node error escape to the user. Each such read of an expected-but-optional config should produce an actionable message + nonzero exit, not a trace. Consider a shared helper (e.g. `readConfigOrExit(path, hint)`) to standardize the pattern. ## Secondary — installer heading escaping bug `install.sh` output shows a corrupted heading: the raw text printed `aicstack/mosaic (npm package)` — a mangled `@mosaicstack/mosaic`. Looks like an escaping/echo bug in the installer heading (leading `@mos` eaten — possibly a variable-expansion, `printf` format, or ANSI/`@` handling issue). Fix the installer heading/echo so it prints `@mosaicstack/mosaic` intact. ## Acceptance criteria - [ ] `mosaic fleet ps` with no roster prints an actionable message + nonzero exit, no stack trace. - [ ] `fleet.js` + siblings swept; every expected-config read has ENOENT/parse guards (shared helper preferred). List the sites fixed in the PR. - [ ] Installer heading prints `@mosaicstack/mosaic` correctly. - [ ] Tests-first, ≥85% coverage on changed logic (unit tests for the missing-roster path and any new shared helper); independent review. ## Gates & scope TDD tests-first ≥85%; trunk-based squash PR; independent review. Mechanical — GPT/Codex lane is fine. Capacity-fill; below #790 and the agents-dir protection task in priority. May be delivered as one PR (both fixes) or two; reviewer's call. Framework-PR firewall: operator-agnostic.
Author
Owner

Backlog (non-blocking, from #818 RoR MINOR-2): normalizeFleetRosterV1 (fleet-roster-v1.ts:199-207) rewraps as new FleetRosterConfigurationError(error.message), dropping the original cause/stack; the instanceof Error catch is also broad enough to relabel a genuine programmer TypeError inside the validation body as a user configuration error. Low risk (pure-validation body). Suggested follow-up: pass { cause: error } and/or narrow the catch. Captured here per coordinator ruling — non-blocking, did not gate the #818 merge.

**Backlog (non-blocking, from #818 RoR MINOR-2):** `normalizeFleetRosterV1` (fleet-roster-v1.ts:199-207) rewraps as `new FleetRosterConfigurationError(error.message)`, dropping the original `cause`/`stack`; the `instanceof Error` catch is also broad enough to relabel a genuine programmer `TypeError` inside the validation body as a user configuration error. Low risk (pure-validation body). Suggested follow-up: pass `{ cause: error }` and/or narrow the catch. Captured here per coordinator ruling — non-blocking, did not gate the #818 merge.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#792