From 5ee7cfeaf61985198558b3d4674f95e022e39c95 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 20 Jun 2026 11:47:32 -0700 Subject: [PATCH 1/2] [codex] Structure desktop SSH prompt presentation failures Co-authored-by: codex --- .../src/ssh/DesktopSshEnvironment.test.ts | 1 + .../src/ssh/DesktopSshPasswordPrompts.test.ts | 67 +++++++- .../src/ssh/DesktopSshPasswordPrompts.ts | 152 +++++++++++++----- 3 files changed, 177 insertions(+), 43 deletions(-) diff --git a/apps/desktop/src/ssh/DesktopSshEnvironment.test.ts b/apps/desktop/src/ssh/DesktopSshEnvironment.test.ts index baed2610286..1fe2b86aae7 100644 --- a/apps/desktop/src/ssh/DesktopSshEnvironment.test.ts +++ b/apps/desktop/src/ssh/DesktopSshEnvironment.test.ts @@ -23,6 +23,7 @@ describe("sshEnvironment", () => { const cause = new DesktopSshPasswordPrompts.DesktopSshPromptPresentationError({ requestId: "prompt-1", destination: "devbox", + operation: "send-prompt-request", cause: new Error("renderer send failed"), }); diff --git a/apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts b/apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts index f0b5b1bd8ef..029e7d68d65 100644 --- a/apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts +++ b/apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts @@ -17,7 +17,9 @@ interface SentMessage { readonly args: readonly unknown[]; } -function makeTestWindow() { +function makeTestWindow( + options: { readonly isDestroyedError?: unknown; readonly sendError?: unknown } = {}, +) { const listeners = new Map void>>(); const sentMessages: SentMessage[] = []; let destroyed = false; @@ -26,7 +28,12 @@ function makeTestWindow() { let focused = false; const window = { - isDestroyed: () => destroyed, + isDestroyed: () => { + if (options.isDestroyedError !== undefined) { + throw options.isDestroyedError; + } + return destroyed; + }, isMinimized: () => minimized, restore: () => { restored = true; @@ -46,6 +53,9 @@ function makeTestWindow() { webContents: { send: (channel: string, ...args: readonly unknown[]) => { sentMessages.push({ channel, args }); + if (options.sendError !== undefined) { + throw options.sendError; + } }, }, }; @@ -55,6 +65,7 @@ function makeTestWindow() { sentMessages, isRestored: () => restored, isFocused: () => focused, + closedListenerCount: () => listeners.get("closed")?.size ?? 0, close: () => { destroyed = true; const closedListeners = [...(listeners.get("closed") ?? [])]; @@ -143,4 +154,56 @@ describe("DesktopSshPasswordPrompts", () => { assert.equal(error.destination, "devbox"); }).pipe(Effect.provide(makeLayer(testWindow.window)), Effect.scoped); }); + + it.effect("cleans up a prompt that fails during renderer delivery", () => { + const cause = new Error("renderer unavailable"); + const testWindow = makeTestWindow({ sendError: cause }); + + return Effect.gen(function* () { + const prompts = yield* DesktopSshPasswordPrompts.DesktopSshPasswordPrompts; + const error = yield* prompts + .request({ + destination: "devbox", + username: "julius", + prompt: "Enter the SSH password.", + attempt: 1, + }) + .pipe(Effect.flip); + + assert.instanceOf(error, DesktopSshPasswordPrompts.DesktopSshPromptPresentationError); + assert.equal(error.operation, "send-prompt-request"); + assert.equal(error.destination, "devbox"); + const requestId = error.requestId; + if (requestId === null) { + assert.fail("renderer delivery failures must retain their request id"); + } + assert.equal(testWindow.closedListenerCount(), 0); + + const resolveError = yield* prompts + .resolve({ requestId, password: "secret" }) + .pipe(Effect.flip); + assert.instanceOf(resolveError, DesktopSshPasswordPrompts.DesktopSshPromptExpiredError); + }).pipe(Effect.provide(makeLayer(testWindow.window)), Effect.scoped); + }); + + it.effect("classifies a failed initial window availability check", () => { + const testWindow = makeTestWindow({ isDestroyedError: new Error("window unavailable") }); + + return Effect.gen(function* () { + const prompts = yield* DesktopSshPasswordPrompts.DesktopSshPasswordPrompts; + const error = yield* prompts + .request({ + destination: "devbox", + username: "julius", + prompt: "Enter the SSH password.", + attempt: 1, + }) + .pipe(Effect.flip); + + assert.instanceOf(error, DesktopSshPasswordPrompts.DesktopSshPromptPresentationError); + assert.equal(error.operation, "check-window-before-request"); + assert.equal(error.requestId, null); + assert.deepEqual(testWindow.sentMessages, []); + }).pipe(Effect.provide(makeLayer(testWindow.window)), Effect.scoped); + }); }); diff --git a/apps/desktop/src/ssh/DesktopSshPasswordPrompts.ts b/apps/desktop/src/ssh/DesktopSshPasswordPrompts.ts index c933bca3cb0..a0ad8da8652 100644 --- a/apps/desktop/src/ssh/DesktopSshPasswordPrompts.ts +++ b/apps/desktop/src/ssh/DesktopSshPasswordPrompts.ts @@ -20,7 +20,26 @@ const DEFAULT_SSH_PASSWORD_PROMPT_TIMEOUT_MS = 3 * 60 * 1000; type DesktopSshPasswordPromptResolutionInput = typeof DesktopSshPasswordPromptResolutionInputSchema.Type; -const WINDOW_UNAVAILABLE_MESSAGE = "T3 Code window is not available for SSH authentication."; +const DesktopSshPromptWindowAvailabilityStage = Schema.Literals([ + "before-request", + "before-presentation", + "after-send", + "after-restore", +]); + +const DesktopSshPromptPresentationOperation = Schema.Literals([ + "check-window-before-request", + "check-window-before-presentation", + "register-window-close-listener", + "send-prompt-request", + "check-window-after-send", + "check-window-minimized", + "restore-window", + "check-window-after-restore", + "focus-window", + "remove-window-close-listener", +]); +type DesktopSshPromptPresentationOperation = typeof DesktopSshPromptPresentationOperation.Type; export class DesktopSshPromptRequestIdGenerationError extends Schema.TaggedErrorClass()( "DesktopSshPromptRequestIdGenerationError", @@ -38,20 +57,22 @@ export class DesktopSshPromptWindowUnavailableError extends Schema.TaggedErrorCl "DesktopSshPromptWindowUnavailableError", { destination: Schema.String, + requestId: Schema.NullOr(Schema.String), + stage: DesktopSshPromptWindowAvailabilityStage, }, ) { override get message(): string { - return WINDOW_UNAVAILABLE_MESSAGE; + const request = this.requestId === null ? "before a request id was assigned" : this.requestId; + return `T3 Code window is unavailable during ${this.stage} for SSH authentication to ${this.destination} (request: ${request}).`; } } -const isDesktopSshPromptWindowUnavailableError = Schema.is(DesktopSshPromptWindowUnavailableError); - export class DesktopSshPromptPresentationError extends Schema.TaggedErrorClass()( "DesktopSshPromptPresentationError", { - requestId: Schema.String, + requestId: Schema.NullOr(Schema.String), destination: Schema.String, + operation: DesktopSshPromptPresentationOperation, cause: Schema.Defect(), }, ) { @@ -263,9 +284,29 @@ export const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* ( "desktop.sshPasswordPrompts.request", )(function* (input) { const window = yield* electronWindow.main; - if (Option.isNone(window) || window.value.isDestroyed()) { + if (Option.isNone(window)) { return yield* new DesktopSshPromptWindowUnavailableError({ destination: input.destination, + requestId: null, + stage: "before-request", + }); + } + + const unavailableBeforeRequest = yield* Effect.try({ + try: () => window.value.isDestroyed(), + catch: (cause) => + new DesktopSshPromptPresentationError({ + requestId: null, + destination: input.destination, + operation: "check-window-before-request", + cause, + }), + }); + if (unavailableBeforeRequest) { + return yield* new DesktopSshPromptWindowUnavailableError({ + destination: input.destination, + requestId: null, + stage: "before-request", }); } @@ -319,11 +360,25 @@ export const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* ( ), ); }; - const cleanup = Effect.sync(() => { + const runPresentationOperation = ( + operation: DesktopSshPromptPresentationOperation, + evaluate: () => A, + ) => + Effect.try({ + try: evaluate, + catch: (cause) => + new DesktopSshPromptPresentationError({ + requestId, + destination: input.destination, + operation, + cause, + }), + }); + const cleanup = runPresentationOperation("remove-window-close-listener", () => { if (!window.value.isDestroyed()) { window.value.removeListener("closed", cancelOnWindowClosed); } - }).pipe(Effect.andThen(removePending(pendingRef, requestId)), Effect.asVoid); + }).pipe(Effect.orDie, Effect.ensuring(removePending(pendingRef, requestId)), Effect.asVoid); const waitForPassword = Deferred.await(deferred).pipe( Effect.timeoutOption(Duration.millis(passwordPromptTimeoutMs)), Effect.flatMap( @@ -340,39 +395,54 @@ export const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* ( ), ); - return yield* Effect.try({ - try: () => { - if (window.value.isDestroyed()) { - throw new DesktopSshPromptWindowUnavailableError({ - destination: input.destination, - }); - } - window.value.once("closed", cancelOnWindowClosed); - window.value.webContents.send(SSH_PASSWORD_PROMPT_CHANNEL, promptRequest); - if (window.value.isDestroyed()) { - throw new DesktopSshPromptWindowUnavailableError({ - destination: input.destination, - }); - } - if (window.value.isMinimized()) { - window.value.restore(); - } - if (window.value.isDestroyed()) { - throw new DesktopSshPromptWindowUnavailableError({ - destination: input.destination, - }); - } - window.value.focus(); - }, - catch: (cause) => - isDesktopSshPromptWindowUnavailableError(cause) - ? cause - : new DesktopSshPromptPresentationError({ - requestId, - destination: input.destination, - cause, - }), - }).pipe(Effect.andThen(waitForPassword), Effect.ensuring(cleanup)); + return yield* Effect.gen(function* () { + const unavailableBeforePresentation = yield* runPresentationOperation( + "check-window-before-presentation", + () => window.value.isDestroyed(), + ); + if (unavailableBeforePresentation) { + return yield* new DesktopSshPromptWindowUnavailableError({ + destination: input.destination, + requestId, + stage: "before-presentation", + }); + } + yield* runPresentationOperation("register-window-close-listener", () => + window.value.once("closed", cancelOnWindowClosed), + ); + yield* runPresentationOperation("send-prompt-request", () => + window.value.webContents.send(SSH_PASSWORD_PROMPT_CHANNEL, promptRequest), + ); + const unavailableAfterSend = yield* runPresentationOperation("check-window-after-send", () => + window.value.isDestroyed(), + ); + if (unavailableAfterSend) { + return yield* new DesktopSshPromptWindowUnavailableError({ + destination: input.destination, + requestId, + stage: "after-send", + }); + } + const minimized = yield* runPresentationOperation("check-window-minimized", () => + window.value.isMinimized(), + ); + if (minimized) { + yield* runPresentationOperation("restore-window", () => window.value.restore()); + } + const unavailableAfterRestore = yield* runPresentationOperation( + "check-window-after-restore", + () => window.value.isDestroyed(), + ); + if (unavailableAfterRestore) { + return yield* new DesktopSshPromptWindowUnavailableError({ + destination: input.destination, + requestId, + stage: "after-restore", + }); + } + yield* runPresentationOperation("focus-window", () => window.value.focus()); + return yield* waitForPassword; + }).pipe(Effect.ensuring(cleanup)); }); return DesktopSshPasswordPrompts.of({ From 32a8c28d4bbded4aa72abe86a349e80878f881c6 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 20 Jun 2026 12:19:16 -0700 Subject: [PATCH 2/2] [codex] Preserve resolved SSH prompt responses Co-authored-by: codex --- .../src/ssh/DesktopSshPasswordPrompts.test.ts | 46 ++++++++++- .../src/ssh/DesktopSshPasswordPrompts.ts | 82 +++++++++++-------- 2 files changed, 93 insertions(+), 35 deletions(-) diff --git a/apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts b/apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts index 029e7d68d65..5ec7dd65d1e 100644 --- a/apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts +++ b/apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts @@ -18,7 +18,11 @@ interface SentMessage { } function makeTestWindow( - options: { readonly isDestroyedError?: unknown; readonly sendError?: unknown } = {}, + options: { + readonly isDestroyedError?: unknown; + readonly isMinimizedError?: unknown; + readonly sendError?: unknown; + } = {}, ) { const listeners = new Map void>>(); const sentMessages: SentMessage[] = []; @@ -34,7 +38,12 @@ function makeTestWindow( } return destroyed; }, - isMinimized: () => minimized, + isMinimized: () => { + if (options.isMinimizedError !== undefined) { + throw options.isMinimizedError; + } + return minimized; + }, restore: () => { restored = true; minimized = false; @@ -52,7 +61,8 @@ function makeTestWindow( }, webContents: { send: (channel: string, ...args: readonly unknown[]) => { - sentMessages.push({ channel, args }); + const message = { channel, args }; + sentMessages.push(message); if (options.sendError !== undefined) { throw options.sendError; } @@ -118,6 +128,7 @@ describe("DesktopSshPasswordPrompts", () => { }) .pipe(Effect.forkScoped); + yield* Effect.yieldNow; yield* Effect.yieldNow; assert.equal(testWindow.sentMessages.length, 1); const sent = testWindow.sentMessages[0]; @@ -186,6 +197,35 @@ describe("DesktopSshPasswordPrompts", () => { }).pipe(Effect.provide(makeLayer(testWindow.window)), Effect.scoped); }); + it.effect("keeps a submitted password when a later presentation step fails", () => { + const testWindow = makeTestWindow({ + isMinimizedError: new Error("failed to read minimized state"), + }); + + return Effect.gen(function* () { + const prompts = yield* DesktopSshPasswordPrompts.DesktopSshPasswordPrompts; + const requestFiber = yield* prompts + .request({ + destination: "devbox", + username: "julius", + prompt: "Enter the SSH password.", + attempt: 1, + }) + .pipe(Effect.forkScoped); + + yield* Effect.yieldNow; + const sent = testWindow.sentMessages[0]; + assert.ok(sent); + const request = sent.args[0] as { readonly requestId: string }; + yield* prompts.resolve({ requestId: request.requestId, password: "secret" }); + const password = yield* Fiber.join(requestFiber); + + assert.equal(password, "secret"); + assert.equal(testWindow.isFocused(), false); + assert.equal(testWindow.closedListenerCount(), 0); + }).pipe(Effect.provide(makeLayer(testWindow.window)), Effect.scoped); + }); + it.effect("classifies a failed initial window availability check", () => { const testWindow = makeTestWindow({ isDestroyedError: new Error("window unavailable") }); diff --git a/apps/desktop/src/ssh/DesktopSshPasswordPrompts.ts b/apps/desktop/src/ssh/DesktopSshPasswordPrompts.ts index a0ad8da8652..aa25d8135c7 100644 --- a/apps/desktop/src/ssh/DesktopSshPasswordPrompts.ts +++ b/apps/desktop/src/ssh/DesktopSshPasswordPrompts.ts @@ -394,6 +394,20 @@ export const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* ( }), ), ); + const preferSubmittedPassword = (error: DesktopSshPasswordPromptRequestError) => + Deferred.poll(deferred).pipe( + Effect.flatMap( + Option.match({ + onSome: (completion) => completion, + onNone: () => + Ref.get(pendingRef).pipe( + Effect.flatMap((entries) => + entries.has(requestId) ? Effect.fail(error) : Deferred.await(deferred), + ), + ), + }), + ), + ); return yield* Effect.gen(function* () { const unavailableBeforePresentation = yield* runPresentationOperation( @@ -410,38 +424,42 @@ export const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* ( yield* runPresentationOperation("register-window-close-listener", () => window.value.once("closed", cancelOnWindowClosed), ); - yield* runPresentationOperation("send-prompt-request", () => - window.value.webContents.send(SSH_PASSWORD_PROMPT_CHANNEL, promptRequest), - ); - const unavailableAfterSend = yield* runPresentationOperation("check-window-after-send", () => - window.value.isDestroyed(), - ); - if (unavailableAfterSend) { - return yield* new DesktopSshPromptWindowUnavailableError({ - destination: input.destination, - requestId, - stage: "after-send", - }); - } - const minimized = yield* runPresentationOperation("check-window-minimized", () => - window.value.isMinimized(), - ); - if (minimized) { - yield* runPresentationOperation("restore-window", () => window.value.restore()); - } - const unavailableAfterRestore = yield* runPresentationOperation( - "check-window-after-restore", - () => window.value.isDestroyed(), - ); - if (unavailableAfterRestore) { - return yield* new DesktopSshPromptWindowUnavailableError({ - destination: input.destination, - requestId, - stage: "after-restore", - }); - } - yield* runPresentationOperation("focus-window", () => window.value.focus()); - return yield* waitForPassword; + return yield* Effect.gen(function* () { + yield* runPresentationOperation("send-prompt-request", () => + window.value.webContents.send(SSH_PASSWORD_PROMPT_CHANNEL, promptRequest), + ); + yield* Effect.yieldNow; + const unavailableAfterSend = yield* runPresentationOperation( + "check-window-after-send", + () => window.value.isDestroyed(), + ); + if (unavailableAfterSend) { + return yield* new DesktopSshPromptWindowUnavailableError({ + destination: input.destination, + requestId, + stage: "after-send", + }); + } + const minimized = yield* runPresentationOperation("check-window-minimized", () => + window.value.isMinimized(), + ); + if (minimized) { + yield* runPresentationOperation("restore-window", () => window.value.restore()); + } + const unavailableAfterRestore = yield* runPresentationOperation( + "check-window-after-restore", + () => window.value.isDestroyed(), + ); + if (unavailableAfterRestore) { + return yield* new DesktopSshPromptWindowUnavailableError({ + destination: input.destination, + requestId, + stage: "after-restore", + }); + } + yield* runPresentationOperation("focus-window", () => window.value.focus()); + return yield* waitForPassword; + }).pipe(Effect.catch(preferSubmittedPassword)); }).pipe(Effect.ensuring(cleanup)); });