Support extensions with shared namespace prefixes
Summary
Today, azd blocks installation of any extension whose namespace shares a prefix with another installed extension's namespace (added in #6671 to resolve #6211). For example, with azure.ai.builder (namespace ai) installed, installing azure.ai.agents (namespace ai.agent) is rejected. This guard ensures predictable command routing today, and we'd like to build on it so that multiple extensions can coexist under a shared parent namespace when there is no real command-name collision.
The goal of this issue is to evolve the namespace handling to support overlapping namespaces natively, while preserving the safety properties of the current check.
Background
bindExtension (cli/azd/cmd/extensions.go) builds the cobra command tree from each installed extension's namespace. The current implementation does not deduplicate when registering nodes:
- If an extension with namespace
ai.agent registers first, it creates an ai group cobra command, then attaches agent as a child.
- If an extension with namespace
ai registers afterwards, it calls current.Add("ai", …) on root, creating a second ai cobra child. Cobra's behavior with duplicate command names is undefined, leading to nondeterministic routing depending on map iteration order in cli/azd/cmd/root.go.
The current safeguard (checkNamespaceConflict in cli/azd/cmd/extension.go) addresses this by blocking installation any time two namespaces share a prefix, which keeps routing deterministic. With richer information now available from extension metadata, we can narrow this check to only block genuine command-name collisions.
Goals
- Allow extensions with overlapping namespaces (e.g.,
ai + ai.finetune) to be installed and used together.
- Continue blocking installations that would create real command-name collisions (e.g., extension A declares an internal subcommand named
finetune AND extension B claims namespace ai.finetune).
- Reject installations where an extension namespace would collide with a built-in azd command (
auth, init, deploy, env, up, down, extension, pipeline, template, mcp, version, config, monitor, hooks, vs-server, add, restore, package, provision, show, etc.).
- Provide a coherent help/usage experience for hybrid namespace nodes.
- Preserve safety when extension metadata is unavailable.
Non-goals
- Restructuring
ActionDescriptor.Add for global deduplication.
- Changes to the
ExtensionCommandMetadata schema.
- Auto-uninstalling a conflicting extension on the user's behalf.
- Lint-time conflict detection at
azd x publish-metadata.
Proposed solution
1. Tree merging in bindExtension
Rewrite the namespace-walk so that it always finds-or-reuses an existing namespace node instead of creating duplicates. Allow a single descriptor to be both:
- A leaf with an action handler, annotations, and
DisableFlagParsing: true (when an extension claims the full path), and
- A parent with child subcommands (when other extensions claim nested paths under it).
Cobra natively supports hybrid commands; subcommand resolution via cmd.Find wins over arg pass-through, so azd ai finetune correctly routes to the finetune child while azd ai init (where init is not a registered child) passes through to the leaf extension's binary.
To distinguish reusable extension-owned namespace nodes from built-in azd commands, mark extension-created nodes with an annotation (e.g., extension.namespace_owner=true). When bindExtension encounters an existing child whose name matches but is not extension-owned, return an install-time error.
2. Targeted overlap detection
Refine the prefix-based check into checkNamespaceCommandOverlap, which uses extensions.Manager.LoadMetadata to enumerate the leaf extension's declared command tree (ExtensionCommandMetadata.Commands). Block installation only when:
- Two namespaces are exactly equal, OR
- Prefix overlap exists AND the leaf extension's metadata declares a top-level subcommand (or alias, including hidden) whose name matches the next path segment of the longer namespace.
The check is symmetric (apply in both directions), case-insensitive, and includes aliases and hidden subcommands.
When metadata is unavailable for a leaf extension (e.g., extensions that don't implement the metadata command), fall back to the existing prefix-overlap behavior to preserve safety.
3. Runtime tie-breaker for post-install drift
If a user has installed both ext-A (namespace ai) and ext-B (namespace ai.finetune), and later ext-A upgrades to a version that newly declares an internal finetune subcommand, the install-time check can no longer help. Add a runtime tie-breaker so that when azd ai finetune is invoked and a registered cobra child matches finetune, the child wins (cobra default), and emit a telemetry warning event so we can detect drift in the wild.
Re-run the overlap check on every extension install/upgrade to surface the conflict earlier when possible.
4. Merged help renderer
A hybrid leaf has:
- Cobra children (subcommands contributed by other extensions in their nested namespaces), and
- Internal subcommands inside the leaf extension's binary that cobra doesn't know about.
Default cobra help would only list cobra children; ext-A --help would only list ext A's internal commands. Either view is incomplete.
Install a custom HelpFunc on hybrid leaf cobra commands that loads the leaf extension's metadata and merges its declared top-level commands with the cobra children into a single deduplicated Available Commands: block. When metadata is unavailable, fall back to invoking the extension binary's --help and appending a "Subcommands contributed by other extensions:" footer listing the cobra children.
Verify internal/figspec produces an equivalent merged spec for hybrid leaves; update if needed. Tab completion remains limited to cobra children — document as a known limitation.
5. Positional argument shadowing
Cobra resolves subcommands before positional arguments, so azd ai finetune will route to the ai.finetune extension even if a user intends to pass finetune as a positional value to ext A. There is no syntactic disambiguation in the standard CLI grammar, so we mitigate via up-front validation and clear guidance:
- Install-time positional check. When checking overlap, also inspect the leaf extension's metadata for any positional
Argument declared at the namespace root (i.e., on a Command whose Name is a single-segment path). If that argument declares ValidValues or Aliases containing the next path segment of the candidate nested namespace, block installation with a precise error citing the conflicting value. Apply symmetrically (validate either direction on install).
- Default behavior. When no overlap is detected up front, accept cobra's default (subcommand wins). The merged help renderer ensures users can discover the contributed subcommand from
azd ai --help.
- Authoring guidance. Update extension authoring documentation to recommend (a) preferring explicit subcommands over open positional args at the namespace root for extensions that may share a namespace prefix, and (b) declaring
Argument.ValidValues whenever feasible so install-time detection can protect users.
- Telemetry. The runtime tie-breaker (Section 3) already emits a warning event when a hybrid leaf's first arg matches a contributed child subcommand, giving us visibility into how often this surfaces in practice.
Edge cases
- Deeply nested mixed depths (e.g.,
ai + ai.models.eval) — create intermediate group nodes under the hybrid ai leaf as needed.
- Repeated install (upgrade) of the same extension — skip self in the overlap check.
- Aliases and hidden subcommands — included in the overlap check.
- Empty namespace string — preserved current behavior (skip).
- Case-insensitive comparison for namespace segments and command names.
- Built-in command collisions at any nesting depth — reject when the existing matching node is not extension-owned.
- Uninstall — tree is rebuilt from the installed list on next process start; no special cleanup required.
- Variadic positional args on the namespace root — only the first arg can be shadowed; subsequent args pass through to the leaf extension.
- Open-ended positional args without
ValidValues — install-time check cannot protect; fall back to cobra default routing and surface via merged help and runtime telemetry.
Affected code
cli/azd/cmd/extensions.go — bindExtension rewrite; merged help renderer.
cli/azd/cmd/extension.go — replace checkNamespaceConflict / namespacesConflict with metadata-driven overlap check (including positional ValidValues); wire into install path.
cli/azd/cmd/auto_install.go — verify partial-namespace auto-install still behaves correctly with hybrid leaves.
cli/azd/internal/figspec — verify merged spec generation for hybrid leaves.
cli/azd/docs/extensions/extension-framework.md (and related authoring docs) — add guidance on subcommand-vs-positional design and the role of ValidValues in coexistence safety.
- Tests:
cli/azd/cmd/extensions_test.go
cli/azd/cmd/extension_namespace_test.go
cli/azd/cmd/extension_test.go
cli/azd/cmd/extension_coverage3_test.go
cli/azd/cmd/run_errors_coverage3_test.go
- Snapshots: rerun
UPDATE_SNAPSHOTS=true go test ./cmd -run 'TestFigSpec|TestUsage' if help text changes.
Acceptance criteria
- An extension with namespace
ai.finetune can be installed alongside an extension with namespace ai, and both work end-to-end (azd ai, azd ai <internal-subcommand>, and azd ai finetune all route correctly), regardless of installation order.
- Installing two extensions where one declares an internal subcommand that collides with the other's nested namespace fails at install time with a clear error identifying the conflicting subcommand.
- Installing two extensions where one declares a positional
Argument.ValidValues (or Aliases) entry that matches the other's next namespace segment fails at install time with a clear error identifying the conflicting value.
- Installing an extension whose namespace top segment matches a built-in azd command fails at install time.
azd <hybrid-leaf> --help shows a unified list of subcommands from both the leaf extension and any nested extensions.
- When the leaf extension does not implement the metadata command, install-time overlap detection falls back to the existing prefix-overlap behavior.
- The runtime tie-breaker prefers a matching cobra child subcommand over arg pass-through for hybrid leaves and emits telemetry on drift.
- Extension authoring documentation includes guidance on subcommand-vs-positional design and the role of
ValidValues in safe coexistence.
Related
Support extensions with shared namespace prefixes
Summary
Today, azd blocks installation of any extension whose namespace shares a prefix with another installed extension's namespace (added in #6671 to resolve #6211). For example, with
azure.ai.builder(namespaceai) installed, installingazure.ai.agents(namespaceai.agent) is rejected. This guard ensures predictable command routing today, and we'd like to build on it so that multiple extensions can coexist under a shared parent namespace when there is no real command-name collision.The goal of this issue is to evolve the namespace handling to support overlapping namespaces natively, while preserving the safety properties of the current check.
Background
bindExtension(cli/azd/cmd/extensions.go) builds the cobra command tree from each installed extension's namespace. The current implementation does not deduplicate when registering nodes:ai.agentregisters first, it creates anaigroup cobra command, then attachesagentas a child.airegisters afterwards, it callscurrent.Add("ai", …)on root, creating a secondaicobra child. Cobra's behavior with duplicate command names is undefined, leading to nondeterministic routing depending on map iteration order incli/azd/cmd/root.go.The current safeguard (
checkNamespaceConflictincli/azd/cmd/extension.go) addresses this by blocking installation any time two namespaces share a prefix, which keeps routing deterministic. With richer information now available from extension metadata, we can narrow this check to only block genuine command-name collisions.Goals
ai+ai.finetune) to be installed and used together.finetuneAND extension B claims namespaceai.finetune).auth,init,deploy,env,up,down,extension,pipeline,template,mcp,version,config,monitor,hooks,vs-server,add,restore,package,provision,show, etc.).Non-goals
ActionDescriptor.Addfor global deduplication.ExtensionCommandMetadataschema.azd x publish-metadata.Proposed solution
1. Tree merging in
bindExtensionRewrite the namespace-walk so that it always finds-or-reuses an existing namespace node instead of creating duplicates. Allow a single descriptor to be both:
DisableFlagParsing: true(when an extension claims the full path), andCobra natively supports hybrid commands; subcommand resolution via
cmd.Findwins over arg pass-through, soazd ai finetunecorrectly routes to thefinetunechild whileazd ai init(whereinitis not a registered child) passes through to the leaf extension's binary.To distinguish reusable extension-owned namespace nodes from built-in azd commands, mark extension-created nodes with an annotation (e.g.,
extension.namespace_owner=true). WhenbindExtensionencounters an existing child whose name matches but is not extension-owned, return an install-time error.2. Targeted overlap detection
Refine the prefix-based check into
checkNamespaceCommandOverlap, which usesextensions.Manager.LoadMetadatato enumerate the leaf extension's declared command tree (ExtensionCommandMetadata.Commands). Block installation only when:The check is symmetric (apply in both directions), case-insensitive, and includes aliases and hidden subcommands.
When metadata is unavailable for a leaf extension (e.g., extensions that don't implement the metadata command), fall back to the existing prefix-overlap behavior to preserve safety.
3. Runtime tie-breaker for post-install drift
If a user has installed both
ext-A(namespaceai) andext-B(namespaceai.finetune), and laterext-Aupgrades to a version that newly declares an internalfinetunesubcommand, the install-time check can no longer help. Add a runtime tie-breaker so that whenazd ai finetuneis invoked and a registered cobra child matchesfinetune, the child wins (cobra default), and emit a telemetry warning event so we can detect drift in the wild.Re-run the overlap check on every extension install/upgrade to surface the conflict earlier when possible.
4. Merged help renderer
A hybrid leaf has:
Default cobra help would only list cobra children;
ext-A --helpwould only list ext A's internal commands. Either view is incomplete.Install a custom
HelpFuncon hybrid leaf cobra commands that loads the leaf extension's metadata and merges its declared top-level commands with the cobra children into a single deduplicatedAvailable Commands:block. When metadata is unavailable, fall back to invoking the extension binary's--helpand appending a "Subcommands contributed by other extensions:" footer listing the cobra children.Verify
internal/figspecproduces an equivalent merged spec for hybrid leaves; update if needed. Tab completion remains limited to cobra children — document as a known limitation.5. Positional argument shadowing
Cobra resolves subcommands before positional arguments, so
azd ai finetunewill route to theai.finetuneextension even if a user intends to passfinetuneas a positional value to ext A. There is no syntactic disambiguation in the standard CLI grammar, so we mitigate via up-front validation and clear guidance:Argumentdeclared at the namespace root (i.e., on aCommandwhoseNameis a single-segment path). If that argument declaresValidValuesorAliasescontaining the next path segment of the candidate nested namespace, block installation with a precise error citing the conflicting value. Apply symmetrically (validate either direction on install).azd ai --help.Argument.ValidValueswhenever feasible so install-time detection can protect users.Edge cases
ai+ai.models.eval) — create intermediate group nodes under the hybridaileaf as needed.ValidValues— install-time check cannot protect; fall back to cobra default routing and surface via merged help and runtime telemetry.Affected code
cli/azd/cmd/extensions.go—bindExtensionrewrite; merged help renderer.cli/azd/cmd/extension.go— replacecheckNamespaceConflict/namespacesConflictwith metadata-driven overlap check (including positionalValidValues); wire into install path.cli/azd/cmd/auto_install.go— verify partial-namespace auto-install still behaves correctly with hybrid leaves.cli/azd/internal/figspec— verify merged spec generation for hybrid leaves.cli/azd/docs/extensions/extension-framework.md(and related authoring docs) — add guidance on subcommand-vs-positional design and the role ofValidValuesin coexistence safety.cli/azd/cmd/extensions_test.gocli/azd/cmd/extension_namespace_test.gocli/azd/cmd/extension_test.gocli/azd/cmd/extension_coverage3_test.gocli/azd/cmd/run_errors_coverage3_test.goUPDATE_SNAPSHOTS=true go test ./cmd -run 'TestFigSpec|TestUsage'if help text changes.Acceptance criteria
ai.finetunecan be installed alongside an extension with namespaceai, and both work end-to-end (azd ai,azd ai <internal-subcommand>, andazd ai finetuneall route correctly), regardless of installation order.Argument.ValidValues(orAliases) entry that matches the other's next namespace segment fails at install time with a clear error identifying the conflicting value.azd <hybrid-leaf> --helpshows a unified list of subcommands from both the leaf extension and any nested extensions.ValidValuesin safe coexistence.Related
-mflag inconsistently not working #6211.