From 191396149ffa14d5bd6a59de91a594c6faabb6eb Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Mon, 22 Jun 2026 11:39:55 -0400 Subject: [PATCH] fix(auth): de-duplicate error output and validate legacy profiles in default exitWithError() already prints '[X] ', so the preceding console.log(fail()) calls double-printed every auth error. Remove the redundant logs and fold richer hints (e.g. --force) into the single exitWithError message. Also make 'auth default' accept a profile registered only in legacy profiles.json under unified mode. --- src/auth/commands/backup-command.ts | 3 +-- src/auth/commands/create-command.ts | 28 ++++++++++++-------------- src/auth/commands/default-command.ts | 1 - src/auth/commands/remove-command.ts | 4 +--- src/auth/commands/resources-command.ts | 3 +-- src/auth/commands/show-command.ts | 3 +-- src/auth/commands/types.ts | 3 +-- src/auth/profile-registry.ts | 12 ++++++++--- 8 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/auth/commands/backup-command.ts b/src/auth/commands/backup-command.ts index f6fe167d..8aa8b595 100644 --- a/src/auth/commands/backup-command.ts +++ b/src/auth/commands/backup-command.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; -import { initUI, color, dim, fail, info, ok } from '../../utils/ui'; +import { initUI, color, dim, info, ok } from '../../utils/ui'; import { exitWithError } from '../../errors'; import { ExitCode } from '../../errors/exit-codes'; import { @@ -44,7 +44,6 @@ export async function handleBackup(ctx: CommandContext, args: string[]): Promise }); if (!profileName) { - console.log(fail('Profile name is required')); console.log(''); console.log(`Usage: ${color('ccs auth backup [--json]', 'command')}`); exitWithError('Profile name is required', ExitCode.PROFILE_ERROR); diff --git a/src/auth/commands/create-command.ts b/src/auth/commands/create-command.ts index 05eacc4a..ee78ffba 100644 --- a/src/auth/commands/create-command.ts +++ b/src/auth/commands/create-command.ts @@ -53,7 +53,6 @@ export async function handleCreate(ctx: CommandContext, args: string[]): Promise }); if (!profileName) { - console.log(fail('Profile name is required')); console.log(''); console.log( `Usage: ${color('ccs auth create [--force] [--bare] [--share-context] [--context-group ] [--deeper-continuity]', 'command')}` @@ -65,20 +64,21 @@ export async function handleCreate(ctx: CommandContext, args: string[]): Promise } if (!isValidAccountProfileName(profileName)) { - const error = - 'Invalid profile name. Use letters/numbers/dash/underscore and start with a letter.'; - console.log(fail(error)); - console.log(''); - exitWithError(error, ExitCode.PROFILE_ERROR); + exitWithError( + 'Invalid profile name. Use letters/numbers/dash/underscore and start with a letter.', + ExitCode.PROFILE_ERROR + ); } // Check if profile already exists (check both legacy and unified) const existsLegacy = ctx.registry.hasProfile(profileName); const existsUnified = ctx.registry.hasAccountUnified(profileName); if (!force && (existsLegacy || existsUnified)) { - console.log(fail(`Profile already exists: ${profileName}`)); - console.log(` Use ${color('--force', 'command')} to overwrite`); - exitWithError(`Profile already exists: ${profileName}`, ExitCode.PROFILE_ERROR); + // Keep the --force hint in the exitWithError message so it appears in the single output line + exitWithError( + `Profile already exists: ${profileName}\n Use --force to overwrite`, + ExitCode.PROFILE_ERROR + ); } const normalizedName = sanitizeProfileNameForInstance(profileName); @@ -87,10 +87,10 @@ export async function handleCreate(ctx: CommandContext, args: string[]): Promise ); if (collidingName) { - const error = `Profile "${profileName}" conflicts with existing profile "${collidingName}" on filesystem.`; - console.log(fail(error)); - console.log(''); - exitWithError(error, ExitCode.PROFILE_ERROR); + exitWithError( + `Profile "${profileName}" conflicts with existing profile "${collidingName}" on filesystem.`, + ExitCode.PROFILE_ERROR + ); } const resolvedContext = resolveCreateAccountContext({ @@ -100,8 +100,6 @@ export async function handleCreate(ctx: CommandContext, args: string[]): Promise }); if (resolvedContext.error) { - console.log(fail(resolvedContext.error)); - console.log(''); exitWithError(resolvedContext.error, ExitCode.PROFILE_ERROR); } diff --git a/src/auth/commands/default-command.ts b/src/auth/commands/default-command.ts index 8836aee1..4e3b6bc1 100644 --- a/src/auth/commands/default-command.ts +++ b/src/auth/commands/default-command.ts @@ -23,7 +23,6 @@ export async function handleDefault(ctx: CommandContext, args: string[]): Promis }); if (!profileName) { - console.log(fail('Profile name is required')); console.log(''); console.log(`Usage: ${color('ccs auth default ', 'command')}`); exitWithError('Profile name is required', ExitCode.PROFILE_ERROR); diff --git a/src/auth/commands/remove-command.ts b/src/auth/commands/remove-command.ts index fed45098..60f4bb3d 100644 --- a/src/auth/commands/remove-command.ts +++ b/src/auth/commands/remove-command.ts @@ -6,7 +6,7 @@ import * as fs from 'fs'; import * as path from 'path'; -import { initUI, color, ok, fail, info } from '../../utils/ui'; +import { initUI, color, ok, info } from '../../utils/ui'; import { InteractivePrompt } from '../../utils/prompt'; import { exitWithError } from '../../errors'; @@ -26,7 +26,6 @@ export async function handleRemove(ctx: CommandContext, args: string[]): Promise }); if (!profileName) { - console.log(fail('Profile name is required')); console.log(''); console.log(`Usage: ${color('ccs auth remove [--yes]', 'command')}`); exitWithError('Profile name is required', ExitCode.PROFILE_ERROR); @@ -37,7 +36,6 @@ export async function handleRemove(ctx: CommandContext, args: string[]): Promise const existsUnified = ctx.registry.hasAccountUnified(profileName); if (!existsLegacy && !existsUnified) { - console.log(fail(`Profile not found: ${profileName}`)); exitWithError(`Profile not found: ${profileName}`, ExitCode.PROFILE_ERROR); } diff --git a/src/auth/commands/resources-command.ts b/src/auth/commands/resources-command.ts index cf6666c3..af7e0bf0 100644 --- a/src/auth/commands/resources-command.ts +++ b/src/auth/commands/resources-command.ts @@ -1,4 +1,4 @@ -import { initUI, header, color, fail, ok, table } from '../../utils/ui'; +import { initUI, header, color, ok, table } from '../../utils/ui'; import { resolveAccountContextPolicy } from '../account-context'; import { isSharedResourceMode, @@ -30,7 +30,6 @@ export async function handleResources(ctx: CommandContext, args: string[]): Prom }); if (!profileName) { - console.log(fail('Profile name is required')); console.log(''); console.log( `Usage: ${color('ccs auth resources [--mode shared|profile-local] [--json]', 'command')}` diff --git a/src/auth/commands/show-command.ts b/src/auth/commands/show-command.ts index 37602688..2ae38136 100644 --- a/src/auth/commands/show-command.ts +++ b/src/auth/commands/show-command.ts @@ -6,7 +6,7 @@ import * as fs from 'fs'; import * as path from 'path'; -import { initUI, header, color, fail, table } from '../../utils/ui'; +import { initUI, header, color, table } from '../../utils/ui'; import { resolveAccountContextPolicy, formatAccountContextPolicy } from '../account-context'; import { describeSettingsSync, summarizeAccountHistory } from '../account-profile-diagnostics'; import { resolveConfiguredPlainCcsResumeLane } from '../resume-lane-diagnostics'; @@ -33,7 +33,6 @@ export async function handleShow(ctx: CommandContext, args: string[]): Promise [--json]', 'command')}`); exitWithError('Profile name is required', ExitCode.PROFILE_ERROR); diff --git a/src/auth/commands/types.ts b/src/auth/commands/types.ts index f091b410..b67d7298 100644 --- a/src/auth/commands/types.ts +++ b/src/auth/commands/types.ts @@ -8,7 +8,7 @@ import ProfileRegistry from '../profile-registry'; import { InstanceManager } from '../../management/instance-manager'; import { exitWithError } from '../../errors'; import { ExitCode } from '../../errors/exit-codes'; -import { color, fail } from '../../utils/ui'; +import { color } from '../../utils/ui'; // Re-export for backward compatibility export { formatRelativeTime } from '../../utils/time'; @@ -100,7 +100,6 @@ export function rejectUnsupportedAuthOptions( } const unknownList = unsupportedOptions.map((flag) => `"${flag}"`).join(', '); - console.log(fail(`Unknown option(s): ${unknownList}`)); console.log(''); console.log(`Usage: ${color(options.usage, 'command')}`); console.log(`Help: ${color('ccs auth --help', 'command')}`); diff --git a/src/auth/profile-registry.ts b/src/auth/profile-registry.ts index b910b298..22c19203 100644 --- a/src/auth/profile-registry.ts +++ b/src/auth/profile-registry.ts @@ -376,13 +376,19 @@ export class ProfileRegistry { } /** - * Set default profile in unified config + * Set default profile in unified config. + * Accepts names from unified config (accounts, profiles, cliproxy variants) + * as well as legacy profiles.json so that mixed-mode installs work correctly. */ setDefaultUnified(name: string): void { + // Check legacy registry outside the mutate callback to avoid re-reading inside the transaction. + const legacyData = this._read(); + const existsLegacy = !!legacyData.profiles[name]; + mutateConfig((config) => { - const exists = + const existsUnified = config.accounts[name] || config.profiles[name] || config.cliproxy?.variants?.[name]; - if (!exists) { + if (!existsUnified && !existsLegacy) { throw new ProfileError(`Profile not found: ${name}`, name); } config.default = name;