aaif-goose/goose · error

Unknown provider: ${providerId}

Error message

Unknown provider: ${providerId}

What it means

Thrown by acpGetProviderDetails: it sends providersList_unstable({providerIds: [providerId]}) and searches response.entries for a matching providerId. The goose backend returns an entry only for ids it knows (built-in or registered providers), so a miss means the id does not exist in the backend's provider inventory — different from 'not configured'. This is a client-side guard before providerEntryToDetails would dereference undefined.

Source

Thrown at ui/desktop/src/acp/providers.ts:120

  const { entries } = await client.goose.providersList_unstable({});
  return entries.map(providerEntryToDetails);
}

export async function acpListSetupProviderDetails(): Promise<ProviderDetails[]> {
  const providers = await acpListProviderDetails();
  return providers.filter((provider) => provider.visible_in_setup);
}

export async function acpListSettingsProviderDetails(): Promise<ProviderDetails[]> {
  const providers = await acpListProviderDetails();
  return providers.filter((provider) => provider.visible_in_setup || provider.is_configured);
}

export async function acpGetProviderDetails(providerId: string): Promise<ProviderDetails> {
  const client = await getAcpClient();
  const { entries } = await client.goose.providersList_unstable({ providerIds: [providerId] });
  const entry = entries.find((candidate) => candidate.providerId === providerId);
  if (!entry) throw new Error(`Unknown provider: ${providerId}`);
  return providerEntryToDetails(entry);
}

async function waitForProviderInventoryRefresh(
  client: Awaited<ReturnType<typeof getAcpClient>>,
  providerId: string,
  refresh: RefreshProviderInventoryResponse_unstable,
  signal?: globalThis.AbortSignal
): Promise<ProviderDetails> {
  const shouldWait =
    refresh.started.includes(providerId) ||
    refresh.skipped?.some(
      (skip) => skip.providerId === providerId && skip.reason === 'already_refreshing'
    );

  let entry: ProviderInventoryEntryDto | undefined;
  const attempts = shouldWait
    ? INVENTORY_REFRESH_TIMEOUT_MS / INVENTORY_REFRESH_POLL_INTERVAL_MS

View on GitHub (pinned to 3810898a74)

Solutions

  1. Call acpListProviderDetails() (or acpListSettingsProviderDetails) and print the available providerIds — use an exact id from that list.
  2. Check for trailing spaces/case mismatch in the id; the find is an exact === on providerId.
  3. If the id came from persisted settings, clear or migrate it after a goose upgrade that renamed providers.
  4. Confirm the backend version: providersList_unstable is an _unstable API and its inventory can change between releases.

Example fix

// before
const { entries } = await client.goose.providersList_unstable({ providerIds: [providerId] });
const entry = entries.find((candidate) => candidate.providerId === providerId);
if (!entry) throw new Error(`Unknown provider: ${providerId}`);

// after (fail with the ids that do exist)
const { entries } = await client.goose.providersList_unstable({ providerIds: [providerId] });
const entry = entries.find((candidate) => candidate.providerId === providerId);
if (!entry) {
  const all = await client.goose.providersList_unstable({ providerIds: [] });
  throw new Error(`Unknown provider: ${providerId}. Available: ${all.entries.map((e) => e.providerId).join(', ')}`);
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Resolve the id against the live inventory before fetching details
const known = await acpListProviderDetails();
const validId = known.find((p) => p.id === providerId)?.id;
if (!validId) {
  throw new Error(`Provider '${providerId}' is not in the inventory. Available: ${known.map((p) => p.id).join(', ')}`);
}

Type guard

async function isKnownProvider(providerId: string): Promise<boolean> {
  const providers = await acpListProviderDetails();
  return providers.some((p) => p.id === providerId);
}

Try / catch

try {
  const details = await acpGetProviderDetails(providerId);
} catch (error) {
  if (/Unknown provider/.test(String(error))) {
    return refreshProviderListUi(); // repopulate picker from the inventory
  }
  throw error;
}

Prevention

When it happens

Trigger: Calling acpGetProviderDetails('openai ') with a typo or whitespace; an id from an older/newer goose version (provider renamed or removed); a custom provider not yet registered in the profile; entries returned under a different providerId spelling (case-sensitive compare).

Common situations: Persisted provider id in settings/localStorage after upgrading goose; hand-edited config referencing a deleted provider; UI list rendered from a cached inventory while the backend inventory changed.

Related errors


AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16). Data as JSON: /api/errors/15f997031d983f1f. Report an issue: GitHub.