fix(cliproxy): respect configured local port instead of hardcoding 8317

All call sites that spawn or probe CLIProxyApiPlus now read
cliproxy_server.local.port from config via resolveLifecyclePort()
instead of using the hardcoded CLIPROXY_DEFAULT_PORT constant.

- Move resolveLifecyclePort helper to src/cliproxy/config/port-manager.ts
- Fix 7 call sites: ccs.ts, config-command.ts, copilot-executor.ts,
  lifecycle.ts, binary-manager.ts, cliproxy-stats-routes.ts,
  cliproxy-local-proxy.ts
- Remove duplicate resolveLocalCliproxyPort helper
- Cache port resolution in /proxy-status handler to avoid repeated I/O

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Molko
2026-05-03 07:40:48 -04:00
co-authored by Claude Opus 4.7
parent 0e83c916f9
commit 509bd5dbef
11 changed files with 34 additions and 45 deletions
+3 -3
View File
@@ -22,7 +22,7 @@ import {
isAuthenticated,
} from './cliproxy';
import { getEffectiveEnvVars, getCompositeEnvVars } from './cliproxy/config/env-builder';
import { CLIPROXY_DEFAULT_PORT } from './cliproxy/config/port-manager';
import { resolveLifecyclePort } from './cliproxy/config/port-manager';
import {
ensureWebSearchMcpOrThrow,
displayWebSearchStatus,
@@ -929,7 +929,7 @@ async function main(): Promise<void> {
}
const customSettingsPath = profileInfo.settingsPath; // undefined for hardcoded profiles
const variantPort = profileInfo.port; // variant-specific port for isolation
const cliproxyPort = variantPort || CLIPROXY_DEFAULT_PORT;
const cliproxyPort = variantPort || resolveLifecyclePort();
if (resolvedTarget !== 'claude') {
const adapter = targetAdapter;
@@ -1385,7 +1385,7 @@ async function main(): Promise<void> {
};
} else if (imageAnalysisStatus.proxyReadiness === 'stopped') {
const ensureServiceResult = await ensureCliproxyService(
CLIPROXY_DEFAULT_PORT,
resolveLifecyclePort(),
verboseProxyLaunch
);
if (!ensureServiceResult.started) {
+3 -2
View File
@@ -8,7 +8,8 @@
import * as fs from 'fs';
import * as path from 'path';
import { info, warn } from '../utils/ui';
import { getBinDir, CLIPROXY_DEFAULT_PORT } from './config/config-generator';
import { getBinDir } from './config/config-generator';
import { resolveLifecyclePort } from './config/port-manager';
import { BinaryInfo, BinaryManagerConfig } from './types';
import {
BACKEND_CONFIG,
@@ -340,7 +341,7 @@ export async function installCliproxyVersion(
const result = await stopProxyFn();
if (result.stopped) {
// Wait for port to be fully released
const portFree = await waitForPortFreeFn(CLIPROXY_DEFAULT_PORT, 5000);
const portFree = await waitForPortFreeFn(resolveLifecyclePort(), 5000);
if (!portFree && verbose) {
console.log(formatWarn('Port did not free up in time, proceeding anyway...'));
}
+2 -2
View File
@@ -14,7 +14,7 @@ import {
import { downloadAndInstall, deleteBinary, getBinaryPath } from './installer';
import { info, warn } from '../../utils/ui';
import { isCliproxyRunning } from '../services/stats-fetcher';
import { CLIPROXY_DEFAULT_PORT } from '../config/config-generator';
import { resolveLifecyclePort } from '../config/port-manager';
import {
CLIPROXY_MAX_STABLE_VERSION,
CLIPROXY_FAULTY_RANGE,
@@ -82,7 +82,7 @@ async function handleAutoUpdate(config: BinaryManagerConfig, verbose: boolean):
return;
}
const proxyRunning = await isCliproxyRunning(CLIPROXY_DEFAULT_PORT);
const proxyRunning = await isCliproxyRunning(resolveLifecyclePort());
const latestNote = isAboveMaxStable(latestVersion) ? ` (latest v${latestVersion} unstable)` : '';
const updateMsg = `${backendLabel} update: v${currentVersion} -> v${targetVersion}${latestNote}`;
+13
View File
@@ -3,6 +3,9 @@
* Handles port number validation and default port resolution
*/
import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
import type { UnifiedConfig } from '../../config/unified-config-types';
/** Default CLIProxy port */
export const CLIPROXY_DEFAULT_PORT = 8317;
@@ -57,3 +60,13 @@ export function normalizeProtocol(protocol: string | undefined): 'http' | 'https
// Invalid protocol (e.g., 'ftp') - default to http
return 'http';
}
/**
* Resolve the local CLIProxy lifecycle port from unified config.
* Falls back to default port when unset/invalid.
*/
export function resolveLifecyclePort(
config: Pick<UnifiedConfig, 'cliproxy_server'> = loadOrCreateUnifiedConfig()
): number {
return validatePort(config.cliproxy_server?.local?.port ?? CLIPROXY_DEFAULT_PORT);
}
@@ -11,7 +11,7 @@
import { initUI, header, color, dim, ok, warn, info } from '../../utils/ui';
import { getProxyStatus, startProxy, stopProxy } from '../../cliproxy/services';
import { detectRunningProxy } from '../../cliproxy/proxy/proxy-detector';
import { resolveLifecyclePort } from './resolve-lifecycle-port';
import { resolveLifecyclePort } from '../../cliproxy/config/port-manager';
export async function handleStart(verbose = false): Promise<void> {
await initUI();
@@ -1,15 +0,0 @@
import { CLIPROXY_DEFAULT_PORT, validatePort } from '../../cliproxy/config/port-manager';
import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
import type { UnifiedConfig } from '../../config/unified-config-types';
type LifecyclePortConfig = Pick<UnifiedConfig, 'cliproxy_server'>;
/**
* Resolve the local CLIProxy lifecycle port from unified config.
* Falls back to default port when unset/invalid.
*/
export function resolveLifecyclePort(
config: LifecyclePortConfig = loadOrCreateUnifiedConfig()
): number {
return validatePort(config.cliproxy_server?.local?.port ?? CLIPROXY_DEFAULT_PORT);
}
+2 -2
View File
@@ -11,7 +11,7 @@ import open from 'open';
import { startServer } from '../web-server';
import { setupGracefulShutdown } from '../web-server/shutdown';
import { ensureCliproxyService } from '../cliproxy/service-manager';
import { CLIPROXY_DEFAULT_PORT } from '../cliproxy/config/config-generator';
import { resolveLifecyclePort } from '../cliproxy/config/port-manager';
import { getDashboardAuthConfig } from '../config/unified-config-loader';
import { initUI, header, ok, info, warn, fail } from '../utils/ui';
import { resolveNamedCommand, type NamedCommandRoute } from './named-command-router';
@@ -137,7 +137,7 @@ export async function handleConfigCommand(
// Ensure CLIProxy service is running for dashboard features
console.log(deps.info('Starting CLIProxy service...'));
const cliproxyResult = await deps.ensureCliproxyService(CLIPROXY_DEFAULT_PORT, verbose);
const cliproxyResult = await deps.ensureCliproxyService(resolveLifecyclePort(), verbose);
logger.info('cliproxy.ensure_result', 'Config command checked CLIProxy availability', {
started: cliproxyResult.started,
alreadyRunning: cliproxyResult.alreadyRunning,
+2 -2
View File
@@ -10,7 +10,7 @@ import { CopilotConfig } from '../config/unified-config-types';
import { getGlobalEnvConfig } from '../config/unified-config-loader';
import { ensureCliproxyService } from '../cliproxy';
import { getEffectiveApiKey } from '../cliproxy/auth/auth-token-manager';
import { CLIPROXY_DEFAULT_PORT } from '../cliproxy/config/port-manager';
import { resolveLifecyclePort } from '../cliproxy/config/port-manager';
import { checkAuthStatus, isCopilotApiInstalled } from './copilot-auth';
import { isDaemonRunning, startDaemon } from './copilot-daemon';
import { ensureCopilotApi } from './copilot-package-manager';
@@ -142,7 +142,7 @@ export async function resolveCopilotImageAnalysisEnv(
if (status.proxyReadiness === 'stopped') {
const ensureServiceResult = await resolvedDeps.ensureCliproxyService(
CLIPROXY_DEFAULT_PORT,
resolveLifecyclePort(),
verbose
);
if (!ensureServiceResult.started) {
+2 -12
View File
@@ -9,8 +9,7 @@
import http from 'http';
import { Request, Response, Router } from 'express';
import { CLIPROXY_DEFAULT_PORT, validatePort } from '../../cliproxy/config/port-manager';
import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
import { resolveLifecyclePort } from '../../cliproxy/config/port-manager';
import { requireLocalAccessWhenAuthDisabled } from '../middleware/auth-middleware';
export interface CliproxyLocalProxyDeps {
@@ -22,15 +21,6 @@ export interface CliproxyLocalProxyDeps {
/** Proxy request timeout in milliseconds (30 seconds) */
const PROXY_TIMEOUT_MS = 30_000;
function resolveLocalCliproxyPort(): number {
try {
const config = loadOrCreateUnifiedConfig();
return validatePort(config.cliproxy_server?.local?.port ?? CLIPROXY_DEFAULT_PORT);
} catch {
return CLIPROXY_DEFAULT_PORT;
}
}
function isJsonContentType(contentType: string | string[] | undefined): boolean {
const values = Array.isArray(contentType) ? contentType : [contentType];
return values.some((value) => value?.toLowerCase().includes('application/json') === true);
@@ -82,7 +72,7 @@ export function createCliproxyLocalProxyRouter(deps: CliproxyLocalProxyDeps = {}
'CLIProxy local proxy requires localhost access when dashboard auth is disabled.'
));
const createRequest = deps.request ?? http.request;
const resolveTargetPort = deps.resolveTargetPort ?? resolveLocalCliproxyPort;
const resolveTargetPort = deps.resolveTargetPort ?? resolveLifecyclePort;
router.use((req: Request, res: Response, next) => {
if (enforceAccess(req, res)) {
@@ -52,7 +52,7 @@ import {
CLIPROXY_MAX_STABLE_VERSION,
CLIPROXY_FAULTY_RANGE,
} from '../../cliproxy/binary/platform-detector';
import { CLIPROXY_DEFAULT_PORT } from '../../cliproxy/config/port-manager';
import { resolveLifecyclePort } from '../../cliproxy/config/port-manager';
import {
MODEL_ENV_VAR_KEYS,
canonicalizeModelIdForProvider,
@@ -321,7 +321,7 @@ router.get('/usage', handleStatsRequest);
*/
router.get('/status', async (_req: Request, res: Response): Promise<void> => {
try {
const running = await isCliproxyRunning();
const running = await isCliproxyRunning(resolveLifecyclePort());
res.json({ running });
} catch (error) {
console.error(`[cliproxy-stats] ${(error as Error).message}`);
@@ -345,15 +345,16 @@ router.get('/proxy-status', async (_req: Request, res: Response): Promise<void>
return;
}
const port = resolveLifecyclePort();
// Session tracker says not running, but proxy might be running without session tracking
// (e.g., started before session persistence was implemented)
const actuallyRunning = await isCliproxyRunning();
const actuallyRunning = await isCliproxyRunning(port);
if (actuallyRunning) {
// Proxy running but no session lock - legacy/untracked instance
res.json({
running: true,
port: CLIPROXY_DEFAULT_PORT,
port,
sessionCount: 0, // Unknown sessions
// No pid/startedAt since we don't have session lock
});
@@ -1,6 +1,5 @@
import { describe, expect, it } from 'bun:test';
import { CLIPROXY_DEFAULT_PORT } from '../../../src/cliproxy/config/port-manager';
import { resolveLifecyclePort } from '../../../src/commands/cliproxy/resolve-lifecycle-port';
import { CLIPROXY_DEFAULT_PORT, resolveLifecyclePort } from '../../../src/cliproxy/config/port-manager';
describe('resolveLifecyclePort', () => {
it('uses configured cliproxy_server.local.port', () => {