mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 14:10:37 -07:00
fix(plan): ensure sessionId is always present for plans storage
The Plan Mode security policy expects a consistent two-level subdirectory structure after tmp/ (~/.gemini/tmp/<project>/<session-id>/plans/). Previously, Storage allowed sessionId to be optional, which resulted in a one-level structure (~/.gemini/tmp/<project>/plans/) when missing, causing the Policy Engine to deny write operations. This change enforces sessionId in the Storage constructor (defaulting to the process-wide sessionId) and ensures the Storage instance is updated when a session is resumed via Config.setSessionId(). This guarantees that generated paths always match the policy's regular expression, even across session restarts. Fixes https://github.com/google-gemini/gemini-cli/issues/21359
This commit is contained in:
@@ -1349,6 +1349,7 @@ export class Config implements McpContext {
|
||||
|
||||
setSessionId(sessionId: string): void {
|
||||
this.sessionId = sessionId;
|
||||
this.storage.setSessionId(sessionId);
|
||||
}
|
||||
|
||||
setTerminalBackground(terminalBackground: string | undefined): void {
|
||||
|
||||
@@ -13,6 +13,7 @@ vi.unmock('./storageMigration.js');
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import * as fs from 'node:fs';
|
||||
import { sessionId as defaultSessionId } from '../utils/session.js';
|
||||
|
||||
vi.mock('fs', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('fs')>();
|
||||
@@ -161,10 +162,10 @@ describe('Storage – additional helpers', () => {
|
||||
expect(Storage.getGlobalBinDir()).toBe(expected);
|
||||
});
|
||||
|
||||
it('getProjectTempPlansDir returns ~/.gemini/tmp/<identifier>/plans when no sessionId is provided', async () => {
|
||||
it('getProjectTempPlansDir returns ~/.gemini/tmp/<identifier>/<defaultSessionId>/plans when no sessionId is provided', async () => {
|
||||
await storage.initialize();
|
||||
const tempDir = storage.getProjectTempDir();
|
||||
const expected = path.join(tempDir, 'plans');
|
||||
const expected = path.join(tempDir, defaultSessionId, 'plans');
|
||||
expect(storage.getProjectTempPlansDir()).toBe(expected);
|
||||
});
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
resolveToRealPath,
|
||||
normalizePath,
|
||||
} from '../utils/paths.js';
|
||||
import { sessionId as defaultSessionId } from '../utils/session.js';
|
||||
import { ProjectRegistry } from './projectRegistry.js';
|
||||
import { StorageMigration } from './storageMigration.js';
|
||||
|
||||
@@ -28,16 +29,20 @@ export const AUTO_SAVED_POLICY_FILENAME = 'auto-saved.toml';
|
||||
|
||||
export class Storage {
|
||||
private readonly targetDir: string;
|
||||
private readonly sessionId: string | undefined;
|
||||
private sessionId: string;
|
||||
private projectIdentifier: string | undefined;
|
||||
private initPromise: Promise<void> | undefined;
|
||||
private customPlansDir: string | undefined;
|
||||
|
||||
constructor(targetDir: string, sessionId?: string) {
|
||||
constructor(targetDir: string, sessionId: string = defaultSessionId) {
|
||||
this.targetDir = targetDir;
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
setSessionId(sessionId: string): void {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
setCustomPlansDir(dir: string | undefined): void {
|
||||
this.customPlansDir = dir;
|
||||
}
|
||||
@@ -280,10 +285,7 @@ export class Storage {
|
||||
}
|
||||
|
||||
getProjectTempPlansDir(): string {
|
||||
if (this.sessionId) {
|
||||
return path.join(this.getProjectTempDir(), this.sessionId, 'plans');
|
||||
}
|
||||
return path.join(this.getProjectTempDir(), 'plans');
|
||||
return path.join(this.getProjectTempDir(), this.sessionId, 'plans');
|
||||
}
|
||||
|
||||
getProjectTempTrackerDir(): string {
|
||||
@@ -311,10 +313,7 @@ export class Storage {
|
||||
}
|
||||
|
||||
getProjectTempTasksDir(): string {
|
||||
if (this.sessionId) {
|
||||
return path.join(this.getProjectTempDir(), this.sessionId, 'tasks');
|
||||
}
|
||||
return path.join(this.getProjectTempDir(), 'tasks');
|
||||
return path.join(this.getProjectTempDir(), this.sessionId, 'tasks');
|
||||
}
|
||||
|
||||
async listProjectChatFiles(): Promise<
|
||||
|
||||
Reference in New Issue
Block a user