Skip to content

Allow multiple ty instances via a granular per-executor lock#628

Merged
bborn merged 1 commit into
mainfrom
task/granular-executor-lock
Jul 6, 2026
Merged

Allow multiple ty instances via a granular per-executor lock#628
bborn merged 1 commit into
mainfrom
task/granular-executor-lock

Conversation

@bborn

@bborn bborn commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Why

Opening a second ty was refused outright ("a ty session is already running") — a blunt process-wide lock added to fix the dual-TUI interrupt-loop bug (#612). But the real constraint is narrower: only one TUI may borrow a given executor's tmux pane at a time. Two boards sitting on the kanban never actually conflict. This relaxes the lock to match the true constraint, so you can have ty open in more than one tab.

What changed

  • Drop the process-wide gate (acquireTUILock + the startup refusal in cmd/task/main.go). Multiple TUIs can now run at once.

  • Add a per-executor flock (acquireExecutorLock(taskID) in internal/ui): an exclusive lock on executor-<id>.lock next to the DB, taken at the single join chokepoint (joinTmuxPanes), held for the life of the detail view, released on Cleanup. When another instance owns it, the TUI refuses to join and shows:

    This executor is running in another ty instance. Close it there to view it here.

    It retries on the normal ~5s poll, so the view auto-recovers once the other instance releases the pane — no user action needed.

  • Crash-safe / isolation-safe (same properties as the old lock): flock releases on process exit, and the lock lives next to the DB so a custom WORKTREE_DB_PATH (QA harness, separate project) gets its own namespace.

  • Debug observability: expose pane_error / busy_elsewhere in the TUI debug state so the QA harness can assert the busy condition without screen-scraping.

The daemon, web viewer, and desktop app are unaffected — they attach non-destructively and never borrow panes.

Testing

  • Unit: acquireExecutorLock — same executor refuses a second holder, different executors don't contend, re-acquire works after release (internal/ui/executor_lock_test.go).
  • Real TUI on the isolated QA harness:
    1. Two TUIs coexist — both boot to the dashboard on the same DB (old lock would refuse the second).
    2. Granular refusal — with the executor lock held by another instance, opening the task shows the message and does not steal the pane (executor stays in the daemon window; the UI session has only its TUI pane).
    3. Auto-recovery — releasing the lock rejoins the executor on the next poll and clears the message.
  • go vet, gofmt, and the internal/ui + cmd/task suites are green.

🤖 Generated with Claude Code

Replace the process-wide TUI lock (which refused a second ty outright) with a
per-executor flock, so multiple TUIs can run side by side. A TUI now only
refuses to borrow an *individual* task's executor pane when another instance
already holds it — showing "This executor is running in another ty instance"
instead of fighting over the pane and trapping the agent in a claude --resume
interrupt loop.

- Drop acquireTUILock and the startup gate in cmd/task/main.go.
- Add acquireExecutorLock(taskID) in internal/ui: an exclusive flock on
  executor-<id>.lock next to the DB, acquired at the single join chokepoint
  (joinTmuxPanes), held for the life of the detail view, released on Cleanup.
  On refusal the view surfaces the message and retries on the normal poll, so
  it auto-recovers within ~5s once the other instance releases the pane.
- Expose pane_error / busy_elsewhere in the debug state so the QA harness can
  assert the busy condition without screen-scraping.

Verified on the isolated QA harness: two TUIs coexist; a held lock refuses the
join without stealing the pane; releasing it auto-rejoins.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bborn bborn merged commit e99922d into main Jul 6, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant