@@ -4063,11 +4063,16 @@ export async function runFailureGet(
40634063) : Promise < FailureGetResult > {
40644064 const out = makeOutput ( opts . output , deps ) ;
40654065 const client = makeClient ( opts , deps ) ;
4066- // We resolve the output dir BEFORE the network call so a missing /
4066+
4067+ // Resolve and validate --out BEFORE the network call so a missing /
40674068 // empty path surfaces as VALIDATION_ERROR (exit 5) without spending
4068- // an API call. `writeBundle` re-validates internally; this is the
4069- // fast-fail.
4070- const requestedDir = opts . out ;
4069+ // an API call. Mirrors `runArtifactGet`; `writeBundle` re-validates
4070+ // internally as defense-in-depth.
4071+ let resolvedDir : string | undefined ;
4072+ if ( opts . out !== undefined ) {
4073+ resolvedDir = resolveBundleDir ( opts . out ) ;
4074+ await assertOutDirParentExists ( resolvedDir ) ;
4075+ }
40714076
40724077 const context = await client . get < CliFailureContext > (
40734078 `/tests/${ encodeURIComponent ( opts . testId ) } /failure` ,
@@ -4080,7 +4085,7 @@ export async function runFailureGet(
40804085 // internally; this call is the cheap upfront trap.
40814086 assertContextIntegrity ( context , 'local' ) ;
40824087
4083- if ( requestedDir !== undefined ) {
4088+ if ( resolvedDir !== undefined ) {
40844089 // Dry-run: do NOT call writeBundle (which would mkdir, fetch
40854090 // presigned URLs, and write files). Print the would-be bundle layout
40864091 // to stderr and emit the wire envelope to stdout so the agent sees
@@ -4089,18 +4094,18 @@ export async function runFailureGet(
40894094 const stderr = deps . stderr ?? ( ( line : string ) => process . stderr . write ( `${ line } \n` ) ) ;
40904095 const fileNames = plannedBundleFiles ( context , opts . failedOnly ) ;
40914096 stderr (
4092- `[dry-run] would write bundle to ${ requestedDir } (${ fileNames . length } files; meta.json renames last)` ,
4097+ `[dry-run] would write bundle to ${ resolvedDir } (${ fileNames . length } files; meta.json renames last)` ,
40934098 ) ;
40944099 for ( const f of fileNames ) stderr ( `[dry-run] ${ f } ` ) ;
40954100 if ( opts . output === 'json' ) {
4096- out . print ( { ok : true , dir : requestedDir , dryRun : true , context } ) ;
4101+ out . print ( { ok : true , dir : resolvedDir , dryRun : true , context } ) ;
40974102 } else {
40984103 // Use a dry-run-specific renderer: the real success renderer
40994104 // says "Bundle written to ..." which would be a lie here. Stdout
41004105 // is the success contract automation may parse, so it must not
41014106 // imply the bundle was created.
41024107 out . print (
4103- { dir : requestedDir , files : fileNames . length , snapshotId : context . snapshotId } ,
4108+ { dir : resolvedDir , files : fileNames . length , snapshotId : context . snapshotId } ,
41044109 data =>
41054110 renderBundleDryRunText ( data as { dir : string ; files : number ; snapshotId : string } ) ,
41064111 ) ;
@@ -4109,7 +4114,7 @@ export async function runFailureGet(
41094114 }
41104115
41114116 const bundle = await writeBundle ( context , {
4112- dir : requestedDir ,
4117+ dir : resolvedDir ,
41134118 failedOnly : opts . failedOnly ,
41144119 fetchImpl : deps . fetchImpl ,
41154120 } ) ;
0 commit comments