@@ -100,7 +100,7 @@ describe('run status files', () => {
100100interface FakeTmuxState {
101101 nextWindow : number
102102 nextPane : number
103- panes : Record < string , { window : string ; options : Record < string , string > } >
103+ panes : Record < string , { window : string ; options : Record < string , string > ; command ?: string } >
104104 /** Every command that reached a pane: `send-keys %1 C-c`, `kill-pane %1`. */
105105 log : string [ ]
106106 /** Commands the fake fails, with the error tmux would print. */
@@ -132,7 +132,7 @@ switch (args[0]) {
132132 case 'new-window': {
133133 const window = '@' + state.nextWindow++
134134 const pane = '%' + state.nextPane++
135- state.panes[pane] = { window, options: {} }
135+ state.panes[pane] = { window, options: {}, command: args[args.length - 1] }
136136 save()
137137 // Runs the pane's command for real, the way tmux would, when a test asks for it.
138138 if (process.env.FAKE_TMUX_EXEC) {
@@ -154,7 +154,13 @@ switch (args[0]) {
154154 // Like tmux 3.x, a pane that is gone answers with an empty line rather than an error.
155155 const pane = state.panes[target()]
156156 const name = args[args.length - 1].slice(2, -1)
157- const value = !pane ? '' : name === 'pane_id' ? target() : (pane.options[name] ?? '')
157+ const value = !pane
158+ ? ''
159+ : name === 'pane_id'
160+ ? target()
161+ : name === 'pane_start_command'
162+ ? (pane.command ?? '')
163+ : (pane.options[name] ?? '')
158164 process.stdout.write(value + '\\n')
159165 break
160166 }
@@ -316,6 +322,25 @@ describe('stopping a tmux run touches only its own pane', () => {
316322 expect ( Object . keys ( tmux . read ( ) . panes ) ) . toEqual ( [ ] )
317323 } )
318324
325+ it ( "never closes a pane that took a finished untracked run's id after tmux restarted" , async ( ) => {
326+ const tmux = fakeTmux ( )
327+ dirs . push ( tmux . dir )
328+ tmux . write ( { ...tmux . read ( ) , fail : { 'set-option' : 'invalid option: @sim-run-id' } } )
329+ const run = await startRun ( 'agent' , 'make build' , null , tmux . env )
330+ if ( 'error' in run ) throw new Error ( run . error )
331+ writeFileSync ( run . statusPath , '0' )
332+ tmux . restart ( )
333+ // The user's own shell gets the ids the run's pane had.
334+ const state = tmux . read ( )
335+ state . panes [ run . pane ] = { window : run . window , options : { } , command : 'zsh' }
336+ tmux . write ( state )
337+
338+ await closeRunPane ( run , tmux . env )
339+
340+ expect ( tmux . read ( ) . log ) . toEqual ( [ ] )
341+ expect ( Object . keys ( tmux . read ( ) . panes ) ) . toEqual ( [ run . pane ] )
342+ } )
343+
319344 it ( 'lets a tagged run start only once its pane is tagged' , async ( ) => {
320345 const tmux = fakeTmux ( )
321346 const run = await started ( tmux )
0 commit comments