Skip to content

fix(deps): reject shorthand '@alias' at parse time with migration error#1301

Open
prateek wants to merge 1 commit into
microsoft:mainfrom
prateek:fix/shorthand-alias-subpath-reject
Open

fix(deps): reject shorthand '@alias' at parse time with migration error#1301
prateek wants to merge 1 commit into
microsoft:mainfrom
prateek:fix/shorthand-alias-subpath-reject

Conversation

@prateek
Copy link
Copy Markdown

@prateek prateek commented May 13, 2026

Summary

apm.yml string-form entries like owner/repo/sub/path@alias (subpath + trailing @alias) were silently miscoerced: the @alias ended up glued to virtual_path and the install failed downstream with the unhelpful Subdirectory 'skills/orchestration@orca-stration' not found in repository.

Shorthand @alias was deliberately retired in #340 (npm/go/cargo @version collision). The whole-repo case (owner/repo@alias) already failed loudly with Invalid repository path component, but the subpath case slipped past _detect_virtual_package because it never looked for @ in the trailing segments.

This PR adds a single parse-time guard so all three shorthand-@alias shapes — owner/repo@alias, owner/repo/sub@alias, owner/repo#ref@alias — produce the same actionable error pointing the user to the object form.

Repro

# apm.yml
dependencies:
  apm:
    - stablyai/orca/skills/orchestration@orca-stration

Before:

[x] 1 package failed:
  +- orca-orchestration@orca-stration -- Failed to download dependency stablyai/orca: Subdirectory 'skills/orchestration@orca-stration' not found in repository

After:

[x] Failed to parse apm.yml: Invalid APM dependency 'stablyai/orca/skills/orchestration@orca-stration': Shorthand '@alias' is not supported in 'stablyai/orca/skills/orchestration@orca-stration'. Use the object form with an 'alias:' field to install a dependency under a custom directory name. See: https://microsoft.github.io/apm/consumer/manage-dependencies/#reference-formats

What changed

  • src/apm_cli/models/dependency/reference.py — new _reject_shorthand_alias() static method called from parse() after the local-path / // checks. Skips https://, http://, ssh://, and SCP shorthand (<user>@host:path) — those forms have dedicated parsers that legitimately extract @alias. Anything else with @ raises with migration guidance.
  • docs/src/content/docs/consumer/manage-dependencies.md — removed the two stale Aliased / Pinned + aliased rows that survived feat: improve version pinning guidance and CLI visibility #340, and folded alias: into the Object form row description.
  • Dropped two stale user/repo@alias examples from the public parse() docstring.
  • Tests: 8 new rejection cases (subpath, deep subpath, subpath+ref, FQDN, URL-encoded %40, trailing @) plus a positive https://user@github.com/... regression test that locks in the guard's HTTPS-userinfo carve-out. Three pre-existing tests that asserted the old "absorb @ into ref" behavior were tightened to assert the new uniform rejection.

Compatibility

  • No round-trip risk. to_canonical() never emits @alias, and LockedDependency.from_dependency_ref / to_dependency_ref work on structured fields, never re-parse()-ing a string. Historical lockfiles cannot contain @alias and so cannot trip the new guard on load.
  • SSH paths unchanged. ssh://...@alias and git@host:path@alias still parse and extract the alias as before.
  • Object form unchanged. { git: ..., path: ..., alias: ... } is unaffected.

Test plan

  • pytest tests/unit/ — 8,301 passing
  • Re-ran the bug-report repro against the patched build — rejected at parse time with the new error
  • Re-ran the object-form workaround from the bug report — installs to .agents/skills/orca-stration/ as expected
  • CI green

`apm.yml` entries like `owner/repo/sub/path@alias` were silently
miscoerced -- the `@alias` ended up glued to the virtual path and failed
downstream with "Subdirectory not found in repository". Shorthand
`@alias` was deliberately removed in microsoft#340 (npm/go/cargo `@version`
collision); the whole-repo case already failed loudly, but the subpath
case slipped past `_detect_virtual_package`.

Add a single parse-time guard (`_reject_shorthand_alias`) that rejects
shorthand `@alias` uniformly with an actionable migration error pointing
to the object form. SSH `@alias` extraction is preserved -- the
`ssh://` and SCP parsers already handle it cleanly, so the guard skips
those forms.

Also drop two stale `@alias` example lines from the public `parse()`
docstring and remove the surviving `@alias` rows from
`consumer/manage-dependencies.md`.
@prateek
Copy link
Copy Markdown
Author

prateek commented May 13, 2026

@microsoft-github-policy-service agree

@prateek prateek marked this pull request as ready for review May 13, 2026 01:36
Copilot AI review requested due to automatic review settings May 13, 2026 01:36
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR tightens DependencyReference.parse() so deprecated shorthand @alias forms (especially with subpaths) are rejected early with a clear migration error, preventing the alias from being silently misinterpreted as part of a virtual path or ref.

Changes:

  • Add a parse-time guard (_reject_shorthand_alias) to uniformly reject shorthand @alias in non-URL, non-SSH forms.
  • Update unit tests to expect the new rejection behavior across shorthand, FQDN, subpath, and URL-encoded @ cases (and add an HTTPS userinfo regression case).
  • Update docs to remove stale shorthand alias examples and document alias: via object form.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/unit/test_package_identity.py Updates shorthand alias tests to expect parse-time rejection with the migration message.
tests/unit/test_generic_git_urls.py Adds/updates rejection cases for nested-group shorthand, including the subpath+alias bug case.
tests/unit/test_canonicalization.py Expands coverage for shorthand alias rejection (subpath, encoded @, trailing @) and adds HTTPS userinfo regression coverage.
src/apm_cli/models/dependency/reference.py Introduces _reject_shorthand_alias() and calls it early in parse() to reject deprecated shorthand alias forms consistently.
docs/src/content/docs/consumer/manage-dependencies.md Removes stale shorthand alias rows and highlights alias: support in object form.

Comment on lines +403 to +410
stripped = dependency_str.strip()
if "@" not in stripped:
return
if stripped.lower().startswith(("https://", "http://", "ssh://")):
return
if SCP_LIKE_RE.match(stripped):
return
raise ValueError(
Comment on lines +1433 to +1434
cls._reject_shorthand_alias(dependency_str)

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.

2 participants