@@ -664,13 +664,41 @@ test("agenticoding settings TUI handles invalid JSON policies", async () => {
664664 } ) ;
665665} ) ;
666666
667+ test ( "agenticoding settings write path refuses non-ENOENT read failures without clobbering global settings" , async ( ) => {
668+ await withIsolatedSettings ( async ( { home, cwd } ) => {
669+ const globalPath = join ( home , ".pi" , "agent" , "settings.json" ) ;
670+ const original = JSON . stringify ( { packages : [ "keep" ] , handoff : { other : true } } ) ;
671+ await writeSettingsFile ( globalPath , original ) ;
672+ await chmod ( globalPath , 0o200 ) ;
673+
674+ const notifications : Array < { message : string ; level : string } > = [ ] ;
675+ const ctx = {
676+ cwd,
677+ hasUI : true ,
678+ ui : { notify : ( message : string , level : string ) => notifications . push ( { message, level } ) } ,
679+ } as any ;
680+
681+ try {
682+ assert . equal ( await writeGlobalHandoffResumeBehavior ( "proceed" , ctx ) , false ) ;
683+ } finally {
684+ await chmod ( globalPath , 0o600 ) ;
685+ }
686+
687+ assert . equal ( await readFile ( globalPath , "utf8" ) , original ) ;
688+ assert . equal ( notifications . length , 1 ) ;
689+ assert . equal ( notifications [ 0 ] . level , "error" ) ;
690+ assert . match ( notifications [ 0 ] . message , / U n a b l e t o r e a d g l o b a l s e t t i n g s J S O N / ) ;
691+ assert . match ( notifications [ 0 ] . message , / n o t w r i t i n g h a n d o f f \. r e s u m e B e h a v i o r / ) ;
692+ } ) ;
693+ } ) ;
694+
667695test ( "agenticoding settings write path handles save failure with error notification" , async ( ) => {
668696 await withIsolatedSettings ( async ( { home } ) => {
669- // Block the settings directory by creating a file where a directory is expected.
670- // writeGlobalHandoffResumeBehavior calls mkdir(dirname(path), { recursive: true }),
671- // so making .pi/agent a file (instead of a directory) will cause mkdir to throw ENOTDIR.
672- await mkdir ( join ( home , ".pi" ) , { recursive : true } ) ;
673- await writeFile ( join ( home , ".pi" , "agent" ) , "block" , "utf8" ) ;
697+ // Keep the read path in the ENOENT/create-new- file branch, then make the
698+ // existing settings directory non-writable so writeFile rejects.
699+ const settingsDir = join ( home , " .pi" , "agent" ) ;
700+ await mkdir ( settingsDir , { recursive : true } ) ;
701+ await chmod ( settingsDir , 0o500 ) ;
674702
675703 const notifications : Array < { message : string ; level : string } > = [ ] ;
676704 const ctx = {
@@ -679,10 +707,14 @@ test("agenticoding settings write path handles save failure with error notificat
679707 ui : { notify : ( message : string , level : string ) => notifications . push ( { message, level } ) } ,
680708 } as any ;
681709
682- await assert . rejects (
683- ( ) => writeGlobalHandoffResumeBehavior ( "proceed" , ctx ) ,
684- / E E X I S T | E N O T D I R | E N O S P C / ,
685- ) ;
710+ try {
711+ await assert . rejects (
712+ ( ) => writeGlobalHandoffResumeBehavior ( "proceed" , ctx ) ,
713+ / E A C C E S | E P E R M | E N O S P C / ,
714+ ) ;
715+ } finally {
716+ await chmod ( settingsDir , 0o700 ) ;
717+ }
686718 } ) ;
687719
688720 // The async IIFE in createAgenticodingSettingsComponent's callback wraps model.save
0 commit comments