mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-17 06:17:09 +00:00
fix(cliproxy): warn gemini and agy users about issue 509
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -8,9 +8,10 @@ import { useState, useMemo } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { Check, X, RefreshCw, Sparkles, Zap, GitBranch, Trash2 } from 'lucide-react';
|
||||
import { Check, X, RefreshCw, Sparkles, Zap, GitBranch, Trash2, AlertTriangle } from 'lucide-react';
|
||||
import { QuickSetupWizard } from '@/components/quick-setup-wizard';
|
||||
import { AddAccountDialog } from '@/components/account/add-account-dialog';
|
||||
import { ProviderEditor } from '@/components/cliproxy/provider-editor';
|
||||
@@ -243,6 +244,10 @@ export function CliproxyPage() {
|
||||
const parentAuthForVariant = selectedVariantData
|
||||
? providers.find((p) => p.provider === selectedVariantData.provider)
|
||||
: undefined;
|
||||
const warningProvider = (selectedVariantData?.provider || selectedStatus?.provider || '')
|
||||
.toLowerCase()
|
||||
.trim();
|
||||
const showAccountSafetyWarning = warningProvider === 'gemini' || warningProvider === 'agy';
|
||||
|
||||
const handleRefresh = () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['cliproxy'] });
|
||||
@@ -389,6 +394,30 @@ export function CliproxyPage() {
|
||||
|
||||
{/* Right Panel */}
|
||||
<div className="flex-1 flex flex-col min-w-0 bg-background">
|
||||
{showAccountSafetyWarning && (
|
||||
<div className="px-4 pt-4">
|
||||
<Alert variant="warning" className="py-2">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<AlertTitle>Account Safety Warning (Issue #509)</AlertTitle>
|
||||
<AlertDescription>
|
||||
Using the same Google account in both <code className="font-mono">ccs gemini</code>{' '}
|
||||
and <code className="font-mono">ccs agy</code> can trigger suspension. If CLI shows{' '}
|
||||
<code className="font-mono">403 Forbidden</code>, treat it as likely account
|
||||
disable/ban and switch accounts.{' '}
|
||||
<a
|
||||
href="https://github.com/kaitranntt/ccs/issues/509"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="underline font-medium"
|
||||
>
|
||||
Read issue #509
|
||||
</a>
|
||||
.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedVariantData && parentAuthForVariant ? (
|
||||
// Variant selected - show ProviderEditor with variant profile name
|
||||
<ProviderEditor
|
||||
|
||||
Reference in New Issue
Block a user