diff --git a/src/claude/claudeRemoteLauncher.ts b/src/claude/claudeRemoteLauncher.ts index b1888804..b8e143cf 100644 --- a/src/claude/claudeRemoteLauncher.ts +++ b/src/claude/claudeRemoteLauncher.ts @@ -6,7 +6,7 @@ import React from "react"; import { claudeRemote } from "./claudeRemote"; import { PermissionHandler } from "./utils/permissionHandler"; import { Future } from "@/utils/future"; -import { SDKAssistantMessage, SDKMessage, SDKUserMessage } from "./sdk"; +import { AbortError, SDKAssistantMessage, SDKMessage, SDKUserMessage } from "./sdk"; import { formatClaudeMessageForInk } from "@/ui/messageFormatterInk"; import { logger } from "@/ui/logger"; import { SDKToLogConverter } from "./utils/sdkToLogConverter"; @@ -23,6 +23,50 @@ interface PermissionsField { allowedTools?: string[]; } +type LaunchErrorInfo = { + asString: string; + name?: string; + message?: string; + code?: string; + stack?: string; +}; + +function getLaunchErrorInfo(e: unknown): LaunchErrorInfo { + let asString = '[unprintable error]'; + try { + asString = typeof e === 'string' ? e : String(e); + } catch { + // Ignore + } + + if (!e || typeof e !== 'object') { + return { asString }; + } + + const err = e as { name?: unknown; message?: unknown; code?: unknown; stack?: unknown }; + + const name = typeof err.name === 'string' ? err.name : undefined; + const message = typeof err.message === 'string' ? err.message : undefined; + const code = typeof err.code === 'string' || typeof err.code === 'number' ? String(err.code) : undefined; + const stack = typeof err.stack === 'string' ? err.stack : undefined; + + return { asString, name, message, code, stack }; +} + +function isAbortError(e: unknown): boolean { + if (e instanceof AbortError) return true; + + if (!e || typeof e !== 'object') { + return false; + } + + const err = e as { name?: unknown; code?: unknown }; + if (typeof err.name === 'string' && err.name === 'AbortError') return true; + if (typeof err.code === 'string' && err.code === 'ABORT_ERR') return true; + + return false; +} + export async function claudeRemoteLauncher(session: Session): Promise<'switch' | 'exit'> { logger.debug('[claudeRemoteLauncher] Starting remote launcher'); @@ -400,8 +444,20 @@ export async function claudeRemoteLauncher(session: Session): Promise<'switch' | session.client.sendSessionEvent({ type: 'message', message: 'Aborted by user' }); } } catch (e) { - logger.debug('[remote]: launch error', e); + const abortError = isAbortError(e); + logger.debug('[remote]: launch error', { + ...getLaunchErrorInfo(e), + abortError, + }); + if (!exitReason) { + if (abortError) { + if (controller.signal.aborted) { + session.client.sendSessionEvent({ type: 'message', message: 'Aborted by user' }); + } + continue; + } + session.client.sendSessionEvent({ type: 'message', message: 'Process exited unexpectedly' }); continue; } @@ -457,4 +513,4 @@ export async function claudeRemoteLauncher(session: Session): Promise<'switch' | } return exitReason || 'exit'; -} \ No newline at end of file +}