mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-24 13:01:29 -07:00
fix(cli): gracefully handle --resume when no sessions exist (#21429)
This commit is contained in:
@@ -747,6 +747,60 @@ describe('gemini.tsx main function kitty protocol', () => {
|
||||
emitFeedbackSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should start normally with a warning when no sessions found for resume', async () => {
|
||||
const { SessionSelector, SessionError } = await import(
|
||||
'./utils/sessionUtils.js'
|
||||
);
|
||||
vi.mocked(SessionSelector).mockImplementation(
|
||||
() =>
|
||||
({
|
||||
resolveSession: vi
|
||||
.fn()
|
||||
.mockRejectedValue(SessionError.noSessionsFound()),
|
||||
}) as unknown as InstanceType<typeof SessionSelector>,
|
||||
);
|
||||
|
||||
const processExitSpy = vi
|
||||
.spyOn(process, 'exit')
|
||||
.mockImplementation((code) => {
|
||||
throw new MockProcessExitError(code);
|
||||
});
|
||||
const emitFeedbackSpy = vi.spyOn(coreEvents, 'emitFeedback');
|
||||
|
||||
vi.mocked(loadSettings).mockReturnValue(
|
||||
createMockSettings({
|
||||
merged: { advanced: {}, security: { auth: {} }, ui: { theme: 'test' } },
|
||||
workspace: { settings: {} },
|
||||
setValue: vi.fn(),
|
||||
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
|
||||
}),
|
||||
);
|
||||
|
||||
vi.mocked(parseArguments).mockResolvedValue({
|
||||
promptInteractive: false,
|
||||
resume: 'latest',
|
||||
} as unknown as CliArgs);
|
||||
vi.mocked(loadCliConfig).mockResolvedValue(
|
||||
createMockConfig({
|
||||
isInteractive: () => true,
|
||||
getQuestion: () => '',
|
||||
getSandbox: () => undefined,
|
||||
}),
|
||||
);
|
||||
|
||||
await main();
|
||||
|
||||
// Should NOT have crashed
|
||||
expect(processExitSpy).not.toHaveBeenCalled();
|
||||
// Should NOT have emitted a feedback error
|
||||
expect(emitFeedbackSpy).not.toHaveBeenCalledWith(
|
||||
'error',
|
||||
expect.stringContaining('Error resuming session'),
|
||||
);
|
||||
processExitSpy.mockRestore();
|
||||
emitFeedbackSpy.mockRestore();
|
||||
});
|
||||
|
||||
it.skip('should log error when cleanupExpiredSessions fails', async () => {
|
||||
const { cleanupExpiredSessions } = await import(
|
||||
'./utils/sessionCleanup.js'
|
||||
@@ -959,13 +1013,18 @@ describe('gemini.tsx main function exit codes', () => {
|
||||
resume: 'invalid-session',
|
||||
} as unknown as CliArgs);
|
||||
|
||||
vi.mock('./utils/sessionUtils.js', () => ({
|
||||
SessionSelector: vi.fn().mockImplementation(() => ({
|
||||
resolveSession: vi
|
||||
.fn()
|
||||
.mockRejectedValue(new Error('Session not found')),
|
||||
})),
|
||||
}));
|
||||
vi.mock('./utils/sessionUtils.js', async (importOriginal) => {
|
||||
const original =
|
||||
await importOriginal<typeof import('./utils/sessionUtils.js')>();
|
||||
return {
|
||||
...original,
|
||||
SessionSelector: vi.fn().mockImplementation(() => ({
|
||||
resolveSession: vi
|
||||
.fn()
|
||||
.mockRejectedValue(new Error('Session not found')),
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
process.env['GEMINI_API_KEY'] = 'test-key';
|
||||
try {
|
||||
|
||||
@@ -84,7 +84,7 @@ import { validateNonInteractiveAuth } from './validateNonInterActiveAuth.js';
|
||||
import { checkForUpdates } from './ui/utils/updateCheck.js';
|
||||
import { handleAutoUpdate } from './utils/handleAutoUpdate.js';
|
||||
import { appEvents, AppEvent } from './utils/events.js';
|
||||
import { SessionSelector } from './utils/sessionUtils.js';
|
||||
import { SessionError, SessionSelector } from './utils/sessionUtils.js';
|
||||
import { SettingsContext } from './ui/contexts/SettingsContext.js';
|
||||
import { MouseProvider } from './ui/contexts/MouseContext.js';
|
||||
import { StreamingState } from './ui/types.js';
|
||||
@@ -706,12 +706,24 @@ export async function main() {
|
||||
// Use the existing session ID to continue recording to the same session
|
||||
config.setSessionId(resumedSessionData.conversation.sessionId);
|
||||
} catch (error) {
|
||||
coreEvents.emitFeedback(
|
||||
'error',
|
||||
`Error resuming session: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
);
|
||||
await runExitCleanup();
|
||||
process.exit(ExitCodes.FATAL_INPUT_ERROR);
|
||||
if (
|
||||
error instanceof SessionError &&
|
||||
error.code === 'NO_SESSIONS_FOUND'
|
||||
) {
|
||||
// No sessions to resume — start a fresh session with a warning
|
||||
startupWarnings.push({
|
||||
id: 'resume-no-sessions',
|
||||
message: error.message,
|
||||
priority: WarningPriority.High,
|
||||
});
|
||||
} else {
|
||||
coreEvents.emitFeedback(
|
||||
'error',
|
||||
`Error resuming session: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
);
|
||||
await runExitCleanup();
|
||||
process.exit(ExitCodes.FATAL_INPUT_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -341,6 +341,29 @@ describe('SessionSelector', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw SessionError with NO_SESSIONS_FOUND when resolving latest with no sessions', async () => {
|
||||
// Empty chats directory — no session files
|
||||
const chatsDir = path.join(tmpDir, 'chats');
|
||||
await fs.mkdir(chatsDir, { recursive: true });
|
||||
|
||||
const emptyConfig = {
|
||||
storage: {
|
||||
getProjectTempDir: () => tmpDir,
|
||||
},
|
||||
getSessionId: () => 'current-session-id',
|
||||
} as Partial<Config> as Config;
|
||||
|
||||
const sessionSelector = new SessionSelector(emptyConfig);
|
||||
|
||||
await expect(sessionSelector.resolveSession('latest')).rejects.toSatisfy(
|
||||
(error) => {
|
||||
expect(error).toBeInstanceOf(SessionError);
|
||||
expect((error as SessionError).code).toBe('NO_SESSIONS_FOUND');
|
||||
return true;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should not list sessions with only system messages', async () => {
|
||||
const sessionIdWithUser = randomUUID();
|
||||
const sessionIdSystemOnly = randomUUID();
|
||||
|
||||
@@ -463,7 +463,7 @@ export class SessionSelector {
|
||||
const sessions = await this.listSessions();
|
||||
|
||||
if (sessions.length === 0) {
|
||||
throw new Error('No previous sessions found for this project.');
|
||||
throw SessionError.noSessionsFound();
|
||||
}
|
||||
|
||||
// Sort by startTime (oldest first, so newest sessions get highest numbers)
|
||||
|
||||
Reference in New Issue
Block a user