diff --git a/agent/credential_pool.py b/agent/credential_pool.py index 4ccf67fe5a4a0..c12cfee279504 100644 --- a/agent/credential_pool.py +++ b/agent/credential_pool.py @@ -2387,17 +2387,33 @@ def _seed_from_singletons(provider: str, entries: List[PooledCredential]) -> Tup # env vars (COPILOT_GITHUB_TOKEN / GH_TOKEN). They don't live in # the auth store or credential pool, so we resolve them here. try: - from hermes_cli.copilot_auth import resolve_copilot_token, get_copilot_api_token + from hermes_cli.copilot_auth import ( + COPILOT_ENV_VARS, + resolve_copilot_token, + get_copilot_api_token, + ) + # All-sources suppression gate BEFORE any work — including the + # `gh auth token` subprocess spawn. resolve_copilot_token() + # shells out (~30ms), and the exchange retries 3x with backoff + # (~13s worst case); a user who suppressed every copilot source + # (hermes auth remove copilot gh_cli) must not pay either on + # every pool load (model picker open, /model, agent startup). + # Enumerating the full source space here matches what + # credential_sources._remove_copilot_gh suppresses, so an + # all-suppressed check is stable. + copilot_sources = ["gh_cli"] + [f"env:{v}" for v in COPILOT_ENV_VARS] + if all(_is_suppressed(provider, s) for s in copilot_sources): + return changed, active_sources token, source = resolve_copilot_token() if token: source_name = "gh_cli" if "gh" in source.lower() else f"env:{source}" - # Suppression gate BEFORE the network exchange. The - # exchange retries 3x with backoff (~13s worst case), so a - # source the user already suppressed (hermes auth remove - # copilot gh_cli) must not burn that dead time on every pool - # load (model picker open, /model, agent startup) just to - # have the entry discarded afterwards. This is the same - # early-gate pattern every other singleton branch uses. + # Per-source suppression gate (a user may suppress only the + # gh CLI path and keep an env var, or vice versa) BEFORE the + # network exchange. The exchange retries 3x with backoff + # (~13s worst case), so a source the user already suppressed + # must not burn that dead time just to have the entry + # discarded afterwards. Same early-gate pattern every other + # singleton branch uses. if _is_suppressed(provider, source_name): return changed, active_sources api_token, enterprise_base_url = get_copilot_api_token(token)