fix(auth): keep resume lane warning non-blocking

- extract account resume lane guidance into a dedicated auth helper

- swallow diagnostic failures so account launches still reach execClaude

- add focused tests for the warning success and failure paths
This commit is contained in:
Tam Nhu Tran
2026-04-04 12:31:19 -04:00
parent 2830c2ae9e
commit 1b3528f61a
3 changed files with 118 additions and 42 deletions
+64
View File
@@ -0,0 +1,64 @@
import * as path from 'path';
import { info, warn } from '../utils/ui';
import {
parseResumeFlagIntent,
resolveRuntimePlainCcsResumeLane,
type ResumeLaneSummary,
} from './resume-lane-diagnostics';
interface ResumeLaneWarningDependencies {
resolvePlainLane?: () => Promise<ResumeLaneSummary>;
log?: (message: string) => void;
debug?: boolean;
}
export async function maybeWarnAboutResumeLaneMismatch(
profileName: string,
accountConfigDir: string,
args: string[],
deps: ResumeLaneWarningDependencies = {}
): Promise<void> {
const resumeIntent = parseResumeFlagIntent(args);
if (!resumeIntent) {
return;
}
const log = deps.log ?? console.error;
try {
const plainLane = await (deps.resolvePlainLane ?? resolveRuntimePlainCcsResumeLane)();
if (path.resolve(plainLane.configDir) === path.resolve(accountConfigDir)) {
return;
}
log(
warn(
`Resume for account "${profileName}" will search that account lane, not the current plain ccs lane.`
)
);
log(info(` Account lane: ${accountConfigDir}`));
log(info(` Plain ccs lane: ${plainLane.label} (${plainLane.configDir})`));
if (resumeIntent.explicitSessionId) {
log(
info(
' This explicit session ID may have been created in a different lane, so Claude may not find it here.'
)
);
}
log(info(' Recover the original lane first: ccs -r'));
log(info(' Back it up before changing setup: ccs auth backup default'));
log(
info(` For future work, align plain ccs with this account: ccs auth default ${profileName}`)
);
log('');
} catch (error) {
log(
warn(
'Resume lane guidance skipped because diagnostics failed; continuing with the account lane.'
)
);
if (deps.debug ?? Boolean(process.env.CCS_DEBUG)) {
log(info(` Diagnostic error: ${(error as Error).message}`));
}
}
}
+1 -42
View File
@@ -1,7 +1,6 @@
import './utils/fetch-proxy-setup';
import * as fs from 'fs';
import * as path from 'path';
import { detectClaudeCli } from './utils/claude-detector';
import {
getSettingsPath,
@@ -65,10 +64,7 @@ import { tryHandleRootCommand } from './commands/root-command-router';
// Import extracted utility functions
import { execClaude } from './utils/shell-executor';
import { isDeprecatedGlmtProfileName, normalizeDeprecatedGlmtEnv } from './utils/glmt-deprecation';
import {
parseResumeFlagIntent,
resolveRuntimePlainCcsResumeLane,
} from './auth/resume-lane-diagnostics';
import { maybeWarnAboutResumeLaneMismatch } from './auth/resume-lane-warning';
// Import target adapter system
import {
@@ -264,43 +260,6 @@ function shouldPassthroughNativeCodexFlagCommand(args: string[]): boolean {
return getNativeCodexPassthroughArgs(args) !== null;
}
async function maybeWarnAboutResumeLaneMismatch(
profileName: string,
accountConfigDir: string,
args: string[]
): Promise<void> {
const resumeIntent = parseResumeFlagIntent(args);
if (!resumeIntent) {
return;
}
const plainLane = await resolveRuntimePlainCcsResumeLane();
if (path.resolve(plainLane.configDir) === path.resolve(accountConfigDir)) {
return;
}
console.error(
warn(
`Resume for account "${profileName}" will search that account lane, not the current plain ccs lane.`
)
);
console.error(info(` Account lane: ${accountConfigDir}`));
console.error(info(` Plain ccs lane: ${plainLane.label} (${plainLane.configDir})`));
if (resumeIntent.explicitSessionId) {
console.error(
info(
` This explicit session ID may have been created in a different lane, so Claude may not find it here.`
)
);
}
console.error(info(` Recover the original lane first: ccs -r`));
console.error(info(` Back it up before changing setup: ccs auth backup default`));
console.error(
info(` For future work, align plain ccs with this account: ccs auth default ${profileName}`)
);
console.error('');
}
function getNativeCodexPassthroughArgs(args: string[]): string[] | null {
const targetArgs = stripTargetFlag(args);
if (resolveTargetType(args) !== 'codex' || targetArgs.length === 0) {
@@ -0,0 +1,53 @@
import { describe, expect, it } from 'bun:test';
import { maybeWarnAboutResumeLaneMismatch } from '../../../src/auth/resume-lane-warning';
describe('resume lane warning', () => {
it('prints guidance when the plain ccs lane differs from the account lane', async () => {
const logs: string[] = [];
await maybeWarnAboutResumeLaneMismatch('work', '/tmp/account-lane', ['--resume'], {
log: (message) => logs.push(message),
resolvePlainLane: async () => ({
kind: 'native',
label: 'native Claude lane',
configDir: '/tmp/native-lane',
projectCount: 12,
}),
});
expect(logs[0]).toContain('Resume for account "work" will search that account lane');
expect(logs).toContain('[i] Account lane: /tmp/account-lane');
expect(logs).toContain('[i] Plain ccs lane: native Claude lane (/tmp/native-lane)');
expect(logs).toContain('[i] Recover the original lane first: ccs -r');
});
it('does not log anything when resume is not requested', async () => {
const logs: string[] = [];
await maybeWarnAboutResumeLaneMismatch('work', '/tmp/account-lane', ['hello'], {
log: (message) => logs.push(message),
resolvePlainLane: async () => {
throw new Error('should not be called');
},
});
expect(logs).toHaveLength(0);
});
it('swallows diagnostic failures and keeps the warning path non-fatal', async () => {
const logs: string[] = [];
await expect(
maybeWarnAboutResumeLaneMismatch('work', '/tmp/account-lane', ['-r'], {
debug: true,
log: (message) => logs.push(message),
resolvePlainLane: async () => {
throw new Error('broken config');
},
})
).resolves.toBeUndefined();
expect(logs[0]).toContain('Resume lane guidance skipped because diagnostics failed');
expect(logs[1]).toContain('Diagnostic error: broken config');
});
});