fix(cliproxy): improve qwen oauth error handling (#33)

Fixes #29

- Update OAuth flow documentation (Qwen uses Device Code Flow, not callback-based)
- Remove incorrect port cleanup for Qwen (Device Code Flow needs no port)
- Fix error messages to show Qwen-specific troubleshooting tips
This commit is contained in:
Kai (Tam Nhu) Tran
2025-12-01 05:22:33 -05:00
committed by GitHub
parent 773225d180
commit 1c3374f6a7
7 changed files with 106 additions and 8 deletions
+21
View File
@@ -1,3 +1,24 @@
# [5.3.0-beta.2](https://github.com/kaitranntt/ccs/compare/v5.3.0-beta.1...v5.3.0-beta.2) (2025-12-01)
### Bug Fixes
* **cliproxy:** improve qwen oauth error handling ([7e0b0fe](https://github.com/kaitranntt/ccs/commit/7e0b0feca8ce2ed5d505c5bf6c84e54c6df8839e)), closes [#29](https://github.com/kaitranntt/ccs/issues/29)
# [5.3.0-beta.1](https://github.com/kaitranntt/ccs/compare/v5.2.0...v5.3.0-beta.1) (2025-12-01)
### Bug Fixes
* **cliproxy:** use double-dash flags for cliproxyapi auth ([#24](https://github.com/kaitranntt/ccs/issues/24)) ([4c81f28](https://github.com/kaitranntt/ccs/commit/4c81f28f0b67ef92cf74d0f5c13a5943ff0a7f00))
* **glmt:** handle 401 errors and headers-already-sent exception ([#30](https://github.com/kaitranntt/ccs/issues/30)) ([c953382](https://github.com/kaitranntt/ccs/commit/c95338232a37981b95b785b47185ce18d6d94b7a)), closes [#26](https://github.com/kaitranntt/ccs/issues/26)
### Features
* **cliproxy:** add qwen code oauth provider support ([#31](https://github.com/kaitranntt/ccs/issues/31)) ([a3f1e52](https://github.com/kaitranntt/ccs/commit/a3f1e52ac68600ba0806d67aacceb6477ffa3543)), closes [#29](https://github.com/kaitranntt/ccs/issues/29)
* **cliproxy:** auto-update cliproxyapi to latest release on startup ([8873ccd](https://github.com/kaitranntt/ccs/commit/8873ccd981679e8acff8965accdc22215c6e4aa2))
# [5.2.0](https://github.com/kaitranntt/ccs/compare/v5.1.1...v5.2.0) (2025-12-01)
+1 -1
View File
@@ -1 +1 @@
5.2.0
5.3.0-beta.2
+1 -1
View File
@@ -83,7 +83,7 @@ $InstallMethod = if ($ScriptDir -and ((Test-Path "$ScriptDir\lib\ccs.ps1") -or (
# IMPORTANT: Update this version when releasing new versions!
# This hardcoded version is used for standalone installations (irm | iex)
# For git installations, VERSION file is read if available
$CcsVersion = "5.2.0"
$CcsVersion = "5.3.0-beta.2"
# Try to read VERSION file for git installations
if ($ScriptDir) {
+1 -1
View File
@@ -84,7 +84,7 @@ fi
# IMPORTANT: Update this version when releasing new versions!
# This hardcoded version is used for standalone installations (curl | bash)
# For git installations, VERSION file is read if available
CCS_VERSION="5.2.0"
CCS_VERSION="5.3.0-beta.2"
# Try to read VERSION file for git installations
if [[ -f "$SCRIPT_DIR/VERSION" ]]; then
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@kaitranntt/ccs",
"version": "5.2.0",
"version": "5.3.0-beta.2",
"description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
"keywords": [
"cli",
+80 -3
View File
@@ -14,12 +14,72 @@
import * as fs from 'fs';
import * as path from 'path';
import { spawn } from 'child_process';
import { spawn, execSync } from 'child_process';
import { ProgressIndicator } from '../utils/progress-indicator';
import { getProviderAuthDir, generateConfig } from './config-generator';
import { ensureCLIProxyBinary } from './binary-manager';
import { CLIProxyProvider } from './types';
/**
* OAuth callback ports used by CLIProxyAPI (hardcoded in binary)
* See: https://github.com/router-for-me/CLIProxyAPI/tree/main/internal/auth
*
* OAuth flow types per provider:
* - Gemini: Authorization Code Flow with local callback server on port 8085
* - Codex: Authorization Code Flow with local callback server on port 1455
* - Agy: Authorization Code Flow with local callback server on port 51121
* - Qwen: Device Code Flow (polling-based, NO callback port needed)
*
* We auto-kill processes on callback ports before OAuth to avoid conflicts.
*/
const OAUTH_CALLBACK_PORTS: Partial<Record<CLIProxyProvider, number>> = {
gemini: 8085,
// codex uses 1455
// agy uses 51121
// qwen uses Device Code Flow - no callback port needed
};
/**
* Kill any process using a specific port
* Used to free OAuth callback port before authentication
*/
function killProcessOnPort(port: number, verbose: boolean): boolean {
try {
if (process.platform === 'win32') {
// Windows: use netstat + taskkill
const result = execSync(`netstat -ano | findstr :${port}`, {
encoding: 'utf-8',
stdio: 'pipe',
});
const lines = result.trim().split('\n');
for (const line of lines) {
const parts = line.trim().split(/\s+/);
const pid = parts[parts.length - 1];
if (pid && /^\d+$/.test(pid)) {
execSync(`taskkill /F /PID ${pid}`, { stdio: 'pipe' });
if (verbose) console.error(`[auth] Killed process ${pid} on port ${port}`);
}
}
return true;
} else {
// Unix: use lsof + kill
const result = execSync(`lsof -ti:${port}`, { encoding: 'utf-8', stdio: 'pipe' });
const pids = result
.trim()
.split('\n')
.filter((p) => p);
for (const pid of pids) {
execSync(`kill -9 ${pid}`, { stdio: 'pipe' });
if (verbose) console.error(`[auth] Killed process ${pid} on port ${port}`);
}
return pids.length > 0;
}
} catch {
// No process on port or command failed - that's fine
return false;
}
}
/**
* Detect if running in a headless environment (no browser available)
*/
@@ -328,6 +388,16 @@ export async function triggerOAuth(
const configPath = generateConfig(provider);
log(`Config generated: ${configPath}`);
// Free OAuth callback port if this provider shares it with another
// Qwen and Gemini both use port 8085 - kill any existing process to avoid conflict
const callbackPort = OAUTH_CALLBACK_PORTS[provider];
if (callbackPort) {
const killed = killProcessOnPort(callbackPort, verbose);
if (killed) {
log(`Freed port ${callbackPort} for OAuth callback`);
}
}
// Build args: config + auth flag + optional --no-browser for headless
const args = ['--config', configPath, oauthConfig.authFlag];
if (headless) {
@@ -428,8 +498,15 @@ export async function triggerOAuth(
} else {
if (!headless) spinner.fail('Authentication incomplete');
console.error('[X] Token not found after authentication');
console.error(' The OAuth flow may have been cancelled or port 8085 was in use');
console.error(' Try: pkill -f cli-proxy-api && ccs gemini --auth');
// Qwen uses Device Code Flow (polling), others use Authorization Code Flow (callback)
if (provider === 'qwen') {
console.error(' Qwen uses Device Code Flow - ensure you completed auth in browser');
console.error(' The polling may have timed out or been cancelled');
console.error(' Try: ccs qwen --auth --verbose');
} else {
console.error(' The OAuth flow may have been cancelled or callback port was in use');
console.error(` Try: pkill -f cli-proxy-api && ccs ${provider} --auth`);
}
resolve(false);
}
} else {
+1 -1
View File
@@ -11,7 +11,7 @@ import { PlatformInfo, SupportedOS, SupportedArch, ArchiveExtension } from './ty
* CLIProxyAPI fallback version (used when GitHub API unavailable)
* Auto-update fetches latest from GitHub; this is only a safety net
*/
export const CLIPROXY_FALLBACK_VERSION = '6.5.27';
export const CLIPROXY_FALLBACK_VERSION = '6.5.31';
/** @deprecated Use CLIPROXY_FALLBACK_VERSION instead */
export const CLIPROXY_VERSION = CLIPROXY_FALLBACK_VERSION;