@@ -31,6 +31,7 @@ function run(args: string[], cwd: string): string {
3131
3232class FakeTmuxGateway implements TmuxGateway {
3333 private readonly windows = new Map < string , TmuxWindowSummary > ( ) ;
34+ readonly commands : Array < { target : string ; command : string } > = [ ] ;
3435
3536 ensureServer ( ) : void { }
3637
@@ -69,7 +70,9 @@ class FakeTmuxGateway implements TmuxGateway {
6970
7071 setWindowOption ( _sessionName : string , _windowName : string , _option : string , _value : string ) : void { }
7172
72- runCommand ( _target : string , _command : string ) : void { }
73+ runCommand ( target : string , command : string ) : void {
74+ this . commands . push ( { target, command } ) ;
75+ }
7376
7477 selectPane ( _target : string ) : void { }
7578
@@ -110,15 +113,15 @@ class FakeHookRunner implements LifecycleHookRunner {
110113 readonly calls : RunLifecycleHookInput [ ] = [ ] ;
111114
112115 constructor (
113- private readonly onRun ?: ( input : RunLifecycleHookInput ) => void ,
116+ private readonly onRun ?: ( input : RunLifecycleHookInput ) => void | Promise < void > ,
114117 ) { }
115118
116119 async run ( input : RunLifecycleHookInput ) : Promise < void > {
117120 this . calls . push ( {
118121 ...input ,
119122 env : { ...input . env } ,
120123 } ) ;
121- this . onRun ?.( input ) ;
124+ await this . onRun ?.( input ) ;
122125 }
123126}
124127
@@ -340,6 +343,47 @@ describe("LifecycleService", () => {
340343 expect ( state ?. session . paneCount ) . toBe ( 2 ) ;
341344 } ) ;
342345
346+ it ( "refreshes runtime env after postCreate so system prompts see .env.local values" , async ( ) => {
347+ const repoRoot = await initRepo ( ) ;
348+ const runtime = new ProjectRuntime ( ) ;
349+ const tmux = new FakeTmuxGateway ( ) ;
350+ const databaseUrl = "postgres://postgres:changeme@127.0.0.1:5432/windmill_feature_prompt?sslmode=disable" ;
351+ const hooks = new FakeHookRunner ( async ( input ) => {
352+ await Bun . write ( join ( input . cwd , ".env.local" ) , `DATABASE_URL=${ databaseUrl } \n` ) ;
353+ } ) ;
354+ const lifecycle = makeLifecycleService (
355+ repoRoot ,
356+ tmux ,
357+ runtime ,
358+ new FakeDockerGateway ( ) ,
359+ hooks ,
360+ {
361+ ...TEST_CONFIG ,
362+ profiles : {
363+ ...TEST_CONFIG . profiles ,
364+ default : {
365+ ...TEST_CONFIG . profiles . default ,
366+ systemPrompt : "Database: ${DATABASE_URL}" ,
367+ } ,
368+ } ,
369+ } ,
370+ ) ;
371+
372+ await lifecycle . createWorktree ( {
373+ branch : "feature/prompt-env" ,
374+ } ) ;
375+
376+ const worktreePath = join ( repoRoot , "__worktrees" , "feature" , "prompt-env" ) ;
377+ const gitDir = new BunGitGateway ( ) . resolveWorktreeGitDir ( worktreePath ) ;
378+ const runtimeEnvText = await Bun . file ( getWorktreeStoragePaths ( gitDir ) . runtimeEnvPath ) . text ( ) ;
379+ const agentCommand = tmux . commands . find ( ( { target } ) =>
380+ target === `${ buildProjectSessionName ( repoRoot ) } :${ buildWorktreeWindowName ( "feature/prompt-env" ) } .0`
381+ ) ?. command ;
382+
383+ expect ( runtimeEnvText ) . toContain ( databaseUrl ) ;
384+ expect ( agentCommand ) . toContain ( `Database: ${ databaseUrl } ` ) ;
385+ } ) ;
386+
343387 it ( "creates a managed worktree under an absolute worktree root" , async ( ) => {
344388 const repoRoot = await initRepo ( ) ;
345389 const absoluteWorktreeRoot = await mkdtemp ( join ( tmpdir ( ) , "webmux-absolute-worktrees-" ) ) ;
0 commit comments