@@ -24,7 +24,7 @@ const manifest = (changes: ManifestChange[], over: Partial<ChangeManifest> = {})
2424 ( { changes, agentCommits : [ ] , metadataChanged : false , linkTargetChanges : [ ] , nestedGitlinkContent : [ ] , digest : `digest-${ changes . length } ` , ...over } ) ;
2525
2626function setup ( options : { manifests ?: Record < string , ChangeManifest & { digest : string } > ; exit ?: Record < string , Partial < InvocationResult > > ;
27- commit ?: ( item : string ) => Promise < void > ; release ?: ( ) => Promise < void > } = { } ) {
27+ commit ?: ( item : string ) => Promise < void > ; release ?: ( ) => Promise < void > ; startError ?: Error } = { } ) {
2828 const dir = mkdtempSync ( join ( tmpdir ( ) , 'codeboost-exec-' ) ) ; dirs . push ( dir ) ;
2929 const store = new Store ( join ( dir , 'state.sqlite' ) ) ;
3030 store . createPlan ( JSON . stringify ( plan ) , 'json' , context , oid ( 1 ) , oid ( 2 ) ) ;
@@ -50,6 +50,7 @@ function setup(options: { manifests?: Record<string, ChangeManifest & { digest:
5050 const sources : ExecutionSources = { planContext : ( ) => context , issue : ( ) => ( { number : 1 , title : 'Issue' , body : 'Please fix' , comments : [ ] } ) , lessons : ( ) => [ ] , vendor : ( ) => 'claude' } ;
5151 const prompts : string [ ] = [ ] , argv : ( readonly ( readonly string [ ] ) [ ] ) [ ] = [ ] , owners : string [ ] = [ ] ;
5252 const deps = executionDeps ( store , workspace , ( input , prompt , ws ) => {
53+ if ( options . startError ) throw options . startError ;
5354 log . push ( `start ${ itemOf ( ws ) } ` ) ; prompts . push ( prompt ) ; argv . push ( input . approvedArgv ) ; owners . push ( input . runnerOwner ) ;
5455 return { attemptId : input . attemptId , settled : Promise . resolve ( { attemptId : input . attemptId , context : input . context , exitCode : 0 , signal : null , stdout : 'done' , stderr : '' , ...options . exit ?. [ itemOf ( ws ) ] } ) , cancel : ( ) => undefined } ;
5556 } , sources , RUNNER_OWNER ) ;
@@ -124,4 +125,16 @@ describe('item execution', () => {
124125 await expect ( executor . runTask ( identity ) ) . rejects . toThrow ( / N e e d s r e s t a r t / ) ;
125126 expect ( runner . status ( identity ) . unresolved ) . toMatchObject ( { reason : 'result-not-saved' } ) ;
126127 } ) ;
128+ it ( 'releases task storage after the terminal write when D settles with another attempt\'s result' , async ( ) => {
129+ const { store, executor, log } = setup ( { exit : { P1 : { attemptId : '00000000-0000-4000-8000-000000000000' } } } ) ;
130+ expect ( await executor . runTask ( identity ) ) . toMatchObject ( { kind : 'stopped' , item : 'P1' , state : 'failed' } ) ;
131+ expect ( log . some ( line => line . startsWith ( 'inspect' ) ) ) . toBe ( false ) ;
132+ expect ( log ) . toContain ( 'release P1 after failed' ) ;
133+ expect ( store . getSnapshot ( identity ) . head ) . toBe ( oid ( 2 ) ) ;
134+ } ) ;
135+ it ( 'releases task storage after the terminal write when D\'s start call throws' , async ( ) => {
136+ const { executor, log } = setup ( { startError : new Error ( 'docker refused' ) } ) ;
137+ expect ( await executor . runTask ( identity ) ) . toMatchObject ( { kind : 'stopped' , item : 'P1' , state : 'failed' , reason : 'Launch failed: docker refused' } ) ;
138+ expect ( log ) . toContain ( 'release P1 after failed' ) ;
139+ } ) ;
127140} ) ;
0 commit comments