fix(auth): de-duplicate error output and validate legacy profiles in default

exitWithError() already prints '[X] <msg>', 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.
This commit is contained in:
Tam Nhu Tran committed 2026-06-22 12:06:58 -04:00
1 parent e44babd8cc
commit 191396149f
8 files changed
+27 -30

No files matched your search

+1 -2
View File
@@ -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 <profile|default> [--json]', 'command')}`);
exitWithError('Profile name is required', ExitCode.PROFILE_ERROR);
+13 -15
View File
@@ -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 <profile> [--force] [--bare] [--share-context] [--context-group <name>] [--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);
}
-1
View File
@@ -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 <profile>', 'command')}`);
exitWithError('Profile name is required', ExitCode.PROFILE_ERROR);
+1 -3
View File
@@ -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 <profile> [--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);
}
+1 -2
View File
@@ -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 <profile> [--mode shared|profile-local] [--json]', 'command')}`
+1 -2
View File
@@ -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<v
});
if (!profileName) {
console.log(fail('Profile name is required'));
console.log('');
console.log(`Usage: ${color('ccs auth show <profile> [--json]', 'command')}`);
exitWithError('Profile name is required', ExitCode.PROFILE_ERROR);
+1 -2
View File
@@ -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')}`);
+9 -3
View File
@@ -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;