fix: avoid Claude token refresh races across services
This commit is contained in:
11
src/index.ts
11
src/index.ts
@@ -66,6 +66,7 @@ import { normalizeStoredSeqCursor } from './message-cursor.js';
|
|||||||
import { initCodexTokenRotation } from './codex-token-rotation.js';
|
import { initCodexTokenRotation } from './codex-token-rotation.js';
|
||||||
import { initTokenRotation } from './token-rotation.js';
|
import { initTokenRotation } from './token-rotation.js';
|
||||||
import {
|
import {
|
||||||
|
shouldStartTokenRefreshLoop,
|
||||||
startTokenRefreshLoop,
|
startTokenRefreshLoop,
|
||||||
stopTokenRefreshLoop,
|
stopTokenRefreshLoop,
|
||||||
} from './token-refresh.js';
|
} from './token-refresh.js';
|
||||||
@@ -300,8 +301,16 @@ async function main(): Promise<void> {
|
|||||||
initTokenRotation();
|
initTokenRotation();
|
||||||
initCodexTokenRotation();
|
initCodexTokenRotation();
|
||||||
|
|
||||||
// Start OAuth token auto-refresh (before subsystems that spawn agents)
|
// Only the Claude service owns Claude OAuth refresh to avoid
|
||||||
|
// cross-service refresh-token races on shared credentials.
|
||||||
|
if (shouldStartTokenRefreshLoop(SERVICE_AGENT_TYPE)) {
|
||||||
startTokenRefreshLoop();
|
startTokenRefreshLoop();
|
||||||
|
} else {
|
||||||
|
logger.info(
|
||||||
|
{ serviceAgentType: SERVICE_AGENT_TYPE },
|
||||||
|
'Skipping Claude OAuth token auto-refresh for non-Claude service',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
loadState();
|
loadState();
|
||||||
|
|
||||||
|
|||||||
13
src/token-refresh.test.ts
Normal file
13
src/token-refresh.test.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { shouldStartTokenRefreshLoop } from './token-refresh.js';
|
||||||
|
|
||||||
|
describe('shouldStartTokenRefreshLoop', () => {
|
||||||
|
it('starts refresh for the Claude service', () => {
|
||||||
|
expect(shouldStartTokenRefreshLoop('claude-code')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('skips refresh for the Codex service', () => {
|
||||||
|
expect(shouldStartTokenRefreshLoop('codex')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -19,6 +19,7 @@ import path from 'path';
|
|||||||
import { logger } from './logger.js';
|
import { logger } from './logger.js';
|
||||||
import { DATA_DIR } from './config.js';
|
import { DATA_DIR } from './config.js';
|
||||||
import { getAllTokens, updateTokenValue } from './token-rotation.js';
|
import { getAllTokens, updateTokenValue } from './token-rotation.js';
|
||||||
|
import type { AgentType } from './types.js';
|
||||||
|
|
||||||
const TOKEN_URL = 'https://api.anthropic.com/v1/oauth/token';
|
const TOKEN_URL = 'https://api.anthropic.com/v1/oauth/token';
|
||||||
const CLIENT_ID = '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
|
const CLIENT_ID = '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
|
||||||
@@ -34,6 +35,12 @@ const CHECK_INTERVAL_MS = 5 * 60 * 1000;
|
|||||||
const REFRESH_BEFORE_EXPIRY_MS = 30 * 60 * 1000;
|
const REFRESH_BEFORE_EXPIRY_MS = 30 * 60 * 1000;
|
||||||
const REQUEST_TIMEOUT_MS = 15_000;
|
const REQUEST_TIMEOUT_MS = 15_000;
|
||||||
|
|
||||||
|
export function shouldStartTokenRefreshLoop(
|
||||||
|
serviceAgentType: AgentType,
|
||||||
|
): boolean {
|
||||||
|
return serviceAgentType === 'claude-code';
|
||||||
|
}
|
||||||
|
|
||||||
interface OAuthCredentials {
|
interface OAuthCredentials {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user