Skip to content

pm health preflight collision check ignores declared ScopedPreflightOverrideDefinition ownership, so the warning is unresolvable #971

Closed

Description

@unbraind

Summary

ScopedPreflightOverrideDefinition lets an extension declare which commands its preflight override owns, and the host honours it at dispatch. But pm health's extension_preflight_override_collision check never consults that declaration: it reports a collision between any two extensions that register a preflight at all, even when their declared commands sets are disjoint.

The result is that the collision is unresolvable from extension code. There is no registration an author can write that clears the warning short of not registering a preflight.

This looks like the preflight analogue of #897 (renderer collision check ignoring host-enforced ownership), which was fixed for renderers.

Evidence (pm 2026.8.10)

Installing 15 published pm extensions into one project gives pm health → ok: false, 24 unique warnings. 21 of them are a complete pairwise clique over the seven extensions that register a preflight (C(7,2) = 21):

extension_preflight_override_collision:project:pm-github:project:pm-gantt-chart
extension_preflight_override_collision:project:pm-jira:project:pm-gantt-chart
extension_preflight_override_collision:project:pm-jira:project:pm-github
extension_preflight_override_collision:project:pm-linear:project:pm-gantt-chart
...
extension_preflight_override_collision:project:pm-todos:project:pm-slack-standup

A clique over "extensions that registered a preflight" is the signature of a check that is not looking at ownership at all.

The fix that should have worked, and does not

pm-todos registered an unscoped override whose first statement was a runtime scope guard:

api.registerPreflight((ctx: PreflightOverrideContext) => {
  const passthrough = { /* ... */ };
  if (ctx?.command !== "todos import") return passthrough;   // runtime-only scoping
  /* ... */
});

I converted it to a static declaration, which is exactly what the SDK type exists for:

api.registerPreflight({
  commands: ["todos import"],
  run: (ctx: PreflightOverrideContext) => { /* ... */ },
});

Verified after the change:

  • tsc --noEmit clean — the field name matches ScopedPreflightOverrideDefinition, so this is not a silently-ignored misnamed field.
  • The built artifact carries the declaration: grep -c 'commands: \["todos import"\]' dist/index.js → 1, and the same string is present in the installed copy under .agents/pm/extensions/pm-todos/dist/index.js.
  • The extension still activates and its commands still register (pm todos --help works), and pm health --full reports no extension_*failed|error|invalid warning.
  • The six pm-todos preflight collision warnings are byte-identical before and after. Unique warning count stays at 24.

So a correctly-scoped preflight collides with six extensions it can never run alongside.

Expected

extension_preflight_override_collision should be raised only when two registrations can actually contend — i.e. their effective command sets intersect. Concretely:

  • two unscoped registrations collide (both are global);
  • an unscoped registration collides with every scoped one;
  • two scoped registrations collide only where commands overlap.

Under that rule the fleet case above collapses from 21 warnings to 0 once each extension declares ownership, and the warning regains its meaning: it would then flag a real double-registration on the same command rather than the mere presence of a second extension.

Why this matters

pm health is the gate the fleet runs in CI with --strict-exit. A warning that cannot be cleared by correct code trains authors to ignore the check, and it is currently the sole reason a project that installs the published extension set reports ok: false. It also removes the incentive to declare ownership at all — which is the thing that makes preflight dispatch correct in the first place, since an unscoped override otherwise runs on every command.

Related

Activity

  1. coderabbitai commented on Aug 10, 2026

    @coderabbitai
    🔗 Related PRs

    #150 - Extension SDK platform maturity: typed flags, self-identity, lifecycle teardown, field-type validation & override testing helpers [merged]
    #326 - feat(extensions): least-privilege capability reconciliation (pm-fk84) [merged]
    #479 - feat(package): isolate project doctor diagnostics [merged]
    #657 - SDK extension authoring, trust boundaries, and scoped rendering [merged]
    #884 - SDK extension inventories, scoped activation, and stable agent context [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 Aug 10, 2026

    @unbraind
    OwnerAuthor

    Tracked canonically as pm-zryb9d under the extension ownership lineage. It owns health/runtime parity for scoped preflight command intersections and fleet negative controls.

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