Skip to content

fix: validate skills/agents/extends are lists (#12)#20

Merged
vessux merged 1 commit into
mainfrom
fix/12-scalar-list-validation
Jun 10, 2026
Merged

fix: validate skills/agents/extends are lists (#12)#20
vessux merged 1 commit into
mainfrom
fix/12-scalar-list-validation

Conversation

@vessux

@vessux vessux commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Closes #12

Problem

loadManifest validated that hooks and mcps are lists of strings, but skills, agents, and extends were cast as string[] with no runtime check. A YAML scalar (e.g. extends: good, skills: local/demo) survived the cast as a plain string and was iterated character-by-character downstream:

  • umbel list crashed with an uncaught TypeError: …join is not a function (list.ts:58), blocking the entire listing.
  • umbel build reported nonsense per-character parents/refs (extends missing parent 'g', nine skills/<char> refs).

Fix

Apply the same Array.isArray(...) && every(typeof === "string") guard already used for hooks/mcps to extends, skills, and agents, throwing a single actionable UsageError:

  • extends'extends' must be a list of names
  • skills / agents'…' must be a list of qualified <source>/<name> refs (wording matched to the hooks/mcps precedent, since these are the same kind of source-qualified ref)

Discovery (discover.ts:46-50) already catches a throwing loadManifest and marks the bundle malformed, so umbel list no longer crashes — it now treats the scalar bundle exactly like a bad-shape hooks/mcps bundle.

Acceptance criteria

  • A scalar skills/agents/extends produces a single "must be a list" validation error.
  • umbel list does not crash on a parseable bundle with a scalar field (treated as malformed).
  • A correctly-formed list (extends: [good]) still works (verified end-to-end).
  • Tests cover scalar inputs for all three fields (plus list-with-non-string-element coverage for the every clause).

Out of scope

How malformed bundles are displayed/surfaced in list/build (tracked in #6) is unchanged — a malformed bundle surfaces as "not found" on build, matching the pre-existing hooks/mcps behavior.

Verification

  • npm test → 347 passing (+3 new)
  • tsc --noEmit → clean
  • biome check . → clean
  • Manual e2e: reproduced the issue's exact scenarios — umbel list no longer crashes; control list-form extends: [good] renders correctly.

A YAML scalar in skills, agents, or extends survived the 'as string[]'
cast and was iterated character-by-character downstream — crashing
'umbel list' (TypeError on .join) and producing per-character nonsense
refs/parents in 'umbel build'.

Apply the same Array.isArray + every(string) guard already used for
hooks/mcps so a scalar yields one actionable 'must be a list' error.
Discovery already catches the throw and marks the bundle malformed, so
'umbel list' no longer crashes — matching the existing hooks/mcps path.
@vessux vessux merged commit d852c7e into main Jun 10, 2026
4 checks passed
@vessux vessux deleted the fix/12-scalar-list-validation branch June 10, 2026 18:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scalar skills/agents/extends crash 'umbel list' and char-iterate in 'umbel build'

1 participant