mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-13 14:15:56 +00:00
fix(cliproxy): warn gemini and agy users about issue 509
This commit is contained in:
1 parent
63f1bacfbe
commit
476eabe08f
5 files changed
+113
-4
No files matched your search
@@ -16,8 +16,13 @@ import { CLIProxyProvider } from './types';
|
||||
import { loadAccountsRegistry, pauseAccount, resumeAccount } from './accounts/registry';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
|
||||
const ISSUE_509_URL = 'https://github.com/kaitranntt/ccs/issues/509';
|
||||
|
||||
/** Providers that use Google OAuth (ban risk when overlapping) */
|
||||
const GOOGLE_OAUTH_PROVIDERS: CLIProxyProvider[] = ['gemini', 'agy', 'codex'];
|
||||
/** Providers that should display direct CLI warnings for #509 */
|
||||
const BAN_WARNING_PROVIDERS: CLIProxyProvider[] = ['gemini', 'agy'];
|
||||
const shownBanWarnings = new Set<CLIProxyProvider>();
|
||||
|
||||
// --- Auto-pause persistence (crash recovery) ---
|
||||
|
||||
@@ -161,6 +166,10 @@ export function warnCrossProviderDuplicates(provider: CLIProxyProvider): boolean
|
||||
console.error('');
|
||||
console.error(warn('Account safety: cross-provider duplicate detected'));
|
||||
console.error(' Same Google account across providers risks account bans (ref: #509).');
|
||||
console.error(
|
||||
' If provider requests start returning 403/Forbidden, treat it as a possible ban.'
|
||||
);
|
||||
console.error(` Details: ${ISSUE_509_URL}`);
|
||||
console.error('');
|
||||
|
||||
for (const [email, providers] of duplicates) {
|
||||
@@ -188,10 +197,67 @@ export function warnNewAccountConflict(
|
||||
` ${maskEmail(email)} is also registered under: ${conflictingProviders.join(', ')}`
|
||||
);
|
||||
console.error(' Concurrent usage may cause Google to ban your account.');
|
||||
console.error(' 403/Forbidden responses can be an early sign of account disablement.');
|
||||
console.error(' Consider pausing the duplicate or using a different account.');
|
||||
console.error(` Details: ${ISSUE_509_URL}`);
|
||||
console.error('');
|
||||
}
|
||||
|
||||
function isBanWarningProvider(provider: CLIProxyProvider): boolean {
|
||||
return BAN_WARNING_PROVIDERS.includes(provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show one-time warning for known OAuth ban risk providers.
|
||||
*/
|
||||
export function warnOAuthBanRisk(provider: CLIProxyProvider): void {
|
||||
if (!isBanWarningProvider(provider) || shownBanWarnings.has(provider)) return;
|
||||
|
||||
shownBanWarnings.add(provider);
|
||||
console.error('');
|
||||
console.error(warn('Account safety warning (#509)'));
|
||||
console.error(
|
||||
' Using the same Google account in both "ccs gemini" and "ccs agy" can trigger suspension.'
|
||||
);
|
||||
console.error(
|
||||
' If you see 403/Forbidden during provider calls, treat it as likely account disable/ban.'
|
||||
);
|
||||
console.error(
|
||||
' Use separate Google accounts per provider and stop retrying blocked accounts.'
|
||||
);
|
||||
console.error(` Details: ${ISSUE_509_URL}`);
|
||||
console.error('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect whether an error message contains a likely 403/Forbidden ban signal.
|
||||
*/
|
||||
export function isPossible403BanSignal(errorMessage: string): boolean {
|
||||
const lower = errorMessage.toLowerCase();
|
||||
return lower.includes('403') || lower.includes('forbidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show targeted warning when OAuth provider errors include 403/Forbidden.
|
||||
* Returns true when warning was emitted.
|
||||
*/
|
||||
export function warnPossible403Ban(provider: CLIProxyProvider, errorMessage: string): boolean {
|
||||
if (!isBanWarningProvider(provider) || !isPossible403BanSignal(errorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
console.error('');
|
||||
console.error(warn(`Account safety: ${provider} returned 403/Forbidden`));
|
||||
console.error(
|
||||
' For gemini/agy flows this often means the Google account was blocked/disabled.'
|
||||
);
|
||||
console.error(' Stop retries for this account and switch to a different account/provider.');
|
||||
console.error(` Details: ${ISSUE_509_URL}`);
|
||||
console.error(` Error: "${truncate(errorMessage, 160)}"`);
|
||||
console.error('');
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Enforcement: auto-pause/restore ---
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,7 +44,12 @@ import { getProviderTokenDir, isAuthenticated, registerAccountFromToken } from '
|
||||
import { executeOAuthProcess } from './oauth-process';
|
||||
import { importKiroToken } from './kiro-import';
|
||||
import { getProxyTarget, buildProxyUrl, buildManagementHeaders } from '../proxy-target-resolver';
|
||||
import { checkNewAccountConflict, warnNewAccountConflict } from '../account-safety';
|
||||
import {
|
||||
checkNewAccountConflict,
|
||||
warnNewAccountConflict,
|
||||
warnOAuthBanRisk,
|
||||
warnPossible403Ban,
|
||||
} from '../account-safety';
|
||||
|
||||
/**
|
||||
* Prompt user to add another account
|
||||
@@ -278,7 +283,9 @@ async function handlePasteCallbackMode(
|
||||
});
|
||||
|
||||
if (!startResponse.ok) {
|
||||
const startError = `OAuth start failed with status ${startResponse.status}`;
|
||||
console.log(fail('Failed to start OAuth flow'));
|
||||
warnPossible403Ban(provider, startError);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -380,7 +387,10 @@ async function handlePasteCallbackMode(
|
||||
};
|
||||
|
||||
if (!callbackResponse.ok || callbackData.status === 'error') {
|
||||
console.log(fail(callbackData.error || 'OAuth callback failed'));
|
||||
const callbackError =
|
||||
callbackData.error || `OAuth callback failed with status ${callbackResponse.status}`;
|
||||
console.log(fail(callbackError));
|
||||
warnPossible403Ban(provider, callbackError);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -417,6 +427,7 @@ export async function triggerOAuth(
|
||||
options: OAuthOptions = {}
|
||||
): Promise<AccountInfo | null> {
|
||||
const oauthConfig = getOAuthConfig(provider);
|
||||
warnOAuthBanRisk(provider);
|
||||
const { verbose = false, add = false, fromUI = false, noIncognito = true } = options;
|
||||
let { nickname } = options;
|
||||
const resolvedKiroMethod =
|
||||
|
||||
@@ -67,6 +67,7 @@ import { checkOrJoinProxy, registerProxySession, setupCleanupHandlers } from './
|
||||
import { parseThinkingOverride } from './thinking-arg-parser';
|
||||
import {
|
||||
warnCrossProviderDuplicates,
|
||||
warnOAuthBanRisk,
|
||||
cleanupStaleAutoPauses,
|
||||
enforceProviderIsolation,
|
||||
restoreAutoPausedAccounts,
|
||||
@@ -187,6 +188,7 @@ export async function execClaudeWithCLIProxy(
|
||||
|
||||
const providerConfig = getProviderConfig(provider);
|
||||
log(`Provider: ${providerConfig.displayName}`);
|
||||
warnOAuthBanRisk(provider);
|
||||
|
||||
// Check remote proxy if configured
|
||||
let useRemoteProxy = false;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
import { fail, warn, info } from '../../utils/ui';
|
||||
import { CLIProxyProvider } from '../types';
|
||||
import { handleBanDetection } from '../account-safety';
|
||||
import { handleBanDetection, warnPossible403Ban } from '../account-safety';
|
||||
import { CompositeTierConfig } from '../../config/unified-config-types';
|
||||
|
||||
/**
|
||||
@@ -59,6 +59,7 @@ export async function handleTokenExpiration(
|
||||
if (account) {
|
||||
handleBanDetection(provider, account.id, tokenResult.error);
|
||||
}
|
||||
warnPossible403Ban(provider, tokenResult.error);
|
||||
}
|
||||
|
||||
// Token expired and refresh failed - trigger re-auth
|
||||
|
||||
Reference in new issue
Block a user