Preserve $TMPDIR when retrying terminal commands outside the sandbox (#304601)

* Changes to include environment variables for retry without sandboxing

* adding e2e tests

* fixing code formatting
This commit is contained in:
dileepyavan
2026-03-26 22:49:29 -07:00
committed by GitHub
parent 1d62cc626a
commit 6afe980a82
6 changed files with 76 additions and 23 deletions

View File

@@ -71,7 +71,7 @@ function extractTextContent(result: vscode.LanguageModelToolResult): string {
participantRegistered = false;
pendingResult = undefined;
pendingCommand = undefined;
pendingTimeout = undefined;
pendingOptions = undefined;
const chatToolsConfig = vscode.workspace.getConfiguration('chat.tools.global');
await chatToolsConfig.update('autoApprove', undefined, vscode.ConfigurationTarget.Global);
@@ -82,10 +82,16 @@ function extractTextContent(result: vscode.LanguageModelToolResult): string {
* Helper: invokes run_in_terminal via a chat participant and returns the tool result text.
* Each call creates a new chat session to avoid participant re-registration issues.
*/
interface RunInTerminalOptions {
timeout?: number;
requestUnsandboxedExecution?: boolean;
requestUnsandboxedExecutionReason?: string;
}
let participantRegistered = false;
let pendingResult: DeferredPromise<vscode.LanguageModelToolResult> | undefined;
let pendingCommand: string | undefined;
let pendingTimeout: number | undefined;
let pendingOptions: RunInTerminalOptions | undefined;
function setupParticipant() {
if (participantRegistered) {
@@ -98,10 +104,10 @@ function extractTextContent(result: vscode.LanguageModelToolResult): string {
}
const currentResult = pendingResult;
const currentCommand = pendingCommand;
const currentTimeout = pendingTimeout ?? 15000;
const currentOptions = pendingOptions ?? {};
pendingResult = undefined;
pendingCommand = undefined;
pendingTimeout = undefined;
pendingOptions = undefined;
try {
const result = await vscode.lm.invokeTool('run_in_terminal', {
input: {
@@ -109,7 +115,11 @@ function extractTextContent(result: vscode.LanguageModelToolResult): string {
explanation: 'Integration test command',
goal: 'Test run_in_terminal output',
isBackground: false,
timeout: currentTimeout
timeout: currentOptions.timeout ?? 15000,
...currentOptions.requestUnsandboxedExecution ? {
requestUnsandboxedExecution: true,
requestUnsandboxedExecutionReason: currentOptions.requestUnsandboxedExecutionReason,
} : {},
},
toolInvocationToken: request.toolInvocationToken,
});
@@ -122,13 +132,18 @@ function extractTextContent(result: vscode.LanguageModelToolResult): string {
disposables.push(participant);
}
async function invokeRunInTerminal(command: string, timeout = 15000): Promise<string> {
async function invokeRunInTerminal(command: string, options?: RunInTerminalOptions): Promise<string>;
async function invokeRunInTerminal(command: string, timeout?: number): Promise<string>;
async function invokeRunInTerminal(command: string, optionsOrTimeout?: RunInTerminalOptions | number): Promise<string> {
setupParticipant();
const opts: RunInTerminalOptions = typeof optionsOrTimeout === 'number'
? { timeout: optionsOrTimeout }
: optionsOrTimeout ?? {};
const resultPromise = new DeferredPromise<vscode.LanguageModelToolResult>();
pendingResult = resultPromise;
pendingCommand = command;
pendingTimeout = timeout;
pendingOptions = opts;
await vscode.commands.executeCommand('workbench.action.chat.newChat');
vscode.commands.executeCommand('workbench.action.chat.open', { query: '@participant test' });
@@ -326,6 +341,27 @@ function extractTextContent(result: vscode.LanguageModelToolResult): string {
assert.ok(acceptable.includes(output.trim()), `Unexpected output: ${JSON.stringify(output.trim())}`);
});
test('requestUnsandboxedExecution preserves sandbox $TMPDIR', async function () {
this.timeout(60000);
const marker = `SANDBOX_UNSANDBOX_${Date.now()}`;
const sentinelName = `sentinel-${marker}.txt`;
// Step 1: Write a sentinel file into the sandbox-provided $TMPDIR.
const writeOutput = await invokeRunInTerminal(`echo ${marker} > "$TMPDIR/${sentinelName}" && echo ${marker}`);
assert.strictEqual(writeOutput.trim(), marker);
// Step 2: Retry with requestUnsandboxedExecution=true while sandbox
// stays enabled. The tool should preserve $TMPDIR from the sandbox so
// the sentinel file created in step 1 is still accessible.
const retryOutput = await invokeRunInTerminal(`cat "$TMPDIR/${sentinelName}"`, {
timeout: 30000,
requestUnsandboxedExecution: true,
requestUnsandboxedExecutionReason: 'Need to verify $TMPDIR persists on unsandboxed retry',
});
assert.strictEqual(retryOutput.trim(), marker);
});
test('cannot write to /tmp', async function () {
this.timeout(60000);