_AUTH_RECORD_PATH is module-level constant — shared across all projects, causes device code loop when switching tenants
Problem
auth.py computes _AUTH_RECORD_PATH once at module import time, before any .env is loaded:
_AUTH_RECORD_PATH = Path(os.environ.get("LOCALAPPDATA") or Path.home()) / ".IdentityService" / "dataverse_cli_auth_record.json"
This means every project on the machine shares the same dataverse_cli_auth_record.json file, regardless of tenant or environment.
Impact when working across multiple projects/clients in parallel:
- Project A authenticates → record written for tenant
aaa / user alice@client-a.com
- Developer switches to Project B (tenant
bbb, user bob@client-b.com) → DeviceCodeCredential loads the stale record from Project A
- The credential silently attempts a refresh for the wrong tenant/account
- When the refresh fails and a new device code is issued, the browser picks up the code, routes to the account picker, the user selects the correct account — but Microsoft rejects it because the tenant in the code doesn't match → infinite redirect loop back to the device code entry page
The bug is not obvious to diagnose: the error manifests as a browser redirect loop, not a clear authentication failure.
Proposed fix
Make the path a function, computed lazily after load_env() has run, using TENANT_ID and the Dataverse hostname as a disambiguating slug:
def _auth_record_path() -> Path:
tenant_id = os.environ.get("TENANT_ID", "")
dataverse_url = os.environ.get("DATAVERSE_URL", "")
base = Path(os.environ.get("LOCALAPPDATA") or Path.home()) / ".IdentityService"
if tenant_id and dataverse_url:
tenant_short = tenant_id.split("-")[0]
try:
from urllib.parse import urlparse
host = urlparse(dataverse_url).hostname or ""
env_slug = host.split(".")[0] # e.g. "contoso-dev" from "contoso-dev.crm4.dynamics.com"
except Exception:
env_slug = ""
slug = f"_{tenant_short}_{env_slug}" if env_slug else f"_{tenant_short}"
return base / f"dataverse_cli_auth_record{slug}.json"
return base / "dataverse_cli_auth_record.json" # safe fallback
Then replace every reference to _AUTH_RECORD_PATH with a call to _auth_record_path() — always after load_env() has been called.
Result: each environment gets its own record (e.g. dataverse_cli_auth_record_be91cf8c_contoso-dev.json), silent refresh works per-project, and parallel multi-client setups no longer interfere with each other.
Environment
- macOS (same issue applies on Linux; Windows uses
LOCALAPPDATA which is also per-user but the collision still occurs)
- Multiple Dataverse environments across different tenants on the same machine
azure-identity device code flow with TokenCachePersistenceOptions
_AUTH_RECORD_PATHis module-level constant — shared across all projects, causes device code loop when switching tenantsProblem
auth.pycomputes_AUTH_RECORD_PATHonce at module import time, before any.envis loaded:This means every project on the machine shares the same
dataverse_cli_auth_record.jsonfile, regardless of tenant or environment.Impact when working across multiple projects/clients in parallel:
aaa/ useralice@client-a.combbb, userbob@client-b.com) →DeviceCodeCredentialloads the stale record from Project AThe bug is not obvious to diagnose: the error manifests as a browser redirect loop, not a clear authentication failure.
Proposed fix
Make the path a function, computed lazily after
load_env()has run, usingTENANT_IDand the Dataverse hostname as a disambiguating slug:Then replace every reference to
_AUTH_RECORD_PATHwith a call to_auth_record_path()— always afterload_env()has been called.Result: each environment gets its own record (e.g.
dataverse_cli_auth_record_be91cf8c_contoso-dev.json), silent refresh works per-project, and parallel multi-client setups no longer interfere with each other.Environment
LOCALAPPDATAwhich is also per-user but the collision still occurs)azure-identitydevice code flow withTokenCachePersistenceOptions