diff --git a/apps/server/src/providers/codex-provider.ts b/apps/server/src/providers/codex-provider.ts index 4f1f2c35..60db38c1 100644 --- a/apps/server/src/providers/codex-provider.ts +++ b/apps/server/src/providers/codex-provider.ts @@ -765,7 +765,7 @@ export class CodexProvider extends BaseProvider { ...(outputSchemaPath ? [CODEX_OUTPUT_SCHEMA_FLAG, outputSchemaPath] : []), ...(imagePaths.length > 0 ? [CODEX_IMAGE_FLAG, imagePaths.join(',')] : []), ...configOverrides, - promptText, + '-', // Read prompt from stdin to avoid shell escaping issues ]; const stream = spawnJSONLProcess({ @@ -775,6 +775,7 @@ export class CodexProvider extends BaseProvider { env: buildEnv(), abortController: options.abortController, timeout: DEFAULT_TIMEOUT_MS, + stdinData: promptText, // Pass prompt via stdin }); for await (const rawEvent of stream) { diff --git a/apps/server/src/services/agent-service.ts b/apps/server/src/services/agent-service.ts index 7736fd6a..3c7fc184 100644 --- a/apps/server/src/services/agent-service.ts +++ b/apps/server/src/services/agent-service.ts @@ -13,6 +13,8 @@ import { isAbortError, loadContextFiles, createLogger, + classifyError, + getUserFriendlyErrorMessage, } from '@automaker/utils'; import { ProviderFactory } from '../providers/provider-factory.js'; import { createChatOptions, validateWorkingDirectory } from '../lib/sdk-options.js'; @@ -374,6 +376,53 @@ export class AgentService { content: responseText, toolUses, }); + } else if (msg.type === 'error') { + // Some providers (like Codex CLI/SaaS or Cursor CLI) surface failures as + // streamed error messages instead of throwing. Handle these here so the + // Agent Runner UX matches the Claude/Cursor behavior without changing + // their provider implementations. + const rawErrorText = + (typeof msg.error === 'string' && msg.error.trim()) || + 'Unexpected error from provider during agent execution.'; + + const errorInfo = classifyError(new Error(rawErrorText)); + + // Keep the provider-supplied text intact (Codex already includes helpful tips), + // only add a small rate-limit hint when we can detect it. + const enhancedText = errorInfo.isRateLimit + ? `${rawErrorText}\n\nTip: It looks like you hit a rate limit. Try waiting a bit or reducing concurrent Agent Runner / Auto Mode tasks.` + : rawErrorText; + + this.logger.error('Provider error during agent execution:', { + type: errorInfo.type, + message: errorInfo.message, + }); + + // Mark session as no longer running so the UI and queue stay in sync + session.isRunning = false; + session.abortController = null; + + const errorMessage: Message = { + id: this.generateId(), + role: 'assistant', + content: `Error: ${enhancedText}`, + timestamp: new Date().toISOString(), + isError: true, + }; + + session.messages.push(errorMessage); + await this.saveSession(sessionId, session.messages); + + this.emitAgentEvent(sessionId, { + type: 'error', + error: enhancedText, + message: errorMessage, + }); + + // Don't continue streaming after an error message + return { + success: false, + }; } } diff --git a/apps/ui/src/components/views/agent-view.tsx b/apps/ui/src/components/views/agent-view.tsx index b70e32d9..be56f70d 100644 --- a/apps/ui/src/components/views/agent-view.tsx +++ b/apps/ui/src/components/views/agent-view.tsx @@ -161,7 +161,6 @@ export function AgentView() { isConnected={isConnected} isProcessing={isProcessing} currentTool={currentTool} - agentError={agentError} messagesCount={messages.length} showSessionManager={showSessionManager} onToggleSessionManager={() => setShowSessionManager(!showSessionManager)} diff --git a/apps/ui/src/components/views/agent-view/components/agent-header.tsx b/apps/ui/src/components/views/agent-view/components/agent-header.tsx index ee020ac5..a6152736 100644 --- a/apps/ui/src/components/views/agent-view/components/agent-header.tsx +++ b/apps/ui/src/components/views/agent-view/components/agent-header.tsx @@ -7,7 +7,6 @@ interface AgentHeaderProps { isConnected: boolean; isProcessing: boolean; currentTool: string | null; - agentError: string | null; messagesCount: number; showSessionManager: boolean; onToggleSessionManager: () => void; @@ -20,7 +19,6 @@ export function AgentHeader({ isConnected, isProcessing, currentTool, - agentError, messagesCount, showSessionManager, onToggleSessionManager, @@ -61,7 +59,6 @@ export function AgentHeader({ {currentTool} )} - {agentError && {agentError}} {currentSessionId && messagesCount > 0 && (