@@ -101,7 +101,7 @@ describe('run status files', () => {
101101interface FakeTmuxState {
102102 nextWindow : number
103103 nextPane : number
104- panes : Record < string , { window : string ; options : Record < string , string > } >
104+ panes : Record < string , { window : string ; options : Record < string , string > ; command ?: string } >
105105 /** Every command that reached a pane: `send-keys %1 C-c`, `kill-pane %1`. */
106106 log : string [ ]
107107 /** Commands the fake fails, with the error tmux would print. */
@@ -149,7 +149,7 @@ switch (args[0]) {
149149 case 'new-window': {
150150 const window = '@' + state.nextWindow++
151151 const pane = '%' + state.nextPane++
152- state.panes[pane] = { window, options: {} }
152+ state.panes[pane] = { window, options: {}, command: args[args.length - 1] }
153153 save()
154154 // Runs the pane's command for real, the way tmux would, when a test asks for it.
155155 if (process.env.FAKE_TMUX_EXEC) {
@@ -171,7 +171,13 @@ switch (args[0]) {
171171 // Like tmux 3.x, a pane that is gone answers with an empty line rather than an error.
172172 const pane = state.panes[target()]
173173 const name = args[args.length - 1].slice(2, -1)
174- const value = !pane ? '' : name === 'pane_id' ? target() : (pane.options[name] ?? '')
174+ const value = !pane
175+ ? ''
176+ : name === 'pane_id'
177+ ? target()
178+ : name === 'pane_start_command'
179+ ? (pane.command ?? '')
180+ : (pane.options[name] ?? '')
175181 process.stdout.write(value + '\\n')
176182 break
177183 }
@@ -367,6 +373,25 @@ describe('stopping a tmux run touches only its own pane', () => {
367373 expect ( Object . keys ( tmux . read ( ) . panes ) ) . toEqual ( [ ] )
368374 } )
369375
376+ it ( "never closes a pane that took a finished untracked run's id after tmux restarted" , async ( ) => {
377+ const tmux = fakeTmux ( )
378+ dirs . push ( tmux . dir )
379+ tmux . write ( { ...tmux . read ( ) , fail : { 'set-option' : 'invalid option: @sim-run-id' } } )
380+ const run = await startRun ( 'agent' , 'make build' , null , tmux . env )
381+ if ( 'error' in run ) throw new Error ( run . error )
382+ writeFileSync ( run . statusPath , '0' )
383+ tmux . restart ( )
384+ // The user's own shell gets the ids the run's pane had.
385+ const state = tmux . read ( )
386+ state . panes [ run . pane ] = { window : run . window , options : { } , command : 'zsh' }
387+ tmux . write ( state )
388+
389+ await closeRunPane ( run , tmux . env )
390+
391+ expect ( tmux . read ( ) . log ) . toEqual ( [ ] )
392+ expect ( Object . keys ( tmux . read ( ) . panes ) ) . toEqual ( [ run . pane ] )
393+ } )
394+
370395 it ( 'lets a tagged run start only once its pane is tagged' , async ( ) => {
371396 const tmux = fakeTmux ( )
372397 const run = await started ( tmux )
0 commit comments