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
- Re-login via Claude Desktop to mint a fresh token — the marker clears automatically on the next fresh injection.
- If the stale warning persists after re-login, verify the credential store Claude Desktop actually writes to is the one being read.
- 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
- Re-login via Claude Desktop as soon as the stale-token warning appears; don't wait for spawn failures.
- Never hardcode or export a stale CLAUDE_CODE_OAUTH_TOKEN by hand — let the resolver inject fresh tokens.
- Watch expiresAt in logs to anticipate refresh needs on long-lived machines.
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
- Worker port did not open after lazy-spawn within the cold-bo
- Worker failed to start before hook, handler will proceed gra
- Generator paused for ${abortCategory}; preserving buffered w
- Transcript path missing or file does not exist: ${transcript
- Transcript file exists but is empty: ${transcriptPath}
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/e560398d46b0e3b4.
Report an issue: GitHub.