fix(cliproxy): warn gemini and agy users about issue 509

This commit is contained in:
Tam Nhu Tran
2026-02-19 10:12:57 +07:00
parent 63f1bacfbe
commit 476eabe08f
5 changed files with 113 additions and 4 deletions
+66
View File
@@ -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 ---
/**
+13 -2
View File
@@ -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 =
+2
View File
@@ -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;
+2 -1
View File
@@ -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
+30 -1
View File
@@ -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