Skip to content

Startup commands frequently don't run on launch (panes processed before PTY session is ready; background workspace never connects) #14

@frd1201

Description

@frd1201

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.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

  1. Create two workspaces, each with multiple panes and a startupCommand, both launchOnStartup: true.
  2. Start Tabby (cold).
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions