Credential Lifecycle

The Agent’s Own Identity Problem

Sandboxing & Permissions addresses what an agent is allowed to do. This page addresses a prerequisite: how the agent proves who it is to the services it depends on — the model provider API, MCP servers, OAuth endpoints, Git hosts, and cloud platforms.

Credential management is not a configuration problem. Credentials expire, need rotation, require secure storage, and must be refreshed proactively — often mid-conversation. An agent that loses its API token during a multi-step task is not gracefully degraded; it is broken. The credential lifecycle is infrastructure, not plumbing.


The Credential Stack

An agent in a typical session holds multiple credentials simultaneously, each with different lifetimes, scopes, and refresh mechanisms.

graph TB
    subgraph credentials ["Active Credentials"]
        API["Model Provider API Key<br/>Anthropic, OpenAI, Google"]
        OAuth["OAuth Access Token<br/>GitHub, Slack, Calendar"]
        MCP["MCP Server Credentials<br/>Per-server env vars"]
        GIT["Git Credentials<br/>SSH keys, PATs"]
        CLOUD["Cloud Provider<br/>AWS, GCP, Azure tokens"]
    end

    subgraph lifecycle ["Lifecycle Operations"]
        ACQ["Acquire"]
        STORE["Store"]
        REFRESH["Refresh"]
        REVOKE["Revoke"]
    end

    credentials --> lifecycle
Credential TypeTypical LifetimeRefresh MechanismStorage Location
API key (Anthropic, OpenAI)Indefinite (until rotated)Manual rotation~/.agent/credentials or env var
OAuth access token1 hourRefresh token exchange~/.agent/oauth/{provider}.json
OAuth refresh token30-90 daysRe-authentication~/.agent/oauth/{provider}.json
MCP server env varsIndefiniteManual update.mcp.json references, resolved at runtime
Git PAT30-365 daysManual regenerationSystem credential store or env var
Cloud provider token1-12 hoursCLI tool refresh (gcloud auth, aws sso)Provider-managed store

OAuth for Agents

Most non-trivial agent deployments require OAuth for at least one service (GitHub for PRs, Google for Calendar, Slack for notifications). The agent harness must implement a complete OAuth flow — not delegate it to the user.

PKCE Authorization Code Flow

The standard flow for CLI agents is OAuth 2.0 with PKCE (Proof Key for Code Exchange), which does not require a client secret:

sequenceDiagram
    participant A as Agent Harness
    participant B as User's Browser
    participant P as OAuth Provider

    A->>A: Generate code_verifier + code_challenge
    A->>B: Open authorization URL with code_challenge
    B->>P: User authenticates and approves scopes
    P->>A: Redirect to localhost callback with auth code
    A->>P: Exchange auth code + code_verifier for tokens
    P->>A: { access_token, refresh_token, expires_in }
    A->>A: Store tokens securely

Key implementation details:

Token Storage

Credentials must be stored securely on disk with appropriate file permissions:

// ~/.agent/oauth/github.json
{
  "provider": "github",
  "access_token": "gho_xxxxxxxxxxxx",
  "refresh_token": "ghr_xxxxxxxxxxxx",
  "token_type": "bearer",
  "scope": "repo,read:org",
  "expires_at": "2026-04-02T10:30:00Z",
  "created_at": "2026-04-02T09:30:00Z"
}

Proactive Refresh

The most common credential failure in agent systems is a token expiring mid-conversation. The model makes an API call, gets a 401, and either retries in a loop (burning tokens) or gives up (breaking the task). Both outcomes are preventable.

The Refresh Timeline

Token lifetime: ──────────────────────────────────────────► expiry

                                    80% of TTL
                                    REFRESH HERE

                        ┌─────────────────┼──────────────┐
                        │  Safe zone      │  Danger zone │
                        │  Token valid    │  May expire  │
                        │  No action      │  mid-request │
                        └─────────────────┼──────────────┘

Refresh at 80% of TTL, not on expiry. For a 1-hour access token, refresh at the 48-minute mark. This provides a 12-minute buffer for network delays, clock skew, and retry attempts.

Refresh Implementation

def ensure_valid_token(credential):
    now = current_time()
    ttl = credential.expires_at - credential.created_at
    refresh_threshold = credential.created_at + (ttl * 0.8)

    if now < refresh_threshold:
        return credential  # Still in safe zone

    # Attempt refresh
    try:
        new_tokens = oauth_refresh(
            provider=credential.provider,
            refresh_token=credential.refresh_token
        )
        save_credential(new_tokens)
        return new_tokens
    except RefreshError:
        # Refresh token may be expired — require re-authentication
        return prompt_reauthentication(credential.provider)

Call ensure_valid_token before every API request, not after a 401. This turns credential refresh from error handling into infrastructure.


MCP Server Credentials

Each MCP server may require its own credentials, configured via environment variable references in .mcp.json:

{
  "mcpServers": {
    "github": {
      "env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" }
    },
    "postgres": {
      "env": { "DATABASE_URL": "${DATABASE_URL}" }
    },
    "slack": {
      "env": { "SLACK_BOT_TOKEN": "${SLACK_BOT_TOKEN}" }
    }
  }
}

The harness resolves ${VAR} references at MCP server startup, not at config load time. This means:


Credential Isolation

The agent harness manages credentials for itself and for the tools it connects to. These must be isolated:

CredentialWho Uses ItIsolation Requirement
Model API keyHarness onlyNever passed to MCP servers or tool processes
OAuth tokensHarness + specific toolsScoped per provider; only the relevant token is passed to each tool
MCP server env varsIndividual MCP serversEach server sees only its own declared variables, not the full environment
Git credentialsGit operations onlyAccessible only during bash(git *) calls, not arbitrary shell commands

The two-phase runtime pattern (Sandboxing) extends to credentials: during the setup phase, the harness authenticates and stores tokens; during the execution phase, credentials are available only to the specific tool or server that needs them, not to the agent’s general-purpose execution environment.


Logout and Revocation

Users must be able to explicitly clear stored credentials. This is a safety requirement, not a convenience feature.

# Clear all stored credentials
agent logout

# Clear credentials for a specific provider
agent logout github

# Clear OAuth tokens only (keep API keys)
agent logout --oauth-only

On logout:


Cross-Platform Credential Patterns

PlatformAPI AuthOAuth SupportCredential StorageMCP Credentials
Claude CodeAPI key via ANTHROPIC_API_KEY env var, or OAuth PKCE for claude.ai accountsBuilt-in OAuth with PKCE, browser-based login flow~/.claude/oauth/ with file permissions${ENV_VAR} references in .mcp.json, resolved at server spawn
OpenAI CodexAPI key via OPENAI_API_KEY env varOAuth via ChatGPT desktop app (implicit)System keychain or env var${ENV_VAR} in codex config
Gemini CLIGoogle Cloud auth (gcloud auth login)Google OAuth via browser, managed by gcloud CLIGoogle Cloud credential store${ENV_VAR} in MCP config
Google ADKApplication Default Credentials (ADC)Separate agent-auth and user-auth flowsProvider-managed (Google Cloud IAM)Service account keys or Workload Identity

The pattern is consistent: API keys for the model provider, OAuth for third-party integrations, and environment variable references for MCP server credentials. The variation is in storage and refresh — some platforms handle refresh automatically (Google Cloud), while others require the harness to implement it (Claude Code, Codex).


Key Takeaways