You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Per-pane startup commands frequently don't run when a workspace launches — intermittently for a single workspace, and almost always for the second workspace when two have launchOnStartup: true. On Windows the delivery rate I measured was only ~11% of panes on cold start.
I instrumented this fairly thoroughly (replaced startup commands with marker-file writes to measure per-pane delivery objectively, and captured the plugin's own console.log output from the renderer via the Chrome DevTools Protocol). There are three independent root causes, all timing/lifecycle related.
Environment
TabbySpaces 0.2.7
Tabby 1.0.2xx, Windows 11
Profiles in use: clink, cmd, and PowerShell (default)
Two workspaces with launchOnStartup: true, 6 panes total, each running a claude --rc "…" startup command
Steps to reproduce
Create two workspaces, each with multiple panes and a startupCommand, both launchOnStartup: true.
Start Tabby (cold).
Observe: many panes never execute their startup command. The second workspace's panes fail almost every time.
Root causes (with evidence)
1. Pane discovery is a single 300 ms shot. StartupCommandService.onTabOpened() does setTimeout(() => this.processChildTabs(tab), 300) and calls getAllTabs() exactly once. If the split panes aren't constructed yet at 300 ms, getAllTabs() returns 0 and every command for that workspace is silently skipped — no retry.
Observed log: Found child tabs: 0.
2. The command is sent before the PTY session exists.
In processTerminalTab() the code checks terminalTab.session?.output$. At processing time the session is usually still null, so it takes the else branch and blind-fires setTimeout(sendCommand, 500) into a shell that isn't ready yet → the typed input is dropped (clink/conpty in particular drop input typed during shell init).
Observed log: No session.output$, falling back to timeout for every pane, followed by sends that never land.
3. Background-workspace panes never connect their session.
When two launchOnStartup workspaces open, only the active tab initializes its PTYs. The workspace that ends up in the background never gets a session at all, so there is nothing to send to.
Observed log: every background pane ends in a permanent "no session" state.
(Side note: the cwd-not-applied reports in #10/#12 may share root cause #2 — the pane isn't actually ready when the plugin acts on it.)
Proposed fix
Three coordinated changes (I have a working implementation and ~100% delivery after applying all three; happy to open a PR):
src/services/startupCommand.service.ts — poll for panes. Replace the single 300 ms processChildTabs call with a short re-scan loop that processes panes as they appear and stops only when all pending commands are handled (or a sane cap). Fixes Welcome to TabbySpaces Discussions! #1.
src/services/startupCommand.service.ts — wait for real readiness. Poll until session.output$ actually exists, then wait for the shell to be ready before sendInput. The most robust readiness signal I found is prompt detection: watch output$, strip ANSI, and fire once the output ends in a prompt char (> / $ / # / ❯), with an output-settle timeout only as a fallback. This replaces the blind 500 ms / first-output heuristic. Fixes The title and color set on Workspace Editor can be shown on tabs #2.
src/providers/toolbar.provider.ts — serialize startup launches. Open launchOnStartup workspaces one at a time, keeping each foreground until its sessions are connected and its commands sent, then open the next. Fixes Release v0.2.0 #3.
A cleaner long-term option
The fundamental fragility is typing the command into the terminal after the prompt appears. The fully race-free approach is to pass the command via the shell's own launch arguments instead — e.g. cmd /k "<cmd>", pwsh -NoExit -Command "<cmd>", bash -c '<cmd>; exec bash' — so the shell runs it itself when it's ready. That removes the timing heuristics and the need to serialize launches. (clink makes the cmd case a bit trickier since it's already injected via args.)
Measured impact of the typing-based fix above: ~11% → 100% pane delivery across cold starts on Windows (clink/cmd/PowerShell), including under load.
Happy to submit a PR if this direction sounds good.
Summary
Per-pane startup commands frequently don't run when a workspace launches — intermittently for a single workspace, and almost always for the second workspace when two have
launchOnStartup: true. On Windows the delivery rate I measured was only ~11% of panes on cold start.I instrumented this fairly thoroughly (replaced startup commands with marker-file writes to measure per-pane delivery objectively, and captured the plugin's own
console.logoutput from the renderer via the Chrome DevTools Protocol). There are three independent root causes, all timing/lifecycle related.Environment
clink,cmd, and PowerShell (default)launchOnStartup: true, 6 panes total, each running aclaude --rc "…"startup commandSteps to reproduce
startupCommand, bothlaunchOnStartup: true.Root causes (with evidence)
1. Pane discovery is a single 300 ms shot.
StartupCommandService.onTabOpened()doessetTimeout(() => this.processChildTabs(tab), 300)and callsgetAllTabs()exactly once. If the split panes aren't constructed yet at 300 ms,getAllTabs()returns 0 and every command for that workspace is silently skipped — no retry.Observed log:
Found child tabs: 0.2. The command is sent before the PTY session exists.
In
processTerminalTab()the code checksterminalTab.session?.output$. At processing time the session is usually stillnull, so it takes theelsebranch and blind-firessetTimeout(sendCommand, 500)into a shell that isn't ready yet → the typed input is dropped (clink/conpty in particular drop input typed during shell init).Observed log:
No session.output$, falling back to timeoutfor every pane, followed by sends that never land.3. Background-workspace panes never connect their session.
When two
launchOnStartupworkspaces open, only the active tab initializes its PTYs. The workspace that ends up in the background never gets a session at all, so there is nothing to send to.Observed log: every background pane ends in a permanent "no session" state.
(Side note: the
cwd-not-applied reports in #10/#12 may share root cause #2 — the pane isn't actually ready when the plugin acts on it.)Proposed fix
Three coordinated changes (I have a working implementation and ~100% delivery after applying all three; happy to open a PR):
src/services/startupCommand.service.ts— poll for panes. Replace the single 300 msprocessChildTabscall with a short re-scan loop that processes panes as they appear and stops only when all pending commands are handled (or a sane cap). Fixes Welcome to TabbySpaces Discussions! #1.src/services/startupCommand.service.ts— wait for real readiness. Poll untilsession.output$actually exists, then wait for the shell to be ready beforesendInput. The most robust readiness signal I found is prompt detection: watchoutput$, strip ANSI, and fire once the output ends in a prompt char (>/$/#/❯), with an output-settle timeout only as a fallback. This replaces the blind 500 ms / first-output heuristic. Fixes The title and color set on Workspace Editor can be shown on tabs #2.src/providers/toolbar.provider.ts— serialize startup launches. OpenlaunchOnStartupworkspaces one at a time, keeping each foreground until its sessions are connected and its commands sent, then open the next. Fixes Release v0.2.0 #3.A cleaner long-term option
The fundamental fragility is typing the command into the terminal after the prompt appears. The fully race-free approach is to pass the command via the shell's own launch arguments instead — e.g.
cmd /k "<cmd>",pwsh -NoExit -Command "<cmd>",bash -c '<cmd>; exec bash'— so the shell runs it itself when it's ready. That removes the timing heuristics and the need to serialize launches. (clink makes thecmdcase a bit trickier since it's already injected via args.)Measured impact of the typing-based fix above: ~11% → 100% pane delivery across cold starts on Windows (clink/cmd/PowerShell), including under load.
Happy to submit a PR if this direction sounds good.