Skip to content

fix(install): warn when --allow-protocol-fallback reuses a custom port across schemes (#786)#789

Merged
danielmeppiel merged 5 commits intomicrosoft:mainfrom
edenfunf:fix/protocol-fallback-port-warning
Apr 21, 2026
Merged

fix(install): warn when --allow-protocol-fallback reuses a custom port across schemes (#786)#789
danielmeppiel merged 5 commits intomicrosoft:mainfrom
edenfunf:fix/protocol-fallback-port-warning

Conversation

@edenfunf
Copy link
Copy Markdown
Contributor

Description

When --allow-protocol-fallback (or APM_ALLOW_PROTOCOL_FALLBACK=1) is enabled and a dependency carries a custom port, _build_repo_url passes the same dep_ref.port to both build_ssh_url and build_https_clone_url. On servers that serve each protocol on a different port (e.g. Bitbucket Datacenter: SSH 7999, HTTPS 7990), the off-protocol URL is wrong.

Strict-by-default (#778) already prevents this for the default path; the bug only surfaces when the user opts into cross-protocol fallback.

Per the maintainer's Option D direction on #786 (warning + docs, no schema change):

  • _clone_with_fallback: when the plan is permissive (fallback active) AND dep_ref.port is not None AND the planned attempts include both SSH and HTTPS, emit a default-visibility [!] warning before the first clone attempt, directing the user to pin the protocol with an explicit ssh:// or https:// URL. The warning routes through the existing _rich_warning idiom (same path as the current per-attempt fallback warning) and is deduped per (host, repo, port) on the GitHubPackageDownloader instance, so a single install run with multiple internal _clone_with_fallback calls (ref resolution + actual clone) still emits exactly one warning per dep.
  • --allow-protocol-fallback help text: one-line caveat that fallback reuses the same port across schemes.
  • docs/.../guides/dependencies.md + getting-started/authentication.md: short :::caution callouts under the transport-selection / fallback sections documenting the limitation and the pin-the-protocol workaround.
  • CHANGELOG.md: one entry under [Unreleased] / Changed.

Explicit non-goals (per the review panel's rejected options): no fallback: schema field, no ssh_port / https_port fields, no schema migration, no multi-port cascade. The existing test_https_attempt_preserves_same_port_across_protocols invariant at tests/unit/test_auth_scoping.py:423 is unchanged -- the warning is what makes that behaviour loud.

Fixes #786

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Maintenance / refactor

Testing

  • Tested locally
  • All existing tests pass
  • Added tests for new functionality (if applicable)

Manual verification

End-to-end against a custom-port dep on an unreachable host:

  • apm install --allow-protocol-fallback with ssh://git@host:7999/owner/repo.git -> [!] Custom port 7999 ... fires exactly once, before any clone attempt (confirmed single emission even though ref-resolution and actual-clone phases each call _clone_with_fallback).
  • Same dep without --allow-protocol-fallback (strict mode) -> no warning.
  • Dep without a custom port + --allow-protocol-fallback -> no warning.
  • apm install --help renders the updated --allow-protocol-fallback caveat.

Automated tests

New file tests/unit/test_protocol_fallback_warning.py (7 tests):

  • Warning fires for ssh:// URL with port + allow_fallback, citing the port and the Bitbucket Datacenter example verbatim.
  • Warning fires for https:// URL with port + allow_fallback.
  • Warning silent in strict mode even with a custom port (the #778 invariant).
  • Warning silent when dep_ref.port is None.
  • Warning fires exactly once per _clone_with_fallback call (not per planned attempt).
  • Warning deduped across repeated _clone_with_fallback calls for the same dep identity (regression guard for the ref-resolution + actual-clone multi-call path).
  • Warning fires again for a distinct (host, repo, port) identity (dedup is per-dep, not global).

Full unit suite: 4170 passed, 0 new failures.

…t across schemes (microsoft#786)

When cross-protocol fallback is enabled and a dependency carries a
custom port, `_build_repo_url` passes the same `dep_ref.port` to both
the SSH and HTTPS URL builders. On servers that serve each protocol on
a different port (e.g. Bitbucket Datacenter: SSH 7999, HTTPS 7990), the
off-protocol URL is wrong.

Strict-by-default (microsoft#778) already prevents this for the default path.
The bug only surfaces when the user opts into fallback via
`--allow-protocol-fallback` or `APM_ALLOW_PROTOCOL_FALLBACK=1`.

Per the maintainer's Option D direction (warning + docs, no schema
change), emit a default-visibility `[!]` warning before the first clone
attempt when a custom port is set and the plan includes both SSH and
HTTPS. The message tells the user to pin the protocol with an explicit
`ssh://` or `https://` URL. The warning is routed through the existing
`_rich_warning` idiom and deduped per (host, repo, port) on the
downloader instance, so a single install run with multiple internal
`_clone_with_fallback` calls (ref resolution + actual clone) still
emits exactly one warning per dep.

Docs callouts under the transport-selection and authentication guides
cover the limitation and the pin-the-protocol workaround; the
`--allow-protocol-fallback` help text picks up a one-line caveat.

No schema changes (`DependencyReference` / `LockedDependency` untouched,
no `ssh_port` / `https_port` / `fallback:` fields). The existing
`test_https_attempt_preserves_same_port_across_protocols` invariant is
unchanged — the warning is what makes that behaviour loud.
@edenfunf
Copy link
Copy Markdown
Contributor Author

@danielmeppiel -- ready for review when you have a moment.

This is the small PR you sketched on #786: the 30-50 line Option D change (warning + docs, no schema impact). It sits downstream of #778's strict-by-default plan without touching the test_https_attempt_preserves_same_port_across_protocols invariant, and is independent of #788 (which addresses #785's credential-resolution path).

One deviation worth flagging from your verdict text: the warning emission is deduped per (host, repo, port) on the GitHubPackageDownloader instance. Without dedup, the "once per dep" rule broke in practice because _clone_with_fallback is called multiple times for the same dep in a single install run (ref resolution clone + the actual dep clone). The unit test test_warning_fires_once_not_per_attempt only covered the single-call path; the dedup and its regression test (test_warning_dedup_across_multiple_clone_calls_for_same_dep) were added after end-to-end apm install surfaced the double emission.

Manual verification matrix is in the PR body. Happy to iterate on the wording or move the emission site to TransportSelector.select if you'd prefer -- I kept it in _clone_with_fallback so the logger stays out of the pure decision engine.

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 addresses issue #786 by making cross-protocol fallback with custom ports safer and more discoverable: when --allow-protocol-fallback is enabled and a dependency has a custom port, APM now warns that the same port will be reused across SSH/HTTPS attempts (which can be wrong on servers with different per-protocol ports).

Changes:

  • Emit a deduped [!] warning in _clone_with_fallback when fallback is permissive and both SSH+HTTPS attempts are planned for a dependency with a custom port.
  • Update apm install --allow-protocol-fallback help text with a custom-port caveat.
  • Add docs callouts + changelog entry and introduce a focused unit test suite for the warning behavior.
Show a summary per file
File Description
src/apm_cli/deps/github_downloader.py Adds per-dependency dedup + emits the new custom-port cross-protocol fallback warning.
src/apm_cli/commands/install.py Extends --allow-protocol-fallback help text with the port-reuse caveat.
tests/unit/test_protocol_fallback_warning.py New unit tests validating warning emission, strict-mode silence, and dedup behavior.
docs/src/content/docs/guides/dependencies.md Adds a caution block documenting the limitation and workaround.
docs/src/content/docs/getting-started/authentication.md Adds a caution block documenting the limitation and workaround.
CHANGELOG.md Adds an [Unreleased] / Changed entry describing the new warning + doc updates.

Copilot's findings

Comments suppressed due to low confidence (1)

src/apm_cli/deps/github_downloader.py:771

  • This warning advises users to "pin the protocol with an explicit ssh:// or https:// URL", but when --allow-protocol-fallback is enabled the TransportSelector intentionally ignores explicit schemes and still plans cross-protocol attempts. Reword the warning to state that avoiding cross-protocol port reuse requires disabling protocol fallback (strict mode), and then using an explicit scheme/URL that matches the intended port.
                _rich_warning(
                    f"Custom port {dep_port} is set on this dependency. "
                    f"Cross-protocol fallback will reuse the same port for the "
                    f"other scheme. If your server uses different ports per "
                    f"protocol (e.g., Bitbucket Datacenter: SSH 7999, HTTPS 7990), "
                    f"pin the protocol with an explicit ssh:// or https:// URL.",
                    symbol="warning",
                )
  • Files reviewed: 6/6 changed files
  • Comments generated: 5

# run calls _clone_with_fallback multiple times per dep (ref-resolution
# clone, then the actual dep clone). We want the warning exactly once
# per (host, repo, port) identity across all those calls.
self._fallback_port_warned: set = set()
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

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

self._fallback_port_warned is used as a set of (host, repo, port) tuples, but it is annotated as a plain set, which loses useful type information. Consider annotating it as a typed set (e.g., set[tuple[Optional[str], str, int]]) to improve readability and static checking.

This issue also appears on line 764 of the same file.

Suggested change
self._fallback_port_warned: set = set()
self._fallback_port_warned: set[tuple[Optional[str], str, int]] = set()

Copilot uses AI. Check for mistakes.
Comment thread src/apm_cli/commands/install.py Outdated
is_flag=True,
default=False,
help="Restore the legacy permissive cross-protocol fallback chain (escape hatch for migrating users; also: APM_ALLOW_PROTOCOL_FALLBACK=1).",
help="Restore the legacy permissive cross-protocol fallback chain (escape hatch for migrating users; also: APM_ALLOW_PROTOCOL_FALLBACK=1). Caveat: fallback reuses the same port across schemes, so pin the protocol with an explicit ssh:// or https:// URL on servers that use different SSH and HTTPS ports.",
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

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

This help text suggests that using an explicit ssh:// or https:// URL will "pin" the protocol even when --allow-protocol-fallback is enabled. In the current transport selection logic, enabling protocol fallback intentionally allows cross-protocol attempts even for explicit-scheme URLs. Please adjust the help text to clarify that pinning requires not using --allow-protocol-fallback (strict mode), and then using an explicit scheme.

Suggested change
help="Restore the legacy permissive cross-protocol fallback chain (escape hatch for migrating users; also: APM_ALLOW_PROTOCOL_FALLBACK=1). Caveat: fallback reuses the same port across schemes, so pin the protocol with an explicit ssh:// or https:// URL on servers that use different SSH and HTTPS ports.",
help="Restore the legacy permissive cross-protocol fallback chain (escape hatch for migrating users; also: APM_ALLOW_PROTOCOL_FALLBACK=1). Caveat: fallback reuses the same port across schemes, so on servers that use different SSH and HTTPS ports, pin the protocol by staying in strict mode (do not use --allow-protocol-fallback) and using an explicit ssh:// or https:// URL.",

Copilot uses AI. Check for mistakes.
Comment on lines +488 to +494
Fallback reuses `dep_ref.port` for both schemes. On servers that use
different ports per protocol (e.g. Bitbucket Datacenter: SSH 7999,
HTTPS 7990), the off-protocol URL will be wrong. APM emits a `[!]`
warning before the first clone attempt when a custom port is set and
fallback is enabled. The fix is to pin the protocol with an explicit
`ssh://...` or `https://...` URL in `apm.yml`, which bypasses the
fallback chain entirely.
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

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

This caution claims that using an explicit ssh://... or https://... URL "bypasses the fallback chain entirely", but when --allow-protocol-fallback is enabled the transport selector still permits cross-protocol attempts even for explicit-scheme URLs. Please revise this section to reflect that explicit URLs bypass cross-protocol fallback only in strict mode (fallback disabled), and consider avoiding the internal name dep_ref.port in user docs (just describe it as the dependency's custom port).

Suggested change
Fallback reuses `dep_ref.port` for both schemes. On servers that use
different ports per protocol (e.g. Bitbucket Datacenter: SSH 7999,
HTTPS 7990), the off-protocol URL will be wrong. APM emits a `[!]`
warning before the first clone attempt when a custom port is set and
fallback is enabled. The fix is to pin the protocol with an explicit
`ssh://...` or `https://...` URL in `apm.yml`, which bypasses the
fallback chain entirely.
Fallback reuses the dependency's custom port for both schemes. On
servers that use different ports per protocol (e.g. Bitbucket
Datacenter: SSH 7999, HTTPS 7990), the off-protocol URL will be
wrong. APM emits a `[!]` warning before the first clone attempt when
a custom port is set and fallback is enabled. To avoid cross-protocol
retries, pin the protocol with an explicit `ssh://...` or
`https://...` URL in `apm.yml` and leave fallback disabled. If
fallback is enabled, APM can still try the other protocol.

Copilot uses AI. Check for mistakes.
Comment on lines +324 to +329
dependency's port on both SSH and HTTPS attempts. On servers that use
different ports per protocol (e.g. Bitbucket Datacenter: SSH 7999,
HTTPS 7990), the off-protocol URL will be wrong. APM emits a `[!]`
warning before the first clone attempt to flag this. Pin the protocol
with an explicit `ssh://...` or `https://...` URL in `apm.yml` to
bypass fallback entirely.
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

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

This caution suggests that pinning with an explicit ssh://... or https://... URL will bypass fallback, but with --allow-protocol-fallback enabled explicit schemes are not strict and cross-protocol attempts may still occur. Please update the wording to clarify that pinning requires strict mode (fallback disabled), and avoid referencing internal implementation names in docs (e.g., dep_ref.port).

Suggested change
dependency's port on both SSH and HTTPS attempts. On servers that use
different ports per protocol (e.g. Bitbucket Datacenter: SSH 7999,
HTTPS 7990), the off-protocol URL will be wrong. APM emits a `[!]`
warning before the first clone attempt to flag this. Pin the protocol
with an explicit `ssh://...` or `https://...` URL in `apm.yml` to
bypass fallback entirely.
dependency URL's port on both SSH and HTTPS attempts. On servers that
use different ports per protocol (e.g. Bitbucket Datacenter: SSH 7999,
HTTPS 7990), the off-protocol URL will be wrong. APM emits a `[!]`
warning before the first clone attempt to flag this. To strictly pin a
dependency to `ssh://...` or `https://...`, leave fallback disabled. If
`--allow-protocol-fallback` is enabled, APM may still try the other
protocol even when the dependency URL uses an explicit scheme.

Copilot uses AI. Check for mistakes.
Comment thread CHANGELOG.md Outdated

### Changed

- `apm install --allow-protocol-fallback` now emits a `[!]` warning before the first clone attempt when a dependency has a custom port and both SSH and HTTPS attempts are planned, explaining that fallback reuses the same port across schemes and suggesting a pinned `ssh://` or `https://` URL for servers that use different ports per protocol (e.g. Bitbucket Datacenter: SSH 7999, HTTPS 7990). Docs note the limitation in the transport-selection and authentication guides. Closes #786
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

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

The new changelog bullet does not follow the established entry format: changelog entries consistently end with the PR number in parentheses (e.g. "(#778)" above). Update this line to end with "(#786)" and avoid relying on "Closes #786" (issue reference) as the sole trailer.

Suggested change
- `apm install --allow-protocol-fallback` now emits a `[!]` warning before the first clone attempt when a dependency has a custom port and both SSH and HTTPS attempts are planned, explaining that fallback reuses the same port across schemes and suggesting a pinned `ssh://` or `https://` URL for servers that use different ports per protocol (e.g. Bitbucket Datacenter: SSH 7999, HTTPS 7990). Docs note the limitation in the transport-selection and authentication guides. Closes #786
- `apm install --allow-protocol-fallback` now emits a `[!]` warning before the first clone attempt when a dependency has a custom port and both SSH and HTTPS attempts are planned, explaining that fallback reuses the same port across schemes and suggesting a pinned `ssh://` or `https://` URL for servers that use different ports per protocol (e.g. Bitbucket Datacenter: SSH 7999, HTTPS 7990). Docs note the limitation in the transport-selection and authentication guides. (#786)

Copilot uses AI. Check for mistakes.
@danielmeppiel
Copy link
Copy Markdown
Collaborator

APM Review Panel — PR #789

[>] Verdict: APPROVE-WITH-FIXES. Land after a single polish pass; no re-queue specialist review needed.

Four specialists (python-architect, cli-logging-expert, auth-expert, devx-ux-expert) reviewed. Two approved cleanly; two flagged the same warning-text gap. Convergence is on a quick, mechanical polish pass.

What's excellent

  • Architecture is right. Dedup on (dep_host, repo_url_base, dep_port) at the per-install downloader is the correct scope, and the trigger placement before the attempt loop is clean.
  • Threat model honest. Framed as UX/mechanical, not a security claim. No credential or URL-construction surface touched. Strict-mode silence verified.
  • Test coverage proportional. 7 tests including strict-mode silence, dedup-across-calls, and re-fire-for-different-dep. Right shape.
  • Story is one CHANGELOG line that reinforces "APM tells you when it's compromising on your behalf."

Findings

  1. [x] BLOCKER — warning text is unactionable. "this dependency" with N deps in flight = noise. Name the offender; repo_url_base and dep_host are already in scope. Target shape:

    [!] Custom port 7999 on bitbucket.example.com/project/repo: SSH unreachable, falling back to HTTPS.
        Pin the URL scheme, or drop --allow-protocol-fallback to fail fast.
        See: <docs URL to the :::caution block>
    

    This single edit absorbs the cli-logging blocker, the devx escape-hatch nit, and the devx doc-link nit in one line. Both :::caution blocks already exist as the link target.

  2. [x] BLOCKER — cli-commands.md:101 stale. Per the cli.instructions contract, a CLI behavior change without the reference doc updated in the same PR is incomplete. The --allow-protocol-fallback description must mention the new pre-attempt port warning, not just the per-retry one. Non-negotiable; same-PR fix.

  3. [i] Follow-up — dedup key case sensitivity. GitHub/Bitbucket hosts are case-insensitive; key is case-sensitive. Worst case: a duplicate warning. Add a # NOTE: case-sensitive comment now, file an issue, move on.

  4. [i] Follow-up — _fallback_port_warned thread-safety (auth-expert). Plain set on shared downloader. Benign race, worst case duplicate warning. CLI is single-threaded today; revisit if/when we parallelize installs.

  5. [i] Nice-to-have — CHANGELOG sentence is ~80 words. Split into "Added" + one-line behavior note. Author discretion.

Merge call

Author addresses (1) + (2) inline — both are mechanical, ~15 minutes. No re-review needed; maintainer can self-confirm against the warning shape above and the cli-commands.md diff. (3)–(5) become follow-up issues, linked from the merge commit. Then merge. [+]


Growth angle

Reviewed via the apm-review-panel skill: python-architect, cli-logging-expert, auth-expert, devx-ux-expert; synthesized by apm-ceo with oss-growth-hacker side-channel.

Copy link
Copy Markdown
Collaborator

@danielmeppiel danielmeppiel left a comment

Choose a reason for hiding this comment

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

[x] BLOCKER — warning text is unactionable. "this dependency" with N deps in flight = noise. Name the offender; repo_url_base and dep_host are already in scope. Target shape:

[!] Custom port 7999 on bitbucket.example.com/project/repo: SSH unreachable, falling back to HTTPS.
    Pin the URL scheme, or drop --allow-protocol-fallback to fail fast.
    See: <docs URL to the :::caution block>

This single edit absorbs the cli-logging blocker, the devx escape-hatch nit, and the devx doc-link nit in one line. Both :::caution blocks already exist as the link target.

[x] BLOCKER — cli-commands.md:101 stale. Per the cli.instructions contract, a CLI behavior change without the reference doc updated in the same PR is incomplete. The --allow-protocol-fallback description must mention the new pre-attempt port warning, not just the per-retry one. Non-negotiable; same-PR fix.

Polish pass from maintainer review of the cross-protocol-fallback port
warning. No functional surface change; message content, docs accuracy,
and CLI reference sync only.

Warning text (blocker 1 -- unactionable wording):
- Name the offender: "Custom port {port} on {host}/{repo}" instead of
  "this dependency" (ambiguous under N-dep installs).
- Name the planned initial + fallback schemes so the user sees which
  scheme will be tried first and which one the port will be reused on.
- Offer both remediations in one line: "Pin the URL scheme, or drop
  --allow-protocol-fallback to fail fast."
- Link to the public docs anchor under the dependencies guide where
  the corresponding :::caution block lives.
- Add a NOTE that the (host, repo, port) dedup key is case-sensitive;
  follow-up tracks case-insensitive normalization.

Reference doc sync (blocker 2 -- cli-commands.md:101 stale):
- Extend the `--allow-protocol-fallback` entry in the CLI reference to
  describe the new pre-attempt port warning, not only the existing
  per-retry warning. Required by the cli.instructions contract for CLI
  behaviour changes.

Docs accuracy (Copilot review findings):
- dependencies.md / authentication.md cautions previously claimed an
  explicit `ssh://` or `https://` URL "bypasses the fallback chain
  entirely". False under `--allow-protocol-fallback`: explicit URLs
  still go through the permissive chain (transport_selection.py:255).
  Both cautions now state the invariant correctly -- pinning only
  hard-stops cross-protocol retries in strict mode.
- Drop the internal `dep_ref.port` identifier from user docs; refer to
  "the dependency's custom port" / "the dependency URL's port".
- `apm install --allow-protocol-fallback` help text picks up the same
  correction (pin AND omit the flag).

CHANGELOG formatting:
- End the entry with "Closes microsoft#786 (microsoft#789)" to match the convention used
  by the surrounding BREAKING and Fixed entries ("Closes microsoft#328 (microsoft#778)",
  "(microsoft#780)", "(microsoft#784)") instead of the bare "Closes microsoft#786" trailer.

Tests:
- Refresh assertions to cover the new shape: host named, repo named,
  both schemes named, both remediations present, docs URL present.
- Drop assertions on the removed Bitbucket-Datacenter example string
  and literal "ssh://"/"https://" substrings (no longer in the terse
  three-line message).
- Existing dedup / strict-mode-silent / no-port-silent / distinct-dep
  coverage untouched.

Full unit suite: 4170 passed. Manual `apm install` verified the four
scenarios (port + fallback, strict + port, fallback + no-port, --help).
@edenfunf
Copy link
Copy Markdown
Contributor Author

edenfunf commented Apr 21, 2026

@danielmeppiel Polish pass pushed (9a0b022). All comments addressed; CI 5/5 green on the new head.

Maintainer blockers

  • Warning text (BLOCKER 1). New 3-line shape names the offender, both planned schemes, both remediations, and links to the docs anchor. Pre-emptive placement kept (per your "trigger placement before the attempt loop is clean" call), so the tense shifted from "SSH unreachable, falling back to HTTPS" (post-hoc) to "if HTTPS fails, APM will retry over SSH" (accurate at emission time). Live output:

    [!] Custom port 7999 on nonexistent.invalid/project/repo: if HTTPS fails, APM will retry over SSH on the same port.
        Pin the URL scheme, or drop --allow-protocol-fallback to fail fast.
        See: https://microsoft.github.io/apm/guides/dependencies/#restoring-the-legacy-permissive-chain
    

    The initial/fallback schemes are derived from plan.attempts at emission time, so whichever protocol the selector actually plans first (HTTPS in the test fixtures' no-token case, SSH when the user has a PAT and an insteadOf rewrite, etc.) is what the user sees.

  • cli-commands.md:101 (BLOCKER 2). --allow-protocol-fallback entry now describes both warnings: the existing per-retry one (unchanged) and the new one-shot pre-attempt port warning, with the same "omit this flag and pin" remediation framing used everywhere else.

Copilot findings

  • Integrate copilot runtime #2 help text. Rewritten to "omit this flag and pin the dependency with an explicit ssh:// or https:// URL". The old wording misled by suggesting the URL alone pins under fallback.
  • Will there be MCP coverage? #3 / Add ARM64 Linux support to CI/CD pipeline #4 docs cautions. Both caution blocks now state the invariant correctly: "To avoid cross-protocol retries, leave --allow-protocol-fallback disabled (strict mode) and pin the dependency with an explicit ssh://... or https://... URL. If fallback is enabled, APM may still try the other protocol even when the URL uses an explicit scheme." The internal dep_ref.port identifier was dropped from the user-facing copy.
  • apm install <my-apm-package-repo> #5 CHANGELOG trailer. Now ends with Closes #786 (#789), matching the surrounding Closes #328 (#778) convention and the bare (#780) / (#784) style.
  • Why do we need a GitHub token? #1 set type annotation. Kept plain set. The other instance vars in GitHubPackageDownloader (git_env, _transport_selector, _allow_fallback, _protocol_pref) are unannotated, and the file mixes typing.List/Dict imports with un-annotated locals; adding set[tuple[Optional[str], str, int]] alone would diverge from both styles. The warn-key construction three lines below (warn_key = (dep_host, repo_url_base, dep_port)) makes the tuple shape self-evident at the use site. Happy to fold this in if you'd rather the module move toward PEP 585 annotations uniformly -- just didn't want to kick off that cleanup inside this PR.

Follow-ups (to become issues linked from the merge commit, per your verdict)

  • Dedup-key case-sensitivity. Now has an inline NOTE at the warn-key construction site. Will open an issue for case-insensitive host normalization.
  • _fallback_port_warned thread-safety. CLI is single-threaded today and the worst-case race is a duplicate warning; will open an issue to revisit when/if installs parallelize.
  • CHANGELOG sentence length. Compressed slightly in this push (one declarative sentence + one-sentence motivation for the BB-DC mismatch scenario); left further splitting at your discretion.

Verification

  • Full unit suite: 4170 passed. Refreshed assertions in tests/unit/test_protocol_fallback_warning.py cover host naming, repo naming, both schemes, both remediations, and the docs URL substring. Dedup-across-calls and distinct-dep regression tests untouched.
  • Manual apm install against ssh://git@nonexistent.invalid:7999/project/repo.git with --allow-protocol-fallback: one warning, exact shape above. Without the flag: no warning. No-port dep with the flag: no warning. apm install --help: updated caveat renders.
  • CI on 9a0b022: Build / Integration / Release Validation / Smoke Test / CLA all green.

Ready for your re-read.

Copy link
Copy Markdown
Collaborator

@danielmeppiel danielmeppiel left a comment

Choose a reason for hiding this comment

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

APM Review Panel -- PR #789 (re-review)

[+] APPROVE. All five blockers and copilot findings from my prior CHANGES_REQUESTED are addressed in 9a0b022. CI green on the new head.

Verified

  • Warning text (B1): names dep + port + both planned schemes + both remediations + docs anchor. The tense shift ("if HTTPS fails, APM will retry over SSH") correctly reflects pre-attempt emission. Plan-derived scheme order means the user always sees what the selector will actually do.
  • cli-commands.md (B2): documents both warnings with consistent "omit + pin" framing.
  • Help text + caution blocks (#2, #3, #4): correctly state the invariant that scheme-pinning alone does not prevent retries under fallback. Internal dep_ref.port removed from user-facing copy.
  • CHANGELOG trailer (#5): Closes #786 (#789) matches house convention.
  • Test refresh: assertions cover host+repo names, both schemes, both remediations, docs URL substring, dedup-across-calls, distinct-dep regression.

Accepted as-is

  • set type annotation (#1): style argument is reasonable -- the surrounding instance vars are unannotated and the warn-key construction three lines below makes the tuple shape obvious. Not worth diverging the file's style for one line. Module-wide PEP 585 cleanup is a separate PR.

Follow-up issues to file post-merge (per your verdict)

  • Case-insensitive host normalization in dedup key.
  • Thread-safety of _fallback_port_warned if/when installs parallelize.

Nice work, @edenfunf -- this is exactly the surgical Option D shape #786 asked for. Strict-by-default keeps holding the line; the warning makes the one remaining sharp edge loud.

Re-reviewed via apm-review-panel skill: prior CHANGES_REQUESTED items verified individually against 9a0b022 diff.

@danielmeppiel danielmeppiel enabled auto-merge April 21, 2026 06:15
Comment thread tests/unit/test_protocol_fallback_warning.py Fixed
… format

CodeQL flagged the bare "bitbucket.example.com" in msg substring check
as "Incomplete URL substring sanitization" -- a false positive here
(this is a test, not URL validation), but the fix strengthens the
assertion at the same time.

Replace `assert "{host}" in msg` + `assert "{repo}" in msg` with a
single anchored check against the exact emitted prefix:

    assert "Custom port {port} on {host}/{repo}:" in msg

Same treatment for the docs URL: `"microsoft.github.io/apm/" in msg`
becomes `"See: https://microsoft.github.io/apm/" in msg`, anchoring on
the `See: ` emit prefix.

Two wins: the CodeQL finding goes away (no bare hostname substring
check), and the tests now document the exact wire format so a
regression that re-splits host and repo, or changes the "Custom port
... on ..." phrasing, will fail loudly instead of silently passing via
substring containment.
auto-merge was automatically disabled April 21, 2026 06:39

Head branch was pushed to by a user without write access

@edenfunf
Copy link
Copy Markdown
Contributor Author

Follow-up push (d032ba0, rebased onto your main merge be1aa2f):

CodeQL flagged "bitbucket.example.com" in msg in the warning tests as Incomplete URL substring sanitization -- a false positive for a test assertion, but fixing it the right way also strengthens the test. Swapped the bare host/URL substring checks for format-anchored assertions:

# before
assert "bitbucket.example.com" in msg
assert "project/repo" in msg
assert "microsoft.github.io/apm/" in msg

# after
assert "Custom port 7999 on bitbucket.example.com/project/repo:" in msg
assert "See: https://microsoft.github.io/apm/" in msg

Two benefits in one:

  1. CodeQL finding clears -- no bare hostname substring check.
  2. Tests now document the exact wire format. A regression that splits host and repo, drops the on preamble, or changes the See: prefix fails loudly instead of silently passing via substring containment.

Same treatment for the HTTPS fixture (git.company.internal/team/repo). test_warning_fires_again_for_different_dep keeps its bare port-number checks ("7999" in m, "8443" in m) because those aren't URL-shaped and CodeQL doesn't flag them.

Target run: tests/unit/test_protocol_fallback_warning.py 7/7 + tests/unit/test_auth_scoping.py 48/48 green. CI running on the new head.

@danielmeppiel danielmeppiel self-requested a review April 21, 2026 06:46
@danielmeppiel
Copy link
Copy Markdown
Collaborator

@edenfunf urllib.parse is a better way

@danielmeppiel danielmeppiel enabled auto-merge April 21, 2026 06:48
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.

[FEATURE] Cross-protocol fallback with custom port: port mismatch across SSH/HTTPS

4 participants