thedotmack/claude-mem · warning

Refusing to inject expired CLAUDE_CODE_OAUTH_TOKEN: ${result

Error message

Refusing to inject expired CLAUDE_CODE_OAUTH_TOKEN: ${result.reason}. Re-login via Claude Desktop to refresh.

What it means

While assembling the isolated environment for a child spawn, EnvManager resolves the Claude OAuth token. If the token is present but expired (result.kind === 'expired'), it refuses to inject CLAUDE_CODE_OAUTH_TOKEN, logs the reason and expiresAt, and writes a stale-marker file so the session-start hook can surface a re-login prompt to the user. The child runs without the token.

Source

Thrown at src/shared/EnvManager.ts:276

      'OAUTH',
      'OAuth token read failed unexpectedly; proceeding without token',
      {},
      error instanceof Error ? error : new Error(String(error)),
    );
    return isolatedEnv;
  }

  switch (result.kind) {
    case 'present':
      isolatedEnv.CLAUDE_CODE_OAUTH_TOKEN = result.token;
      logger.info('OAUTH', 'Injected fresh CLAUDE_CODE_OAUTH_TOKEN at spawn-time', {
        source: result.source,
        expiresAt: result.expiresAt,
      });
      clearStaleMarker();
      break;
    case 'expired':
      logger.warn(
        'OAUTH',
        `Refusing to inject expired CLAUDE_CODE_OAUTH_TOKEN: ${result.reason}. Re-login via Claude Desktop to refresh.`,
        { expiresAt: result.expiresAt },
      );
      writeStaleMarker(result.reason);
      break;
    case 'absent':
      logger.debug('OAUTH', `No OAuth token available: ${result.reason}`);
      // Token is absent — any prior stale-marker would have been written
      // when the token was expired, but is no longer accurate now that the
      // token is gone. Clear it so the session-start hook stops surfacing
      // a stale "expired token, re-login" warning (CodeRabbit review on PR
      // #2282).
      clearStaleMarker();
      break;
  }

  return isolatedEnv;

View on GitHub (pinned to e2d1df569a)

Solutions

  1. Re-login via Claude Desktop to mint a fresh token — the marker clears automatically on the next fresh injection.
  2. If the stale warning persists after re-login, verify the credential store Claude Desktop actually writes to is the one being read.
  3. Recurring fast expiry: check system clock skew, since expiry is wall-clock based.

Example fix

// before
isolatedEnv.CLAUDE_CODE_OAUTH_TOKEN = process.env.CLAUDE_CODE_OAUTH_TOKEN; // may be expired

// after
const result = resolveOAuthToken();
if (result.kind === 'present') {
  isolatedEnv.CLAUDE_CODE_OAUTH_TOKEN = result.token;
} else if (result.kind === 'expired') {
  logger.warn('OAUTH', `Refusing to inject expired CLAUDE_CODE_OAUTH_TOKEN: ${result.reason}. Re-login via Claude Desktop to refresh.`, { expiresAt: result.expiresAt });
  writeStaleMarker(result.reason);
}
Defensive patterns

Strategy: validation

Validate before calling

const result = resolveOAuthToken();
if (result.kind === 'expired') {
  // surface re-login to the user instead of spawning unauthenticated
  return promptRelogin(result.reason);
}

Type guard

type TokenResult =
  | { kind: 'present'; token: string; source: string; expiresAt: number }
  | { kind: 'expired'; reason: string; expiresAt: number }
  | { kind: 'absent'; reason: string };

const isExpiredToken = (r: TokenResult): r is Extract<TokenResult, { kind: 'expired' }> =>
  r.kind === 'expired';

Prevention

When it happens

Trigger: The OAuth credential store holds a token whose expiresAt is in the past at spawn time; the 'expired' switch branch fires and writeStaleMarker(result.reason) records it.

Common situations: Claude Desktop login older than the token lifetime; user logged out or switched accounts; machine slept past expiry and resumed a long-lived worker.

Related errors


AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20). Data as JSON: /api/errors/e560398d46b0e3b4. Report an issue: GitHub.