diff --git a/src/agents/auth-profiles.ts b/src/agents/auth-profiles.ts index 51f50bdf1b9..ae2d36e6055 100644 --- a/src/agents/auth-profiles.ts +++ b/src/agents/auth-profiles.ts @@ -27,6 +27,7 @@ export { export { clearRuntimeAuthProfileStoreSnapshots, ensureAuthProfileStore, + ensureAuthProfileStoreWithoutExternalProfiles, hasAnyAuthProfileStoreSource, loadAuthProfileStoreForSecretsRuntime, loadAuthProfileStoreWithoutExternalProfiles, diff --git a/src/agents/auth-profiles/store.ts b/src/agents/auth-profiles/store.ts index 0fb29f683be..eff573180c6 100644 --- a/src/agents/auth-profiles/store.ts +++ b/src/agents/auth-profiles/store.ts @@ -298,23 +298,30 @@ export function loadAuthProfileStoreWithoutExternalProfiles(agentDir?: string): export function ensureAuthProfileStore( agentDir?: string, options?: { allowKeychainPrompt?: boolean }, +): AuthProfileStore { + return overlayExternalAuthProfiles( + ensureAuthProfileStoreWithoutExternalProfiles(agentDir, options), + { agentDir }, + ); +} + +export function ensureAuthProfileStoreWithoutExternalProfiles( + agentDir?: string, + options?: { allowKeychainPrompt?: boolean }, ): AuthProfileStore { const runtimeStore = resolveRuntimeAuthProfileStore(agentDir); if (runtimeStore) { - return overlayExternalAuthProfiles(runtimeStore, { agentDir }); + return runtimeStore; } - const store = loadAuthProfileStoreForAgent(agentDir, options); const authPath = resolveAuthStorePath(agentDir); const mainAuthPath = resolveAuthStorePath(); if (!agentDir || authPath === mainAuthPath) { - return overlayExternalAuthProfiles(store, { agentDir }); + return store; } const mainStore = loadAuthProfileStoreForAgent(undefined, options); - const merged = mergeAuthProfileStores(mainStore, store); - - return overlayExternalAuthProfiles(merged, { agentDir }); + return mergeAuthProfileStores(mainStore, store); } export function findPersistedAuthProfileCredential(params: { diff --git a/src/infra/provider-usage.auth.plugin.test.ts b/src/infra/provider-usage.auth.plugin.test.ts index 8e135062cfc..fe1f5185e6f 100644 --- a/src/infra/provider-usage.auth.plugin.test.ts +++ b/src/infra/provider-usage.auth.plugin.test.ts @@ -10,11 +10,16 @@ const hasAnyAuthProfileStoreSourceMock = vi.fn(() => false); const ensureAuthProfileStoreMock = vi.fn(() => ({ profiles: {}, })); +const ensureAuthProfileStoreWithoutExternalProfilesMock = vi.fn(() => ({ + profiles: {}, +})); const resolveAuthProfileOrderMock = vi.fn((_params: unknown): string[] => []); vi.mock("../agents/auth-profiles.js", () => ({ dedupeProfileIds: (profileIds: string[]) => [...new Set(profileIds)], ensureAuthProfileStore: () => ensureAuthProfileStoreMock(), + ensureAuthProfileStoreWithoutExternalProfiles: () => + ensureAuthProfileStoreWithoutExternalProfilesMock(), hasAnyAuthProfileStoreSource: () => hasAnyAuthProfileStoreSourceMock(), listProfilesForProvider: () => [], resolveApiKeyForProfile: async () => null, @@ -54,6 +59,10 @@ describe("resolveProviderAuths plugin boundary", () => { ensureAuthProfileStoreMock.mockReturnValue({ profiles: {}, }); + ensureAuthProfileStoreWithoutExternalProfilesMock.mockClear(); + ensureAuthProfileStoreWithoutExternalProfilesMock.mockReturnValue({ + profiles: {}, + }); resolveAuthProfileOrderMock.mockReset(); resolveAuthProfileOrderMock.mockReturnValue([]); resolveProviderUsageAuthWithPluginMock.mockReset(); @@ -160,7 +169,7 @@ describe("resolveProviderAuths plugin boundary", () => { it("keeps auth-profile credential sources provider-specific", async () => { hasAnyAuthProfileStoreSourceMock.mockReturnValue(true); - ensureAuthProfileStoreMock.mockReturnValue({ + ensureAuthProfileStoreWithoutExternalProfilesMock.mockReturnValue({ profiles: { "anthropic:default": { type: "api_key", @@ -201,6 +210,25 @@ describe("resolveProviderAuths plugin boundary", () => { provider: "anthropic", }), ); + expect(ensureAuthProfileStoreMock).not.toHaveBeenCalled(); + }); + + it("does not overlay external auth profiles while checking the skip gate", async () => { + hasAnyAuthProfileStoreSourceMock.mockReturnValue(true); + + await withTempHome(async (homeDir) => { + await expect( + resolveProviderAuths({ + providers: ["anthropic"], + skipPluginAuthWithoutCredentialSource: true, + env: { HOME: homeDir }, + }), + ).resolves.toEqual([]); + }); + + expect(ensureAuthProfileStoreWithoutExternalProfilesMock).toHaveBeenCalledTimes(1); + expect(ensureAuthProfileStoreMock).not.toHaveBeenCalled(); + expect(resolveProviderUsageAuthWithPluginMock).not.toHaveBeenCalled(); }); it("skips plugin usage auth per provider when only another provider has direct credentials", async () => { diff --git a/src/infra/provider-usage.auth.ts b/src/infra/provider-usage.auth.ts index e5e11cfb806..be906caa169 100644 --- a/src/infra/provider-usage.auth.ts +++ b/src/infra/provider-usage.auth.ts @@ -1,6 +1,7 @@ import { dedupeProfileIds, ensureAuthProfileStore, + ensureAuthProfileStoreWithoutExternalProfiles, hasAnyAuthProfileStoreSource, listProfilesForProvider, resolveApiKeyForProfile, @@ -230,7 +231,9 @@ function hasAuthProfileCredentialSource(params: { state: UsageAuthState; provider: UsageProviderId; }): boolean { - const store = resolveUsageAuthStore(params.state); + const store = ensureAuthProfileStoreWithoutExternalProfiles(params.state.agentDir, { + allowKeychainPrompt: false, + }); const order = resolveAuthProfileOrder({ cfg: params.state.cfg, store,