@@ -38,10 +38,14 @@ vi.mock('@/lib/workspaces/utils', () => workspacesUtilsMock)
3838
3939vi . mock ( '@/lib/workflows/executor/human-in-the-loop-manager' , ( ) => humanInTheLoopManagerMock )
4040
41+ import { executeResumeWorkflow } from '@/lib/workflows/executor/resume-execution'
4142import { POST } from '@/app/api/resume/[workflowId]/[executionId]/[contextId]/route'
4243import { handleResumeExecution } from '@/app/api/resume/resume-handler'
44+ import { attachExecutionResult , buildBlockExecutionError } from '@/executor/utils/errors'
45+ import type { SerializedBlock } from '@/serializer/types'
4346
44- const { mockEnqueueOrStartResume, mockGetPausedExecutionDetail } = humanInTheLoopManagerMockFns
47+ const { mockEnqueueOrStartResume, mockGetPausedExecutionDetail, mockStartResumeExecution } =
48+ humanInTheLoopManagerMockFns
4549const { mockGetWorkspaceBilledAccountUserId : mockGetCurrentPayer } = workspacesUtilsMockFns
4650
4751const { mockShouldExecuteInline } = asyncJobsMockFns
@@ -362,6 +366,102 @@ describe('POST /api/resume/[workflowId]/[executionId]/[contextId]', () => {
362366 )
363367 } )
364368
369+ describe ( 'a synchronous resume whose run fails' , ( ) => {
370+ const STARTED_RESUME = {
371+ status : 'started' ,
372+ resumeExecutionId : 'resume-execution-1' ,
373+ resumeEntryId : 'resume-entry-1' ,
374+ pausedExecution : { id : 'paused-execution-1' } ,
375+ contextId : CONTEXT_ID ,
376+ resumeInput : { approved : true } ,
377+ userId : 'current-api-key-user' ,
378+ }
379+
380+ /** The shape the engine throws when a block fails mid-run: block context plus the partial result. */
381+ function inBandRunFailure ( ) {
382+ const conditionBlock = {
383+ id : 'condition-1' ,
384+ metadata : { id : 'condition' , name : 'Route' } ,
385+ } as SerializedBlock
386+ const error = buildBlockExecutionError ( {
387+ block : conditionBlock ,
388+ error : new Error ( 'Condition evaluation failed' ) ,
389+ } )
390+ attachExecutionResult ( error , {
391+ success : false ,
392+ output : { partial : true } ,
393+ error : error . message ,
394+ logs : [ ] ,
395+ metadata : {
396+ startTime : '2026-08-05T00:00:00.000Z' ,
397+ endTime : '2026-08-05T00:00:01.000Z' ,
398+ duration : 1000 ,
399+ } ,
400+ } )
401+ return error
402+ }
403+
404+ function resumeOnV2Surface ( ) {
405+ return executeResumeWorkflow ( {
406+ workflowId : WORKFLOW_ID ,
407+ executionId : EXECUTION_ID ,
408+ contextId : CONTEXT_ID ,
409+ workspaceId : WORKSPACE_ID ,
410+ userId : 'current-api-key-user' ,
411+ resumeInput : { approved : true } ,
412+ isApiCaller : true ,
413+ pollingSurface : 'v2' ,
414+ allowStreaming : false ,
415+ } )
416+ }
417+
418+ beforeEach ( ( ) => {
419+ mockEnqueueOrStartResume . mockResolvedValueOnce ( STARTED_RESUME )
420+ } )
421+
422+ it ( 'settles an in-band failure as a failed run with a classified error' , async ( ) => {
423+ mockStartResumeExecution . mockRejectedValueOnce ( inBandRunFailure ( ) )
424+
425+ await expect ( resumeOnV2Surface ( ) ) . resolves . toEqual ( {
426+ kind : 'sync' ,
427+ executionId : 'resume-execution-1' ,
428+ success : false ,
429+ status : 'failed' ,
430+ output : { partial : true } ,
431+ error : 'Route: Condition evaluation failed' ,
432+ structuredError : {
433+ message : 'Condition evaluation failed' ,
434+ code : 'BLOCK_EXECUTION_FAILED' ,
435+ blockId : 'condition-1' ,
436+ blockName : 'Route' ,
437+ blockType : 'condition' ,
438+ } ,
439+ metadata : {
440+ startTime : '2026-08-05T00:00:00.000Z' ,
441+ endTime : '2026-08-05T00:00:01.000Z' ,
442+ duration : 1000 ,
443+ } ,
444+ } )
445+ } )
446+
447+ it ( 'still throws a failure that produced no run' , async ( ) => {
448+ const infrastructureFailure = new Error ( 'Run buffer unavailable' )
449+ mockStartResumeExecution . mockRejectedValueOnce ( infrastructureFailure )
450+
451+ await expect ( resumeOnV2Surface ( ) ) . rejects . toBe ( infrastructureFailure )
452+ } )
453+
454+ it ( 'keeps the legacy route answering an in-band failure with its 400 error body' , async ( ) => {
455+ mockStartResumeExecution . mockRejectedValueOnce ( inBandRunFailure ( ) )
456+ const { request, context } = makeRequest ( )
457+
458+ const response = await POST ( request , context )
459+
460+ expect ( response . status ) . toBe ( 400 )
461+ expect ( await response . json ( ) ) . toEqual ( { error : 'Route: Condition evaluation failed' } )
462+ } )
463+ } )
464+
365465 it . each ( [
366466 { statusCode : 402 , message : 'Member usage limit reached' , retryable : false } ,
367467 { statusCode : 429 , message : 'Target concurrency full' , retryable : true } ,
0 commit comments