@@ -34,6 +34,11 @@ describe("restart-helper", () => {
3434 throw error ;
3535 }
3636 } ) ;
37+ await fs . rmdir ( path . dirname ( scriptPath ) ) . catch ( ( error : unknown ) => {
38+ if ( ( error as NodeJS . ErrnoException ) . code !== "ENOENT" ) {
39+ throw error ;
40+ }
41+ } ) ;
3742 }
3843
3944 async function makeTempDir ( prefix : string ) {
@@ -135,9 +140,63 @@ exit 0
135140 expect ( content ) . toContain ( "systemctl --user restart 'openclaw-gateway.service'" ) ;
136141 // Script should self-cleanup
137142 expect ( content ) . toContain ( 'rm -f "$0"' ) ;
143+ expect ( content ) . toContain ( 'rmdir "$script_dir" 2>/dev/null || true' ) ;
138144 await cleanupScript ( scriptPath ) ;
139145 } ) ;
140146
147+ it ( "creates restart scripts in a private temp directory with exclusive creation" , async ( ) => {
148+ Object . defineProperty ( process , "platform" , { value : "linux" } ) ;
149+ const timestamp = 1_727_201_234_567 ;
150+ const oldCandidatePath = path . join ( os . tmpdir ( ) , `openclaw-restart-${ timestamp } .sh` ) ;
151+ const victimDir = await makeTempDir ( "openclaw-restart-helper-victim-" ) ;
152+ const victimPath = path . join ( victimDir , "restart.sh" ) ;
153+ await fs . rm ( oldCandidatePath , { force : true } ) ;
154+ await fs . writeFile ( victimPath , "preexisting script\n" , "utf-8" ) ;
155+
156+ let candidateIsSymlink = false ;
157+ try {
158+ await fs . symlink ( victimPath , oldCandidatePath ) ;
159+ candidateIsSymlink = true ;
160+ } catch {
161+ await fs . writeFile ( oldCandidatePath , "preexisting script\n" , { flag : "wx" } ) ;
162+ }
163+
164+ const dateSpy = vi . spyOn ( Date , "now" ) . mockReturnValue ( timestamp ) ;
165+ const writeFileSpy = vi . spyOn ( fs , "writeFile" ) ;
166+
167+ try {
168+ const { scriptPath } = await prepareAndReadScript ( {
169+ OPENCLAW_PROFILE : "default" ,
170+ } ) ;
171+ const scriptDir = path . dirname ( scriptPath ) ;
172+ const relativeScriptDir = path . relative ( os . tmpdir ( ) , scriptDir ) ;
173+
174+ expect ( scriptPath ) . not . toBe ( oldCandidatePath ) ;
175+ expect ( scriptDir ) . not . toBe ( os . tmpdir ( ) ) ;
176+ expect ( relativeScriptDir ) . not . toBe ( "" ) ;
177+ expect ( relativeScriptDir . startsWith ( ".." ) ) . toBe ( false ) ;
178+ expect ( path . isAbsolute ( relativeScriptDir ) ) . toBe ( false ) ;
179+ expect ( path . basename ( scriptDir ) ) . toMatch ( / ^ o p e n c l a w - r e s t a r t - / ) ;
180+ expect ( writeFileSpy ) . toHaveBeenLastCalledWith (
181+ scriptPath ,
182+ expect . any ( String ) ,
183+ expect . objectContaining ( { flag : "wx" , mode : 0o755 } ) ,
184+ ) ;
185+ await expect ( fs . readFile ( victimPath , "utf-8" ) ) . resolves . toBe ( "preexisting script\n" ) ;
186+ if ( ! candidateIsSymlink ) {
187+ await expect ( fs . readFile ( oldCandidatePath , "utf-8" ) ) . resolves . toBe (
188+ "preexisting script\n" ,
189+ ) ;
190+ }
191+ await cleanupScript ( scriptPath ) ;
192+ } finally {
193+ dateSpy . mockRestore ( ) ;
194+ writeFileSpy . mockRestore ( ) ;
195+ await fs . rm ( oldCandidatePath , { force : true } ) ;
196+ await fs . rm ( victimDir , { recursive : true , force : true } ) ;
197+ }
198+ } ) ;
199+
141200 it ( "uses OPENCLAW_SYSTEMD_UNIT override for systemd scripts" , async ( ) => {
142201 Object . defineProperty ( process , "platform" , { value : "linux" } ) ;
143202 const { scriptPath, content } = await prepareAndReadScript ( {
@@ -203,6 +262,7 @@ exit 1
203262 expect ( content ) . toContain ( "launchctl bootstrap 'gui/501'" ) ;
204263 expect ( content ) . toContain ( "Bootstrap loads RunAtLoad agents" ) ;
205264 expect ( content ) . toContain ( 'rm -f "$0"' ) ;
265+ expect ( content ) . toContain ( 'rmdir "$script_dir" 2>/dev/null || true' ) ;
206266 await cleanupScript ( scriptPath ) ;
207267 } ) ;
208268
@@ -379,6 +439,7 @@ exit 0
379439 expect ( content ) . toContain ( "openclaw restart launched startup fallback" ) ;
380440 expectWindowsRestartWaitOrdering ( content ) ;
381441 expect ( content ) . toContain ( 'del "%~f0" >nul 2>&1' ) ;
442+ expect ( content ) . toContain ( 'rmdir "%OPENCLAW_RESTART_SCRIPT_DIR%" >nul 2>&1' ) ;
382443 await cleanupScript ( scriptPath ) ;
383444 } ) ;
384445
0 commit comments