Skip to content

Instantly share code, notes, and snippets.

@johnlindquist
Created January 26, 2026 23:39
Show Gist options
  • Select an option

  • Save johnlindquist/0547341e60fb2088599104bab2726838 to your computer and use it in GitHub Desktop.

Select an option

Save johnlindquist/0547341e60fb2088599104bab2726838 to your computer and use it in GitHub Desktop.
Vercel AI Gateway OIDC Authentication Failure in Workflow DevKit - Root Cause Analysis

Vercel AI Gateway OIDC Authentication Failure in Workflow DevKit

Executive Summary

When using the Vercel AI SDK with Workflow DevKit, AI Gateway authentication fails with GatewayAuthenticationError even when VERCEL_OIDC_TOKEN is present in the environment. The root cause is that the @vercel/oidc package requires Node.js fs module for token validation/refresh, which is not available in the workflow sandbox.

Environment

  • AI SDK Version: 6.0.50 (also tested with 5.0.76)
  • @ai-sdk/gateway: 3.0.23
  • @vercel/oidc: 3.0.5
  • Workflow DevKit: 4.0.1-beta.44
  • Date Discovered: January 26, 2026

Error Message

GatewayAuthenticationError: Unauthenticated request to AI Gateway.

To authenticate, set the AI_GATEWAY_API_KEY environment variable with your API key.

Alternatively, you can use a provider module instead of the AI Gateway.

Reproduction

import { generateText } from 'ai';
import { fetch } from 'workflow';

export async function myWorkflow(input: string) {
  'use workflow';

  // This is the documented pattern but it FAILS
  globalThis.fetch = fetch;

  const { text } = await generateText({
    model: 'openai/o4-mini',
    prompt: input,
  });
}

Investigation Findings

What We Verified

Check Result
process.env available in workflow ✅ Yes (81 keys)
VERCEL_OIDC_TOKEN in workflow env ✅ SET (1195 chars)
globalThis.fetch override works ✅ Applied successfully
Token still set at error time ✅ STILL SET
AI SDK call succeeds ❌ FAILED

Key Insight

The workflow sandbox DOES have access to process.env including VERCEL_OIDC_TOKEN. The issue is NOT missing environment variables.

Root Cause Analysis

Authentication Flow

The AI SDK's gateway authentication flows through these packages:

ai (generateText)
  └── @ai-sdk/gateway (getGatewayAuthToken)
        └── @vercel/oidc (getVercelOidcToken)

The Problem in @vercel/oidc

File: @vercel/oidc/dist/get-vercel-oidc-token.js

async function getVercelOidcToken() {
  let token = "";
  try {
    token = getVercelOidcTokenSync();  // Gets token from process.env ✅
  } catch (error) { /* ... */ }

  try {
    // Dynamic imports that use fs module
    const [{ getTokenPayload, isExpired }, { refreshToken }] = await Promise.all([
      await import("./token-util.js"),  // ⚠️ Uses fs module
      await import("./token.js")
    ]);

    // Check if token needs refresh
    if (!token || isExpired(getTokenPayload(token))) {
      await refreshToken();  // ❌ FAILS - needs fs
    }
  } catch (error) {
    throw new VercelOidcTokenError(`Failed to refresh OIDC token`, error);
  }
}

File: @vercel/oidc/dist/token-util.js

var fs = __toESM(require("fs"));  // ❌ Not available in workflow sandbox
var path = __toESM(require("path"));

function findProjectInfo() {
  const dir = findRootDir();
  const prjPath = path.join(dir, ".vercel", "project.json");
  if (!fs.existsSync(prjPath)) {  // ❌ fs.existsSync fails in sandbox
    throw new VercelOidcTokenError("project.json not found");
  }
  // ...
}

function loadToken(projectId) {
  // ...
  const tokenPath = path.join(dir, "com.vercel.token", `${projectId}.json`);
  if (!fs.existsSync(tokenPath)) {  // ❌ fs.existsSync fails in sandbox
    return null;
  }
  // ...
}

Failure Chain

  1. ✅ Token is read from process.env.VERCEL_OIDC_TOKEN
  2. @vercel/oidc checks if token is expired using isExpired(getTokenPayload(token))
  3. ⚠️ If expired (or validation needed), refreshToken() is called
  4. refreshToken()findProjectInfo() → requires fs module
  5. fs module is NOT available in workflow sandbox (by design - for determinism)
  6. ❌ Error is thrown → caught → wrapped as GatewayAuthenticationError

Workarounds

Workaround 1: Use 'use step' Functions (Recommended)

Step functions run in the full Node.js context where fs is available:

import { generateText } from 'ai';

// Step function has full Node.js access
async function generateWithAI(prompt: string) {
  'use step';
  const { text } = await generateText({
    model: 'openai/o4-mini',
    prompt,
  });
  return text;
}

export async function myWorkflow(input: string) {
  'use workflow';

  // Call the step function - auth works
  const result = await generateWithAI(input);
}

Workaround 2: Use API Key Instead of OIDC

Use AI_GATEWAY_API_KEY instead of OIDC tokens:

# .env.local
AI_GATEWAY_API_KEY=your_key_here

This bypasses the OIDC token validation flow entirely.

Suggested Fix for @vercel/oidc

The @vercel/oidc package should detect when running in a restricted environment (like workflow sandbox) and gracefully handle the case where fs is unavailable:

async function getVercelOidcToken() {
  let token = "";
  try {
    token = getVercelOidcTokenSync();
  } catch (error) { /* ... */ }

  // If we have a token from env, try to use it directly first
  if (token) {
    try {
      const { getTokenPayload, isExpired } = await import("./token-util.js");
      if (!isExpired(getTokenPayload(token))) {
        return token;  // Token is valid, use it directly
      }
    } catch (error) {
      // fs not available (e.g., workflow sandbox)
      // Return the token anyway and let the server validate it
      return token;
    }
  }

  // Only attempt refresh if we can access fs
  try {
    const { refreshToken } = await import("./token.js");
    await refreshToken();
    token = getVercelOidcTokenSync();
  } catch (error) {
    if (token) {
      // We have a token but can't refresh - use what we have
      return token;
    }
    throw new VercelOidcTokenError(`Failed to refresh OIDC token`, error);
  }

  return token;
}

Alternatively, the workflow runtime could:

  1. Pre-validate tokens before workflow execution starts
  2. Inject a mock fs that returns appropriate values for token operations
  3. Provide a workflow-specific OIDC token getter that doesn't require filesystem access

Impact

  • All AI SDK calls using gateway models in workflow functions fail
  • Affects both generateText and generateObject
  • Users must restructure code to use step functions for all AI calls
  • This is not documented and the documented globalThis.fetch = fetch pattern does not work

Related Files

  • @vercel/oidc/dist/get-vercel-oidc-token.js - Token retrieval
  • @vercel/oidc/dist/token-util.js - Uses fs for token validation
  • @vercel/oidc/dist/token.js - Uses fs for token refresh
  • @ai-sdk/gateway/src/gateway-provider.ts - Calls getVercelOidcToken
  • ai/src/prompt/wrap-gateway-error.ts - Error wrapping

Conclusion

The documented pattern of using globalThis.fetch = fetch in workflow functions does not work for AI Gateway authentication because the underlying @vercel/oidc package requires filesystem access that is not available in the workflow sandbox. The workaround is to move all AI SDK calls to step functions which have full Node.js runtime access.


Report generated: January 26, 2026 Investigated by: Claude (Opus 4.5) with user verification

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment