Skip to content

Shell command (\!) API keys are cached forever, causing expired token errors with authHeader #1835

@alennartz

Description

@alennartz

Description

When using authHeader: true with a shell command (!) for apiKey (e.g., Azure AD tokens via az cli), the token is fetched once at startup and never refreshed. Sessions that outlast the token's lifetime (typically ~60 minutes for Azure) fail with expired token errors, requiring a full restart of pi.

Configuration

{
  "providers": {
    "azure-foundry": {
      "baseUrl": "https://example.services.ai.azure.com/anthropic",
      "api": "anthropic-messages",
      "apiKey": "!az account get-access-token --resource https://cognitiveservices.azure.com --query accessToken -o tsv",
      "authHeader": true,
      "models": [...]
    }
  }
}

Root Cause

Three things combine to make this unfixable without restarting:

  1. resolve-config-value.ts caches shell command results in a Map with no TTL. Once cached, the same value is returned for the lifetime of the process:

    const commandResultCache = new Map<string, string | undefined>();
    // ...
    if (commandResultCache.has(commandConfig)) {
        return commandResultCache.get(commandConfig); // stale forever
    }
  2. ModelRegistry.refresh() does not call clearConfigValueCache() before reloading models, so even opening /model and re-selecting doesn't help — the cache still returns the old token.

  3. authHeader: true resolves the API key and bakes it into the model's static headers.Authorization at parse time in parseModels(). There's no dynamic re-resolution at request time.

Expected Behavior

Users should never see an expired token error during a session. Token refresh should be completely transparent — no manual intervention, no /model re-selection, no restart.

Suggested Fix

Add a cacheTtl field (in seconds) to the provider configuration for ! shell commands:

{
  "providers": {
    "azure-foundry": {
      "apiKey": "!az account get-access-token --resource https://cognitiveservices.azure.com --query accessToken -o tsv",
      "cacheTtl": 1800,
      ...
    }
  }
}

When cacheTtl is set, the ! command result should be re-executed automatically after the TTL expires. The re-resolution needs to happen at request time — not just at model parse time — so that authHeader headers are always current. This way the token stays fresh transparently and the user never encounters an auth error from an expired token.

Without a cacheTtl, the current cache-forever behavior is fine for static secrets that don't expire.

Docs Note

The models.md documentation says:

The file reloads each time you open /model. Edit during session; no restart needed.

This is misleading for ! commands since the command cache prevents actual re-execution even when the file is re-read. It probably makes sense to also alter the logic/model to clear cache entries and refresh them. This could serve as a minimal fix for this issue, but the devx is not as nice as the full fix proposed above

Metadata

Metadata

Assignees

No one assigned

    Labels

    inprogressIssue is being worked on

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions