Skip to content

SDK: FlagDefinition's index signature defeats excess-property checking, so a misnamed flag field type-checks and then aborts activation (dropping later commands) #825

Closed

Description

@unbraind

pm --version 2026.7.29. Found while building a new package against the typed authoring surface.

What happens

FlagDefinition ends with an index signature:

export interface FlagDefinition {
    long?: string;
    short?: string;
    value_name?: string;
    value_type?: FlagValueType;
    // ...
    /** Extension metadata remains structurally interoperable while activation rejects unknown runtime fields. */
    [key: string]: unknown;
}

That index signature disables excess-property checking, so this type-checks cleanly with strict: true:

api.registerCommand({
  name: "vcs preview",
  flags: [{ name: "--fail-on <threshold>", description: "...", type: "string" }],
  run: (ctx) => ({ ok: true }),
});

tsc --noEmit is silent. tsc emits. The package builds. Then activation hard-fails:

registerCommand definition metadata invalid (command="vcs preview", registration_index=1):
registerFlags flags[0] contains unknown field(s): name

The correct spelling is long + value_name, but nothing in the type system says so, because name is admitted by the index signature — and name is the plausible guess, since CommandDefinition and ExtensionCommandArgumentDefinition both use name.

Why this is worse than an ordinary typo

The failure is not local. Per GH-719/#764 semantics, a rejected registerCommand aborts registration at that command and silently drops every later sibling. My extension registered 3 commands; the bad flag was on the 2nd, so commands 2 and 3 vanished while command 1 stayed. And per #772 a dropped command's --help exits 0 printing the parent group's help, and invoking it gives a Commander arity error that never mentions extensions.

So the chain is: a field name the type system accepts → activation abort → silently missing commands → exit-0 misleading errors at the call site. The typed authoring surface exists precisely to catch this class at compile time, and here it cannot.

The same index signature is on SchemaFieldDefinition, SchemaItemTypeOptionDefinition and SchemaItemTypeCommandOptionPolicyDefinition, so the gap is not specific to flags.

Suggested fix

The doc comment states the intent: "Extension metadata remains structurally interoperable while activation rejects unknown runtime fields." The problem is that the type permits exactly the set the runtime rejects. Options, in preference order:

  1. Drop the index signature from the known-field interfaces and let authors widen deliberately (flags: [{...}] as FlagDefinition[]) if they really carry extra metadata. This restores excess-property checking, which is the one mechanism that catches a misspelled optional field.
  2. Keep interoperability but constrain the escape hatch, e.g. [key: x_${string}]: unknown, so forward-compatible extra metadata is namespaced and any unnamespaced unknown key is a compile error. This matches what activation enforces.
  3. If neither is acceptable, at minimum have the activation error name the correct field: unknown field(s): name — did you mean "long"? The current message says what is wrong but not what is right, and long/value_name/value_type are not guessable from name/type.

(1) or (2) would have turned my 3 debugging cycles into one red squiggle.

Activity

  1. coderabbitai commented on Jul 30, 2026

    @coderabbitai
    🔗 Related PRs

    #49 - feat(schema): pm schema add-type for config-driven custom item types [closed]
    #150 - Extension SDK platform maturity: typed flags, self-identity, lifecycle teardown, field-type validation & override testing helpers [closed]
    #499 - Harden SDK schema-driven input fidelity and package preflight [merged]
    #563 - feat(sdk): own schema primitives and modernize init prefixes [merged]
    #657 - SDK extension authoring, trust boundaries, and scoped rendering [merged]


    📝 Issue Planner

    Check the box below or use the @coderabbitai plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    🧪 Issue enrichment is currently in open beta.

    You can configure auto-planning by selecting labels in the issue_enrichment configuration.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  2. unbraind commented on Jul 30, 2026

    @unbraind
    OwnerAuthor

    Tracked canonically as pm-unwsns after an all-status duplicate search. The PM item records scope, acceptance criteria, lineage, and this GitHub intake; it remains open and unclaimed until actively implemented.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions