diff --git a/src/auth/resume-lane-warning.ts b/src/auth/resume-lane-warning.ts new file mode 100644 index 00000000..ca59217c --- /dev/null +++ b/src/auth/resume-lane-warning.ts @@ -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; + log?: (message: string) => void; + debug?: boolean; +} + +export async function maybeWarnAboutResumeLaneMismatch( + profileName: string, + accountConfigDir: string, + args: string[], + deps: ResumeLaneWarningDependencies = {} +): Promise { + 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}`)); + } + } +} diff --git a/src/ccs.ts b/src/ccs.ts index 9618da71..2806235f 100644 --- a/src/ccs.ts +++ b/src/ccs.ts @@ -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 { - 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) { diff --git a/tests/unit/auth/resume-lane-warning.test.ts b/tests/unit/auth/resume-lane-warning.test.ts new file mode 100644 index 00000000..49eb3064 --- /dev/null +++ b/tests/unit/auth/resume-lane-warning.test.ts @@ -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'); + }); +});