@@ -237,6 +237,40 @@ describe("exec approvals store helpers", () => {
237237 expect ( listExecApprovalTempFiles ( dir ) ) . toEqual ( [ ] ) ;
238238 } ) ;
239239
240+ it ( "normalizes fallback temp files before copying" , ( ) => {
241+ const dir = createHomeDir ( ) ;
242+ const approvalsPath = approvalsFilePath ( dir ) ;
243+ fs . mkdirSync ( path . dirname ( approvalsPath ) , { recursive : true } ) ;
244+ fs . writeFileSync ( approvalsPath , '{"version":1,"agents":{}}\n' , "utf8" ) ;
245+ const actualWriteFileSync = fs . writeFileSync . bind ( fs ) ;
246+ vi . spyOn ( fs , "writeFileSync" ) . mockImplementation ( ( file , data , options ) => {
247+ const result = actualWriteFileSync ( file , data , options as never ) ;
248+ const filePath = String ( file ) ;
249+ if (
250+ typeof file !== "number" &&
251+ filePath . includes ( ".exec-approvals." ) &&
252+ filePath . endsWith ( ".tmp" )
253+ ) {
254+ fs . chmodSync ( file , 0o000 ) ;
255+ }
256+ return result ;
257+ } ) ;
258+ const actualRenameSync = fs . renameSync . bind ( fs ) ;
259+ vi . spyOn ( fs , "renameSync" ) . mockImplementation ( ( from , to ) => {
260+ if ( String ( to ) === approvalsPath ) {
261+ const error = Object . assign ( new Error ( "locked target" ) , { code : "EPERM" } ) ;
262+ throw error ;
263+ }
264+ return actualRenameSync ( from , to ) ;
265+ } ) ;
266+
267+ saveExecApprovals ( { version : 1 , defaults : { security : "full" } , agents : { } } ) ;
268+
269+ expect ( fs . readFileSync ( approvalsPath , "utf8" ) ) . toContain ( '"security": "full"' ) ;
270+ expect ( fs . statSync ( approvalsPath ) . mode & 0o777 ) . toBe ( 0o600 ) ;
271+ expect ( listExecApprovalTempFiles ( dir ) ) . toEqual ( [ ] ) ;
272+ } ) ;
273+
240274 it ( "restores the previous approvals file when fallback copy fails" , ( ) => {
241275 const dir = createHomeDir ( ) ;
242276 const approvalsPath = approvalsFilePath ( dir ) ;
0 commit comments