@@ -19,7 +19,7 @@ import { join, sep } from '../../../../base/common/path.js';
1919import { URI } from '../../../../base/common/uri.js';
2020import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
2121import { INativeEnvironmentService } from '../../../environment/common/environment.js';
22- import { FileSystemProviderCapabilities, IFileService, type IWriteFileOptions } from '../../../files/common/files.js';
22+ import { FileOperationError, FileOperationResult, FileSystemProviderCapabilities, IFileService, type IWriteFileOptions } from '../../../files/common/files.js';
2323import { InstantiationService } from '../../../instantiation/common/instantiationService.js';
2424import { ServiceCollection } from '../../../instantiation/common/serviceCollection.js';
2525import { ILogService, NullLogService } from '../../../log/common/log.js';
@@ -717,6 +717,7 @@ async function createAgentSession(disposables: DisposableStore, options?: {
717717 shellInitWriteGate?: Promise<void>;
718718 onShellInitWrite?: () => void;
719719 shellInitWriteFailureLeavesArtifact?: boolean;
720+ shellInitDeleteMissingThrows?: boolean;
720721 sessionDatabase?: ISessionDatabase;
721722 /** Configure the mock session before {@link CopilotAgentSession.initializeSession} runs. */
722723 configureMockSession?: (session: MockCopilotSession) => void;
@@ -891,6 +892,9 @@ async function createAgentSession(disposables: DisposableStore, options?: {
891892 mockSession.operationLog.push('file.delete:shellInit');
892893 }
893894 const matches = [...storedFileContents.keys()].filter(key => key.startsWith(resource.toString()) || key.startsWith(resource.fsPath));
895+ if (options?.shellInitDeleteMissingThrows && resource.fsPath.includes('/agentHost/shellInit/') && matches.length === 0) {
896+ throw new FileOperationError('not found', FileOperationResult.FILE_NOT_FOUND);
897+ }
894898 // Like the disk provider, a non-recursive delete removes only an
895899 // empty directory and fails while descendants remain.
896900 if (!delOptions?.recursive && matches.some(key => key !== resource.toString() && key !== resource.fsPath)) {
@@ -12385,6 +12389,22 @@ Use the attached image as context.
1238512389 });
1238612390 });
1238712391
12392+ test('does not retry cleanup when a failed write created no directory', async () => {
12393+ const { session, mockSession, setConfigValue } = await createAgentSession(disposables, {
12394+ shellInitWriteFailures: 1,
12395+ shellInitDeleteMissingThrows: true,
12396+ });
12397+ setConfigValue(SessionConfigKey.ShellInitSnippets, [initScript]);
12398+ await session.send('go', undefined, 'turn-1', 'interactive');
12399+
12400+ setConfigValue(SessionConfigKey.ShellInitSnippets, []);
12401+ await session.send('go', undefined, 'turn-2', 'interactive');
12402+ const deletesAfterClear = mockSession.operationLog.filter(operation => operation === 'file.delete:shellInit').length;
12403+ await session.send('go', undefined, 'turn-3', 'interactive');
12404+
12405+ assert.strictEqual(mockSession.operationLog.filter(operation => operation === 'file.delete:shellInit').length, deletesAfterClear);
12406+ });
12407+
1238812408 test('uses atomic writes when the file provider supports them', async () => {
1238912409 const { mockSession, fileWriteOptions } = await createAgentSession(disposables, {
1239012410 configValues: { [SessionConfigKey.ShellInitSnippets]: [initScript] },
0 commit comments