From 26e7ef6787f5fe945b548f14dab1d8fccf5dd776 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Thu, 16 Jul 2026 11:04:09 -0700 Subject: [PATCH] fix(ai-vault): preserve resume env deletion through drag --- .../right-sidebar/AiVaultSessionRow.tsx | 1 + .../tab-group/AiVaultSessionDropLayer.tsx | 1 + .../src/lib/ai-vault-session-drag.test.ts | 19 +++++++++++++++++++ src/renderer/src/lib/ai-vault-session-drag.ts | 14 +++++++++++++- 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/components/right-sidebar/AiVaultSessionRow.tsx b/src/renderer/src/components/right-sidebar/AiVaultSessionRow.tsx index 448be77c122..b5303199ce0 100644 --- a/src/renderer/src/components/right-sidebar/AiVaultSessionRow.tsx +++ b/src/renderer/src/components/right-sidebar/AiVaultSessionRow.tsx @@ -100,6 +100,7 @@ export function VaultSessionRow({ sessionFilePath: session.filePath, sessionExecutionHostId: session.executionHostId, ...(resumeStartup.env ? { env: resumeStartup.env } : {}), + ...(resumeStartup.envToDelete ? { envToDelete: resumeStartup.envToDelete } : {}), ...(resumeStartup.launchConfig ? { launchConfig: resumeStartup.launchConfig } : {}) }) window.dispatchEvent(new Event(AI_VAULT_SESSION_DRAG_START_EVENT)) diff --git a/src/renderer/src/components/tab-group/AiVaultSessionDropLayer.tsx b/src/renderer/src/components/tab-group/AiVaultSessionDropLayer.tsx index 2ae7765022a..6d20c74c159 100644 --- a/src/renderer/src/components/tab-group/AiVaultSessionDropLayer.tsx +++ b/src/renderer/src/components/tab-group/AiVaultSessionDropLayer.tsx @@ -204,6 +204,7 @@ export default function AiVaultSessionDropLayer({ worktreeId, command: payload.command, ...(payload.env ? { env: payload.env } : {}), + ...(payload.envToDelete ? { envToDelete: payload.envToDelete } : {}), ...(payload.launchConfig ? { launchConfig: payload.launchConfig } : {}), targetGroupId: dropTarget.groupId, splitDirection: dropTarget.zone === 'center' ? undefined : dropTarget.zone diff --git a/src/renderer/src/lib/ai-vault-session-drag.test.ts b/src/renderer/src/lib/ai-vault-session-drag.test.ts index e51a0c78efd..87da752157a 100644 --- a/src/renderer/src/lib/ai-vault-session-drag.test.ts +++ b/src/renderer/src/lib/ai-vault-session-drag.test.ts @@ -50,6 +50,7 @@ describe('Session History session drag data', () => { command: "cd '/repo' && claude --resume session-1", sessionFilePath: '/Users/ada/.claude/projects/-repo/session-1.jsonl', env: { ANTHROPIC_BASE_URL: 'https://claude.example.test' }, + envToDelete: ['CODEX_HOME', 'ORCA_CODEX_HOME'], launchConfig: { agentCommand: 'claude --dangerously-skip-permissions', agentArgs: '--dangerously-skip-permissions', @@ -110,6 +111,24 @@ describe('Session History session drag data', () => { expect(readAiVaultSessionDragData(transfer)).toBeNull() }) + it('rejects malformed env deletion lists', () => { + const transfer = createTransfer() + transfer.setData( + AI_VAULT_SESSION_DRAG_TYPE, + JSON.stringify({ + kind: 'ai-vault-session', + version: 1, + agent: 'codex', + sessionId: 'session-1', + title: 'Malformed env deletion', + command: 'codex resume session-1', + envToDelete: ['CODEX_HOME', ''] + }) + ) + + expect(readAiVaultSessionDragData(transfer)).toBeNull() + }) + it('rejects array-shaped launch config env records', () => { const transfer = createTransfer() transfer.setData( diff --git a/src/renderer/src/lib/ai-vault-session-drag.ts b/src/renderer/src/lib/ai-vault-session-drag.ts index 7b5ec941dd7..b4bc124ae06 100644 --- a/src/renderer/src/lib/ai-vault-session-drag.ts +++ b/src/renderer/src/lib/ai-vault-session-drag.ts @@ -17,8 +17,9 @@ export type AiVaultSessionDragPayload = { // WSL) to reject SSH panes that cannot reach it. sessionFilePath?: string sessionExecutionHostId?: ExecutionHostId - // Why: drag/drop resume must preserve planned env/default args, not just the shell command. + // Why: drag/drop resume must preserve planned env mutations/default args, not just the command. env?: Record + envToDelete?: string[] launchConfig?: SleepingAgentLaunchConfig } @@ -44,6 +45,14 @@ function isStringRecord(value: unknown): value is Record { return Object.values(value).every((entry) => typeof entry === 'string') } +function isEnvDeletionList(value: unknown): value is string[] { + return ( + Array.isArray(value) && + value.length <= 32 && + value.every((entry) => typeof entry === 'string' && entry.length > 0 && entry.length <= 256) + ) +} + function isLaunchConfig(value: unknown): value is SleepingAgentLaunchConfig { if (!value || typeof value !== 'object') { return false @@ -72,6 +81,7 @@ function isSerializedPayload(value: unknown): value is SerializedAiVaultSessionD (payload.sessionExecutionHostId === undefined || Boolean(normalizeExecutionHostId(payload.sessionExecutionHostId))) && (payload.env === undefined || isStringRecord(payload.env)) && + (payload.envToDelete === undefined || isEnvDeletionList(payload.envToDelete)) && (payload.launchConfig === undefined || isLaunchConfig(payload.launchConfig)) ) } @@ -126,6 +136,7 @@ export function readAiVaultSessionDragData( sessionFilePath, sessionExecutionHostId, env, + envToDelete, launchConfig } = parsed return { @@ -136,6 +147,7 @@ export function readAiVaultSessionDragData( ...(sessionFilePath ? { sessionFilePath } : {}), ...(sessionExecutionHostId ? { sessionExecutionHostId } : {}), ...(env ? { env } : {}), + ...(envToDelete ? { envToDelete } : {}), ...(launchConfig ? { launchConfig } : {}) } } catch {