Describe the bug
With a non-empty COPILOT_AGENT_SESSION_ID, which Copilot agent hosts set for the shells they start, any UserCredential.acquire_token call whose silent result makes check_result raise AuthenticationError enters the interactive authentication fallback, even for non-SSH requests such as az account get-access-token. Without the variable, the same failure returns the error and an az login hint.
Cause, in src/azure-cli-core/azure/cli/core/auth/msal_credentials.py, UserCredential.acquire_token:
- The agentic-session block (added with
agentic_session.py) always sets kwargs["data"]["client_session"] = agentic_session_id.
- On
AuthenticationError, the existing fallback uses 'data' in kwargs to detect VM SSH (az ssh):
if 'data' in kwargs:
logger.warning(ex)
result = self._acquire_token_interactive(scopes, **kwargs)
else:
raise
az account get-access-token reaches this through Profile.get_raw_token, which calls cred.acquire_token(scopes) without data. Agentic mode now always populates data, so this branch fires for any such failing request. The code's own comment says non-SSH scenarios should "not launch browser, but show the error message and az login command instead."
This code is unchanged on the current dev branch.
Impact in practice: tools that shell out to az run it non-interactively with a timeout; here that was .NET Azure.Identity 1.21.0 AzureCliCredential via DefaultAzureCredential. Each affected failing call attempts interactive authentication and opens a browser sign-in tab. When the SDK times out it kills the process it launched, but the Python process kept running (PPID 1) with a 127.0.0.1 listening socket (see Additional context). A test suite making five such calls per run left orphans in bursts of five; we found 55 orphaned az account get-access-token Python processes on one machine.
Related command
az account get-access-token --output json --resource https://cognitiveservices.azure.com --tenant <tenant-id>
(this is exactly what Azure.Identity's AzureCliCredential runs)
Errors
With COPILOT_AGENT_SESSION_ID unset, the command fails fast as expected:
ERROR: AADSTS530004: AcceptCompliantDevice setting isn't configured for this organization. The admin needs to configure this setting to allow external users access to protected resources. ...
Run the command below to authenticate interactively; additional arguments may be added as needed:
az logout
az login --tenant "<tenant-id>" --scope "https://cognitiveservices.azure.com/.default"
With it set, the command does not exit with the authentication error. It logs the error as a warning and enters interactive authentication. With BROWSER=/usr/bin/true the process stayed running with a Python LISTEN socket on 127.0.0.1:<port>; without it, the default browser opens a sign-in page.
Issue script & Debug output
Repro needs a signed-in user account for which silent token acquisition fails in the target tenant (e.g. a guest/external account blocked by Conditional Access, or any interaction_required / MFA claims case). BROWSER=/usr/bin/true stops a real browser opening but still shows the interactive flow starting.
TENANT='<tenant-id>' # replace with a real tenant ID
R=https://cognitiveservices.azure.com
# 1. Expected behaviour: fails immediately with AADSTS... and the `az login` hint
env -u COPILOT_AGENT_SESSION_ID az account get-access-token --resource $R --tenant $TENANT
# 2. Bug: starts interactive auth-code flow and hangs
COPILOT_AGENT_SESSION_ID=test BROWSER=/usr/bin/true \
az account get-access-token --resource $R --tenant $TENANT --debug
# in another shell: lsof -nP -iTCP -sTCP:LISTEN | grep -i python -> 127.0.0.1:<port>
Expected behavior
Agentic-session mode should not change the failure behaviour. A failed silent token request outside az ssh should raise the AuthenticationError with the az login hint, exactly as without COPILOT_AGENT_SESSION_ID. In particular, az account get-access-token, which is called by SDK credentials in non-interactive contexts, should never open a browser.
Suggested fix: detect the SSH case explicitly instead of relying on 'data' in kwargs. The SSH path supplies a data dict containing token_type: "ssh-cert" (plus req_cnf and key_id), so the fallback could check (kwargs.get("data") or {}).get("token_type") == "ssh-cert". A smaller fix is to record whether the caller supplied data before the agentic block adds client_session, though that keeps the older, broader heuristic.
Environment Summary
azure-cli 2.90.0 (Homebrew)
msal 1.36.0
Python 3.14.7
OS macOS 27.0.1 (arm64)
COPILOT_AGENT_SESSION_ID set by the GitHub Copilot agent host
Additional context
- Related, separate: Homebrew's
az wrapper (.../bin/az) runs python -Im azure.cli "$@" without exec, which adds a shell process between the caller and Python. Terminating only that shell can leave Python running. .NET Azure.Identity launches az through /bin/sh -c and calls Process.Kill() without killing the process tree, which is what we observed. Using exec in the wrapper would remove this layer, though full cleanup also depends on how the caller launches and terminates the process.
- Workaround for affected users: unset
COPILOT_AGENT_SESSION_ID for SDK/test processes, or make sure the default az account can get tokens silently for the tenants it is asked about.
Describe the bug
With a non-empty
COPILOT_AGENT_SESSION_ID, which Copilot agent hosts set for the shells they start, anyUserCredential.acquire_tokencall whose silent result makescheck_resultraiseAuthenticationErrorenters the interactive authentication fallback, even for non-SSH requests such asaz account get-access-token. Without the variable, the same failure returns the error and anaz loginhint.Cause, in
src/azure-cli-core/azure/cli/core/auth/msal_credentials.py,UserCredential.acquire_token:agentic_session.py) always setskwargs["data"]["client_session"] = agentic_session_id.AuthenticationError, the existing fallback uses'data' in kwargsto detect VM SSH (az ssh):az account get-access-tokenreaches this throughProfile.get_raw_token, which callscred.acquire_token(scopes)withoutdata. Agentic mode now always populatesdata, so this branch fires for any such failing request. The code's own comment says non-SSH scenarios should "not launch browser, but show the error message andaz logincommand instead."This code is unchanged on the current
devbranch.Impact in practice: tools that shell out to az run it non-interactively with a timeout; here that was .NET Azure.Identity 1.21.0
AzureCliCredentialviaDefaultAzureCredential. Each affected failing call attempts interactive authentication and opens a browser sign-in tab. When the SDK times out it kills the process it launched, but the Python process kept running (PPID 1) with a127.0.0.1listening socket (see Additional context). A test suite making five such calls per run left orphans in bursts of five; we found 55 orphanedaz account get-access-tokenPython processes on one machine.Related command
az account get-access-token --output json --resource https://cognitiveservices.azure.com --tenant <tenant-id>(this is exactly what Azure.Identity's
AzureCliCredentialruns)Errors
With
COPILOT_AGENT_SESSION_IDunset, the command fails fast as expected:With it set, the command does not exit with the authentication error. It logs the error as a warning and enters interactive authentication. With
BROWSER=/usr/bin/truethe process stayed running with a Python LISTEN socket on127.0.0.1:<port>; without it, the default browser opens a sign-in page.Issue script & Debug output
Repro needs a signed-in user account for which silent token acquisition fails in the target tenant (e.g. a guest/external account blocked by Conditional Access, or any
interaction_required/ MFA claims case).BROWSER=/usr/bin/truestops a real browser opening but still shows the interactive flow starting.Expected behavior
Agentic-session mode should not change the failure behaviour. A failed silent token request outside
az sshshould raise theAuthenticationErrorwith theaz loginhint, exactly as withoutCOPILOT_AGENT_SESSION_ID. In particular,az account get-access-token, which is called by SDK credentials in non-interactive contexts, should never open a browser.Suggested fix: detect the SSH case explicitly instead of relying on
'data' in kwargs. The SSH path supplies adatadict containingtoken_type: "ssh-cert"(plusreq_cnfandkey_id), so the fallback could check(kwargs.get("data") or {}).get("token_type") == "ssh-cert". A smaller fix is to record whether the caller supplieddatabefore the agentic block addsclient_session, though that keeps the older, broader heuristic.Environment Summary
Additional context
azwrapper (.../bin/az) runspython -Im azure.cli "$@"withoutexec, which adds a shell process between the caller and Python. Terminating only that shell can leave Python running. .NET Azure.Identity launches az through/bin/sh -cand callsProcess.Kill()without killing the process tree, which is what we observed. Usingexecin the wrapper would remove this layer, though full cleanup also depends on how the caller launches and terminates the process.COPILOT_AGENT_SESSION_IDfor SDK/test processes, or make sure the defaultazaccount can get tokens silently for the tenants it is asked about.