You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
APM currently authenticates to Azure DevOps using only Personal Access Tokens via the ADO_APM_PAT environment variable. This is a hard blocker for users in environments where:
Org policy restricts or prohibits PAT creation (common in security-conscious enterprises)
Authentication is performed via service principals or managed identities (Entra ID / AAD)
CI/CD pipelines use Workload Identity Federation (WIF) instead of long-lived secrets
GitHub Actions workflows authenticate to Azure via OIDC federated credentials
This issue proposes adding Entra ID (AAD) bearer token authentication as a non-disruptive fallback, picked up automatically from an active az CLI session. PAT users see no behavior change. Users who cannot or do not use PATs gain a zero-config path that aligns with the broader industry move away from long-lived shared secrets.
Problem
Today, every ADO code path in APM (install, outdated, dependency validation, ref listing) routes through ADO_APM_PAT:
src/apm_cli/core/token_manager.py:49 -> 'ado_modules': ['ADO_APM_PAT'] is the entire token chain
src/apm_cli/core/auth.py excludes ADO from per-org env-var lookup, credential-fill fallback, and credential-fill retry on failure
src/apm_cli/utils/github_host.py:188build_ado_https_clone_url() embeds the token in the clone URL
When an organization disables PAT creation, or when an automated workload runs without a PAT (only an AAD identity), apm install fails with no recovery path. The user-facing error tells them to set a variable they cannot generate.
ADO's REST and Git endpoints already accept AAD bearer tokens anywhere a PAT is accepted; we just don't acquire or send them.
Goals
Add an automatic AAD bearer-token path for ADO that requires zero configuration when an az CLI session is active.
Preserve the existing PAT path with no behavior change for current users.
Keep the dependency footprint flat (no new Python packages).
Make CI/CD usage trivial: dropping APM into a step that follows azure/login@v2 (GitHub Actions) or AzureCLI@2 (Azure Pipelines with WIF) should "just work".
Improve error messages so they adapt to what is actually available on the user's machine.
Non-goals
Adding an apm auth command surface (out of scope; package managers don't manage credentials).
Adding a azure-identity or MSAL Python dependency (rejected during design — too heavy for the marginal coverage gain).
Auto-detecting employer / organization affinity to pick a path (rejected — APM is not an identity-aware tool).
Per-org ADO token env vars (e.g. ADO_APM_PAT_{ORG}). Defer until demand materializes.
Running interactive auth flows (az login) on behalf of the user.
AAD bearer token from az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 (if az is on PATH and an active session exists)
Auth error with adaptive guidance based on observable signals
499b84ac-1321-427f-aa17-267ca6975798 is the well-known Entra ID resource ID for Azure DevOps. The acquired token is scoped to ADO and not usable against other Azure resources.
Architecture
flowchart TD
A[Dependency Reference] --> AH{Host is dev.azure.com?}
AH -->|No| GH[GitHub auth chain unchanged]
AH -->|Yes| AD1{ADO_APM_PAT set?}
AD1 -->|Yes| AD2[Use PAT - Basic auth]
AD1 -->|No| AD3{az on PATH and logged in?}
AD3 -->|Yes| AD4[az account get-access-token]
AD3 -->|No| AD5[Auth error - guide to PAT or az login]
AD4 --> AD6{ADO accepts bearer?}
AD6 -->|Yes| J[Success]
AD6 -->|No - wrong tenant| AD7[Error: show tenant ID, suggest az login --tenant]
AD2 --> AD8{ADO accepts PAT?}
AD8 -->|Yes| J
AD8 -->|No - az available| AD4
AD8 -->|No - no az| AD9[Auth error - PAT rejected]
Zero new dependencies. azure-identity pulls MSAL, cryptography, requests, and a multi-MB transitive tree.
Aligns with how every recommended CI integration sets up auth (azure/login@v2, AzureCLI@2) — both leave az logged in for downstream steps.
Cold start latency ~200-500ms; cached after first call within the process.
The narrow case where azure-identity would beat subprocess (a containerized workload with WIF env vars injected but no az login) has a clean escape hatch via the existing PAT env var (see "Headless escape hatch" below).
Bearer transport
For git clone and git ls-remote:
Use git -c http.extraheader="Authorization: Bearer <jwt>". AAD bearer tokens are JWTs of ~1-2KB, which exceed safe URL-embedding limits and would leak into shell history, process tables, and git's own logs.
Security: the header MUST be injected via environment variables, not via the -c argument, to avoid leaking the bearer into the OS process table. Use git's standard GIT_CONFIG_COUNT mechanism:
The bearer path is indistinguishable from the PAT path at default verbosity. No "Using AAD bearer" banner. Users asked for packages, not an auth status report.
apm install --verbose:
[>] Resolving 3 dependencies...
[i] dev.azure.com -- no ADO_APM_PAT; using bearer from az cli
[i] github.com -- token from GH_TOKEN
[+] dev.azure.com/contoso/platform/_git/coding-standards (v2.1.0, locked)
...
[*] Installed 3 packages
Stale PAT, silent fallback to bearer (default verbosity): identical to the happy path, with one diagnostic warning at the end of the run:
...
[*] Installed 3 packages
[!] ADO_APM_PAT was rejected for dev.azure.com (HTTP 401); fell back to az cli bearer.
Consider unsetting the stale variable.
Error message redesign
Four scenarios, distinguished by observable signals (shutil.which("az"), az exit code, ADO HTTP status). Each error stays under an 8-line budget and gives one or two concrete next actions.
Case 1 -- No az on PATH, no PAT:
[x] Authentication failed for dev.azure.com/contoso/platform/_git/standards.
Azure DevOps requires authentication. Set a Personal Access Token:
export ADO_APM_PAT=your_token
Create one at https://dev.azure.com/contoso/_usersSettings/tokens
with Code (Read) scope.
Docs: https://aka.ms/apm-ado-auth
Case 2 -- az returns token, ADO rejects (wrong tenant):
[x] Authentication failed for dev.azure.com/contoso/platform/_git/standards.
Your az cli session (tenant: 72f988bf-...) returned a bearer token,
but Azure DevOps rejected it (HTTP 401).
Check that you are signed into the correct tenant:
az account show
az login --tenant <correct-tenant-id>
Docs: https://aka.ms/apm-ado-auth
(Tenant ID pulled from az account show --query tenantId -o tsv.)
Case 3 -- az present, not logged in, no PAT:
[x] Authentication failed for dev.azure.com/contoso/platform/_git/standards.
Azure DevOps requires authentication. You have two options:
1. Sign in with Azure CLI (recommended for Entra ID users):
az login
apm install # retry -- no env var needed
2. Use a Personal Access Token:
export ADO_APM_PAT=your_token
Docs: https://aka.ms/apm-ado-auth
Case 4 -- PAT rejected AND bearer rejected:
[x] Authentication failed for dev.azure.com/contoso/platform/_git/standards.
ADO_APM_PAT was rejected (HTTP 401) -- the token may be expired or revoked.
az cli bearer was also rejected (HTTP 403).
To fix:
1. Unset the stale PAT: unset ADO_APM_PAT
2. Re-authenticate: az login
3. Retry: apm install
Docs: https://aka.ms/apm-ado-auth
Discoverability decision
No new commands. No apm auth login, no apm auth status. npm, pip, and cargo do not have auth subcommands; APM should not either. The redesigned error messages are the discovery surface. apm install --verbose already shows resolved token source via the existing CommandLogger.auth_resolved() API.
If apm doctor is introduced in the future as a general health check, ADO auth becomes one of many checks — it should not be the trigger for adding the command.
Env var decision
No new environment variables. Bearer tokens are short-lived (~1h); asking users to manually export APM_ADO_BEARER=... would expire and fail cryptically. The whole point is that APM acquires a fresh token per invocation. ADO_APM_PAT remains as the explicit-credential escape hatch.
Population detection decision
APM does not detect employer or organization affinity. The same resolution chain runs for every user; behavior adapts to observable signals (is az available? did it return a token? did ADO accept it?), not to identity. The only host-aware logic is the existing host classification (dev.azure.com and *.visualstudio.com).
Code changes (file-level enumeration)
File
Change
src/apm_cli/core/token_manager.py (line 49)
Extend 'ado_modules' chain entries to include the bearer resolver.
src/apm_cli/core/auth.py
New _resolve_ado_bearer() helper. Remove ADO exclusions at lines 311, 325, 454, 468 (the bearer fallback replaces credential-fill exclusion logic for ADO). Rewrite build_error_context() ADO branch (~lines 406-410) with adaptive logic for the four error cases above.
src/apm_cli/utils/github_host.py (line 188)
If unifying on http.extraheader: refactor build_ado_https_clone_url() to return a token-less URL plus header material. Otherwise: keep PAT URL-embedding and add a parallel function for bearer.
Thread bearer through git clone subprocess via GIT_CONFIG_COUNT/KEY_0/VALUE_0 env vars on the spawned process.
src/apm_cli/core/azure_cli.py (new, small)
az detection, token acquisition, error classification, in-memory cache.
Implementation risk to validate first
GitPython's Repo.clone_from() accepts multi_options=[...] for arbitrary -c flags, but for security we want to use GIT_CONFIG_COUNT/KEY/VALUE env vars instead. Spike (~30 min): verify GitPython propagates the spawning process env to the underlying git subprocess (it should, since it uses subprocess.Popen under the hood, but confirm). If it does not, drop GitPython for ADO clones and shell out to git directly.
Documentation changes
(Generated by the doc-writer agent during design; line numbers verified against main.)
Lines 11-17 (resolution chain prose): extend with an ADO-specific chain bullet alongside the GitHub-like chain.
Lines 25-33 (token lookup table): replace the line-33 prose For Azure DevOps, the only token source is ADO_APM_PAT. Add a row for ADO_APM_PAT (priority 1, ADO-only) and a row for az account get-access-token (priority 2, ADO-only, AAD bearer).
Lines 127-147 (## Azure DevOps): rewrite section to cover both PAT and az login paths side by side. State precedence: PAT first, az bearer fallback. Note the silent-fallback diagnostic warning when PAT is stale and az succeeds.
Lines 149-158 (package source behavior table, ADO row at line 157): change ADO_APM_PAT only -> ADO_APM_PAT -> az CLI bearer (AAD).
Lines 246-267 (mermaid flowchart TD): replace with the new chart from the Architecture section above.
New sub-section under ## Troubleshooting (insert after line 232, before ### Diagnosing auth failures): ### Azure DevOps auth failures covering the four error scenarios.
Lines 43-52 (## Azure DevOps (ADO)): replace with mirror of the rewritten doc-site section. Terse — link to the doc-site for the full troubleshooting flow rather than duplicating the four error cases.
docs/src/content/docs/integrations/ci-cd.md
Lines 77-89 (Azure Pipelines snippet): currently hardcodes ADO_APM_PAT: $(ADO_PAT). Add a second snippet showing the WIF pattern with AzureCLI@2 and noADO_APM_PAT env var. State that APM auto-detects the az session.
Add a GitHub Actions snippet showing azure/login@v2 -> apm install with no token env, for ADO deps in GitHub-hosted CI.
docs/src/content/docs/enterprise/security.md
Line 251 (token handling table): update Azure DevOps row -> ADO_APM_PAT, then AAD bearer via az CLI.
Line 255: extend "scoped to their git host" sentence to cover the bearer token: AAD bearer is acquired with the ADO resource ID 499b84ac-... and only sent to ADO. Add one sentence on process-table safety (GIT_CONFIG_COUNT/KEY/VALUE injection, never CLI-arg).
.apm/agents/auth-expert.agent.md
Line 26-30 (Core Knowledge): add bullet on ADO Basic-PAT vs AAD-bearer dual flow and the ADO resource GUID.
Line 35 (token precedence): add ADO chain as a separate bulleted line: ADO precedence: ADO_APM_PAT -> az account get-access-token (AAD bearer) -> error.
Line 56 (Common Pitfalls): extend "ADO uses Basic auth..." pitfall to note bearer flow uses Authorization: Bearer <jwt>, not Basic; do not mix the two. Add new pitfall: stale ADO_APM_PAT shadows a working az session unless explicitly unset.
.apm/skills/auth/SKILL.md
Line 5 (frontmatter description): add az CLI bearer fallback so the skill activates on related code.
Line 16 (activation triggers): add az account get-access-token.
(Mirror copies under .github/skills/auth/SKILL.md and .github/agents/auth-expert.agent.md are regenerated by apm install --target copilot per the dogfood pattern; updating .apm/ source is sufficient if the regen-and-commit step happens in the implementation PR.)
CHANGELOG.md
Add entry under ## [Unreleased] -> ### Added:
Azure DevOps authentication via Azure CLI: apm install now automatically acquires an Entra ID bearer token from az account get-access-token when no ADO_APM_PAT is set. Users with an active az login session can install ADO-hosted packages without configuring any environment variable. If ADO_APM_PAT is set but rejected (expired or revoked), APM falls back to the az bearer automatically. Error messages for ADO auth failures now adapt to whether az is available. Works inside azure/login@v2 and AzureCLI@2 CI contexts with no token env var required.
Acceptance test additions (out of scope for doc-only changes; tracked here for the implementation PR)
.github/workflows/auth-acceptance.yml (lines 76, 82): add a test mode exercising the AAD path using azure/login@v2 with WIF and no ADO_APM_PAT.
.github/workflows/ci-integration.yml (lines 152, 203): same pattern.
Open questions for the implementer
Unify on http.extraheader for both PAT and bearer, or keep URL-embedding for PAT? Recommended: unify. Cleaner code path, removes process-table leakage for the PAT path too, and makes the JWT escape hatch reliable. Trade-off: a slightly larger blast radius for the change.
Token cache scope. In-memory per process (no disk cache) is the recommended default. Confirm there is no scenario where APM spawns short-lived child processes that would re-pay the ~200-500ms cold start unnecessarily.
Fallback from PAT to bearer on 401. Recommended: fall back silently, surface a [!] warning in the end-of-run diagnostic summary. Confirm this matches the existing DiagnosticCollector UX patterns.
Acceptance criteria
apm install with an ADO dep and no ADO_APM_PAT set, but az login active, succeeds with no auth-related output at default verbosity.
apm install --verbose shows the token source resolved (bearer from az cli).
apm install with a stale ADO_APM_PAT (rejected with 401) and active az login succeeds, with one [!] diagnostic warning at the end.
apm install inside a GitHub Actions step that follows azure/login@v2 succeeds with no ADO_APM_PAT env var declared.
apm install inside an Azure Pipelines AzureCLI@2 task succeeds with no ADO_APM_PAT env var declared.
apm install with no PAT and no az on PATH produces the Case 1 error message.
apm install with az logged into a tenant that has no ADO access produces the Case 2 error message including the tenant ID.
apm install with az installed but not logged in, and no PAT, produces the Case 3 error message.
No new Python dependencies added.
Bearer token is never present on the git command line (verified by inspecting ps-equivalent during a clone).
All existing PAT-based tests continue to pass with no behavioral change.
Documentation surfaces enumerated above are updated in the same PR.
CHANGELOG entry added under ## [Unreleased] -> ### Added.
Summary
APM currently authenticates to Azure DevOps using only Personal Access Tokens via the
ADO_APM_PATenvironment variable. This is a hard blocker for users in environments where:This issue proposes adding Entra ID (AAD) bearer token authentication as a non-disruptive fallback, picked up automatically from an active
azCLI session. PAT users see no behavior change. Users who cannot or do not use PATs gain a zero-config path that aligns with the broader industry move away from long-lived shared secrets.Problem
Today, every ADO code path in APM (
install,outdated, dependency validation, ref listing) routes throughADO_APM_PAT:src/apm_cli/core/token_manager.py:49->'ado_modules': ['ADO_APM_PAT']is the entire token chainsrc/apm_cli/core/auth.pyexcludes ADO from per-org env-var lookup, credential-fill fallback, and credential-fill retry on failuresrc/apm_cli/utils/github_host.py:188build_ado_https_clone_url()embeds the token in the clone URLWhen an organization disables PAT creation, or when an automated workload runs without a PAT (only an AAD identity),
apm installfails with no recovery path. The user-facing error tells them to set a variable they cannot generate.ADO's REST and Git endpoints already accept AAD bearer tokens anywhere a PAT is accepted; we just don't acquire or send them.
Goals
azCLI session is active.azure/login@v2(GitHub Actions) orAzureCLI@2(Azure Pipelines with WIF) should "just work".Non-goals
apm authcommand surface (out of scope; package managers don't manage credentials).azure-identityor MSAL Python dependency (rejected during design — too heavy for the marginal coverage gain).ADO_APM_PAT_{ORG}). Defer until demand materializes.az login) on behalf of the user.Design
Resolution chain (ADO hosts only)
ADO_APM_PATenvironment variable (explicit, highest priority — preserves OSS UX)az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798(ifazis on PATH and an active session exists)499b84ac-1321-427f-aa17-267ca6975798is the well-known Entra ID resource ID for Azure DevOps. The acquired token is scoped to ADO and not usable against other Azure resources.Architecture
flowchart TD A[Dependency Reference] --> AH{Host is dev.azure.com?} AH -->|No| GH[GitHub auth chain unchanged] AH -->|Yes| AD1{ADO_APM_PAT set?} AD1 -->|Yes| AD2[Use PAT - Basic auth] AD1 -->|No| AD3{az on PATH and logged in?} AD3 -->|Yes| AD4[az account get-access-token] AD3 -->|No| AD5[Auth error - guide to PAT or az login] AD4 --> AD6{ADO accepts bearer?} AD6 -->|Yes| J[Success] AD6 -->|No - wrong tenant| AD7[Error: show tenant ID, suggest az login --tenant] AD2 --> AD8{ADO accepts PAT?} AD8 -->|Yes| J AD8 -->|No - az available| AD4 AD8 -->|No - no az| AD9[Auth error - PAT rejected]Token acquisition
Subprocess to the Azure CLI:
Why subprocess (and not
azure-identitySDK):azure-identitypulls MSAL, cryptography, requests, and a multi-MB transitive tree.azure/login@v2,AzureCLI@2) — both leaveazlogged in for downstream steps.azure-identitywould beat subprocess (a containerized workload with WIF env vars injected but noaz login) has a clean escape hatch via the existing PAT env var (see "Headless escape hatch" below).Bearer transport
For
git cloneandgit ls-remote:Use
git -c http.extraheader="Authorization: Bearer <jwt>". AAD bearer tokens are JWTs of ~1-2KB, which exceed safe URL-embedding limits and would leak into shell history, process tables, and git's own logs.Security: the header MUST be injected via environment variables, not via the
-cargument, to avoid leaking the bearer into the OS process table. Use git's standardGIT_CONFIG_COUNTmechanism:For REST API calls: standard
Authorization: Bearer <jwt>header on the HTTP request.Recommendation: unify on
http.extraheaderfor both PAT and bearerToday, the PAT path embeds the token in the clone URL. Recommend migrating both PAT and bearer to use
http.extraheader-via-env-vars:Authorization: Basic <base64(":<pat>")>Authorization: Bearer <jwt>Benefits:
ADO_APM_PAT=$(az account get-access-token ...)becomes reliable (JWT length no longer an issue)Implementer's discretion if they want to scope this issue to only the new bearer path and tackle URL-embedding cleanup separately.
Behavior matrix
ADO_APM_PATsetaz loginactive, no PATazure/login@v2(GitHub Actions)ADO_APM_PAT: ${{ secrets.PAT }}AzureCLI@2(Azure Pipelines)env: { ADO_APM_PAT: $(token) }azavailable[!]warning in diagnostic summaryazlogged into wrong tenantaz login --tenant <id>az, no PATazpresent but not logged in, no PATaz loginOR set PATazADO_APM_PATenv var (escape hatch)Headless escape hatch
For the rare environment without
azbut with raw WIF env vars (pure containerized workloads), users can pre-acquire and inject:This works only if the implementation unifies on
http.extraheader(recommended above), because JWT-length URL-embedding is unreliable today.Performance
az account get-access-token: ~200-500ms cold (subprocess spawn + token cache check).Security posture
az)az login)UX
First-run output
Default install (no auth noise):
The bearer path is indistinguishable from the PAT path at default verbosity. No "Using AAD bearer" banner. Users asked for packages, not an auth status report.
apm install --verbose:Stale PAT, silent fallback to bearer (default verbosity): identical to the happy path, with one diagnostic warning at the end of the run:
Error message redesign
Four scenarios, distinguished by observable signals (
shutil.which("az"),azexit code, ADO HTTP status). Each error stays under an 8-line budget and gives one or two concrete next actions.Case 1 -- No
azon PATH, no PAT:Case 2 --
azreturns token, ADO rejects (wrong tenant):(Tenant ID pulled from
az account show --query tenantId -o tsv.)Case 3 --
azpresent, not logged in, no PAT:Case 4 -- PAT rejected AND bearer rejected:
Discoverability decision
No new commands. No
apm auth login, noapm auth status.npm,pip, andcargodo not have auth subcommands; APM should not either. The redesigned error messages are the discovery surface.apm install --verbosealready shows resolved token source via the existingCommandLogger.auth_resolved()API.If
apm doctoris introduced in the future as a general health check, ADO auth becomes one of many checks — it should not be the trigger for adding the command.Env var decision
No new environment variables. Bearer tokens are short-lived (~1h); asking users to manually
export APM_ADO_BEARER=...would expire and fail cryptically. The whole point is that APM acquires a fresh token per invocation.ADO_APM_PATremains as the explicit-credential escape hatch.Population detection decision
APM does not detect employer or organization affinity. The same resolution chain runs for every user; behavior adapts to observable signals (is
azavailable? did it return a token? did ADO accept it?), not to identity. The only host-aware logic is the existing host classification (dev.azure.comand*.visualstudio.com).Code changes (file-level enumeration)
src/apm_cli/core/token_manager.py(line 49)'ado_modules'chain entries to include the bearer resolver.src/apm_cli/core/auth.py_resolve_ado_bearer()helper. Remove ADO exclusions at lines 311, 325, 454, 468 (the bearer fallback replaces credential-fill exclusion logic for ADO). Rewritebuild_error_context()ADO branch (~lines 406-410) with adaptive logic for the four error cases above.src/apm_cli/utils/github_host.py(line 188)http.extraheader: refactorbuild_ado_https_clone_url()to return a token-less URL plus header material. Otherwise: keep PAT URL-embedding and add a parallel function for bearer.src/apm_cli/deps/github_downloader.py(lines 711-724)GIT_CONFIG_COUNT/KEY_0/VALUE_0env vars on the spawned process.src/apm_cli/core/azure_cli.py(new, small)azdetection, token acquisition, error classification, in-memory cache.Implementation risk to validate first
GitPython's
Repo.clone_from()acceptsmulti_options=[...]for arbitrary-cflags, but for security we want to useGIT_CONFIG_COUNT/KEY/VALUEenv vars instead. Spike (~30 min): verify GitPython propagates the spawning process env to the underlyinggitsubprocess (it should, since it usessubprocess.Popenunder the hood, but confirm). If it does not, drop GitPython for ADO clones and shell out togitdirectly.Documentation changes
(Generated by the doc-writer agent during design; line numbers verified against
main.)docs/src/content/docs/getting-started/authentication.mdFor Azure DevOps, the only token source is ADO_APM_PAT.Add a row forADO_APM_PAT(priority 1, ADO-only) and a row foraz account get-access-token(priority 2, ADO-only, AAD bearer).## Azure DevOps): rewrite section to cover both PAT andaz loginpaths side by side. State precedence: PAT first,azbearer fallback. Note the silent-fallback diagnostic warning when PAT is stale andazsucceeds.ADO_APM_PAT only->ADO_APM_PAT -> az CLI bearer (AAD).flowchart TD): replace with the new chart from the Architecture section above.## Troubleshooting(insert after line 232, before### Diagnosing auth failures):### Azure DevOps auth failurescovering the four error scenarios.packages/apm-guide/.apm/skills/apm-usage/authentication.md## Azure DevOps (ADO)): replace with mirror of the rewritten doc-site section. Terse — link to the doc-site for the full troubleshooting flow rather than duplicating the four error cases.docs/src/content/docs/integrations/ci-cd.mdADO_APM_PAT: $(ADO_PAT). Add a second snippet showing the WIF pattern withAzureCLI@2and noADO_APM_PATenv var. State that APM auto-detects theazsession.azure/login@v2->apm installwith no token env, for ADO deps in GitHub-hosted CI.docs/src/content/docs/enterprise/security.mdADO_APM_PAT, then AAD bearer via az CLI.499b84ac-...and only sent to ADO. Add one sentence on process-table safety (GIT_CONFIG_COUNT/KEY/VALUEinjection, never CLI-arg)..apm/agents/auth-expert.agent.mdADO precedence: ADO_APM_PAT -> az account get-access-token (AAD bearer) -> error.Authorization: Bearer <jwt>, not Basic; do not mix the two. Add new pitfall: staleADO_APM_PATshadows a workingazsession unless explicitly unset..apm/skills/auth/SKILL.mddescription): addaz CLI bearer fallbackso the skill activates on related code.az account get-access-token.(Mirror copies under
.github/skills/auth/SKILL.mdand.github/agents/auth-expert.agent.mdare regenerated byapm install --target copilotper the dogfood pattern; updating.apm/source is sufficient if the regen-and-commit step happens in the implementation PR.)CHANGELOG.mdAdd entry under
## [Unreleased]->### Added:Acceptance test additions (out of scope for doc-only changes; tracked here for the implementation PR)
.github/workflows/auth-acceptance.yml(lines 76, 82): add a test mode exercising the AAD path usingazure/login@v2with WIF and noADO_APM_PAT..github/workflows/ci-integration.yml(lines 152, 203): same pattern.Open questions for the implementer
http.extraheaderfor both PAT and bearer, or keep URL-embedding for PAT? Recommended: unify. Cleaner code path, removes process-table leakage for the PAT path too, and makes the JWT escape hatch reliable. Trade-off: a slightly larger blast radius for the change.[!]warning in the end-of-run diagnostic summary. Confirm this matches the existingDiagnosticCollectorUX patterns.Acceptance criteria
apm installwith an ADO dep and noADO_APM_PATset, butaz loginactive, succeeds with no auth-related output at default verbosity.apm install --verboseshows the token source resolved (bearer from az cli).apm installwith a staleADO_APM_PAT(rejected with 401) and activeaz loginsucceeds, with one[!]diagnostic warning at the end.apm installinside a GitHub Actions step that followsazure/login@v2succeeds with noADO_APM_PATenv var declared.apm installinside an Azure PipelinesAzureCLI@2task succeeds with noADO_APM_PATenv var declared.apm installwith no PAT and noazon PATH produces the Case 1 error message.apm installwithazlogged into a tenant that has no ADO access produces the Case 2 error message including the tenant ID.apm installwithazinstalled but not logged in, and no PAT, produces the Case 3 error message.gitcommand line (verified by inspectingps-equivalent during a clone).## [Unreleased]->### Added.References
az account get-access-token: https://learn.microsoft.com/cli/azure/account#az-account-get-access-token499b84ac-1321-427f-aa17-267ca6975798(well-known)azure/login@v2GitHub Action (OIDC federated credentials): https://github.com/Azure/loginAzureCLI@2Azure Pipelines task with WIF service connection: https://learn.microsoft.com/azure/devops/pipelines/tasks/reference/azure-cli-v2http.extraheaderconfiguration: https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpextraHeaderGIT_CONFIG_COUNTenv var injection: https://git-scm.com/docs/git-config#ENVIRONMENT