Summary
PR #587 (generic git host / Gitea support) surfaces several pre-existing architectural and DX gaps in the dependency identity, token resolution, and error-reporting paths. None of these block #587 directly, but they should land before multi-host support is GA so we don't ship known sharp edges to users adopting Gitea / Bitbucket / self-hosted GitLab.
Items
1. Lockfile identity collision: get_unique_key() omits host
DependencyReference.get_unique_key() and LockedDependency.get_unique_key() both return the bare repo_url (e.g. team/skills) without the host. Two dependencies with the same owner/repo on different hosts collide silently in apm.lock:
- name: skills
repo_url: gitea.myorg.com/team/skills
- name: skills
repo_url: github.com/team/skills
→ second entry overwrites the first; install order becomes load-bearing.
Severity: correctness / data-integrity. Becomes much more likely once #587 lands.
Files: src/apm_cli/models/dependency/reference.py (lines ~176-190), src/apm_cli/deps/lockfile.py (lines ~43-49, ~196).
Suggested fix: include host in get_unique_key() for non-default hosts (preserve bare owner/repo for github.com to keep existing lockfiles stable).
2. Asymmetric token resolution between download and clone paths
_download_github_file calls auth_resolver.resolve() directly (line ~1207 of github_downloader.py), while the clone path correctly routes through _resolve_dep_token() which returns None for generic hosts. The asymmetry is not a security issue (HTTPS is the transport boundary per core/auth.py:377-379), but it produces misleading 401 errors when a GitHub PAT is sent to a Gitea / GitLab server that rejects it — instead of falling through to git-credential-helper as the clone path does.
Severity: DX / error-message clarity.
Suggested fix: route _download_github_file token resolution through _resolve_dep_token() for consistency.
3. is_gitlab_hostname() is convention-based and fragile
startswith("gitlab.") misses org-managed GitLab instances on bespoke hostnames (code.acme.com, git.team.org, etc.). The whole GitLab-vs-other-generic-host bifurcation in #587 falls back to a heuristic that doesn't hold.
Severity: correctness on non-conformant hostnames.
Suggested fix: consider explicit user signaling (type: gitlab in apm.yml) or capability-detection via API probe + cache, rather than relying on hostname conventions.
4. Silent error swallowing in download fallback chain
_download_github_file runs up to 7 attempts (raw + API × ref × API version), and currently swallows non-404 errors at each step — auth failures, 5xx, network errors all surface to the user as "file not found." There's also no verbose_callback breadcrumb per attempt, so --verbose doesn't help users understand which endpoint was tried.
Severity: DX / debuggability.
Suggested fix: classify exceptions, only swallow 404, surface 401/403/5xx with the host + endpoint context. Emit one verbose_callback line per attempt.
5. Pre-existing GitHub-specific error strings reachable for generic hosts
Strings like "GitHub API rate limit exceeded" are now reachable from Gitea / GitLab error paths via the shared download code. Should be parameterized on host.
Severity: polish.
Non-goals
Not changing the deliberate project stance that "HTTPS is the transport security boundary" (auth.py:377-379) — global PATs may be sent to user-configured hosts. This issue is about correctness, identity, and error-clarity, not transport gating.
Why now
PR #587 quadruples the surface area of the download fallback chain and introduces multi-host scenarios the lockfile identity model wasn't designed for. Tracking these explicitly so they don't get forgotten when #587 lands.
Summary
PR #587 (generic git host / Gitea support) surfaces several pre-existing architectural and DX gaps in the dependency identity, token resolution, and error-reporting paths. None of these block #587 directly, but they should land before multi-host support is GA so we don't ship known sharp edges to users adopting Gitea / Bitbucket / self-hosted GitLab.
Items
1. Lockfile identity collision:
get_unique_key()omits hostDependencyReference.get_unique_key()andLockedDependency.get_unique_key()both return the barerepo_url(e.g.team/skills) without the host. Two dependencies with the sameowner/repoon different hosts collide silently inapm.lock:→ second entry overwrites the first; install order becomes load-bearing.
Severity: correctness / data-integrity. Becomes much more likely once #587 lands.
Files:
src/apm_cli/models/dependency/reference.py(lines ~176-190),src/apm_cli/deps/lockfile.py(lines ~43-49, ~196).Suggested fix: include host in
get_unique_key()for non-default hosts (preserve bareowner/repoforgithub.comto keep existing lockfiles stable).2. Asymmetric token resolution between download and clone paths
_download_github_filecallsauth_resolver.resolve()directly (line ~1207 ofgithub_downloader.py), while the clone path correctly routes through_resolve_dep_token()which returnsNonefor generic hosts. The asymmetry is not a security issue (HTTPS is the transport boundary percore/auth.py:377-379), but it produces misleading 401 errors when a GitHub PAT is sent to a Gitea / GitLab server that rejects it — instead of falling through to git-credential-helper as the clone path does.Severity: DX / error-message clarity.
Suggested fix: route
_download_github_filetoken resolution through_resolve_dep_token()for consistency.3.
is_gitlab_hostname()is convention-based and fragilestartswith("gitlab.")misses org-managed GitLab instances on bespoke hostnames (code.acme.com,git.team.org, etc.). The whole GitLab-vs-other-generic-host bifurcation in #587 falls back to a heuristic that doesn't hold.Severity: correctness on non-conformant hostnames.
Suggested fix: consider explicit user signaling (
type: gitlabinapm.yml) or capability-detection via API probe + cache, rather than relying on hostname conventions.4. Silent error swallowing in download fallback chain
_download_github_fileruns up to 7 attempts (raw + API × ref × API version), and currently swallows non-404 errors at each step — auth failures, 5xx, network errors all surface to the user as "file not found." There's also noverbose_callbackbreadcrumb per attempt, so--verbosedoesn't help users understand which endpoint was tried.Severity: DX / debuggability.
Suggested fix: classify exceptions, only swallow 404, surface 401/403/5xx with the host + endpoint context. Emit one
verbose_callbackline per attempt.5. Pre-existing GitHub-specific error strings reachable for generic hosts
Strings like
"GitHub API rate limit exceeded"are now reachable from Gitea / GitLab error paths via the shared download code. Should be parameterized onhost.Severity: polish.
Non-goals
Not changing the deliberate project stance that "HTTPS is the transport security boundary" (auth.py:377-379) — global PATs may be sent to user-configured hosts. This issue is about correctness, identity, and error-clarity, not transport gating.
Why now
PR #587 quadruples the surface area of the download fallback chain and introduces multi-host scenarios the lockfile identity model wasn't designed for. Tracking these explicitly so they don't get forgotten when #587 lands.