Skip to content

fix: move expensive operations off UI event loop and fix stale preview pane#253

Open
miamachine wants to merge 6 commits intosmtg-ai:mainfrom
miamachine:fix/async-metadata-updates
Open

fix: move expensive operations off UI event loop and fix stale preview pane#253
miamachine wants to merge 6 commits intosmtg-ai:mainfrom
miamachine:fix/async-metadata-updates

Conversation

@miamachine
Copy link

@miamachine miamachine commented Feb 26, 2026

Summary

Three related fixes that improve Claude Squad UI responsiveness and fix stale preview state:

1. Move metadata updates off the UI event loop

  • Root cause: The tickUpdateMetadataMessage handler runs git add -N . + git diff for every active instance synchronously on the Bubble Tea event loop. On large repos (225K files / 30GB), each instance takes ~2.6s, so 5 instances blocks all input processing for ~13 seconds per tick.
  • Fix: Move the per-instance metadata work (HasUpdated + diff computation) into a tea.Cmd that runs in a background goroutine, and parallelize across instances with a sync.WaitGroup. Results are sent back as a metadataUpdateDoneMsg and applied on the main goroutine to avoid data races with View.
  • Guard against pile-up: A metadataUpdating flag prevents overlapping ticks from spawning concurrent updates.

2. Move session creation off the UI event loop

  • Root cause: instance.Start(true) (git worktree setup, tmux session creation, trust screen detection) blocks the bubbletea Update handler for 30-60+ seconds, especially with CrowdStrike.
  • Fix: Run instance.Start(true) as an async tea.Cmd with a Loading status for visual feedback (spinner). Guards prevent user interaction with not-yet-started instances.
  • Bonus: Fixes a bug where newInstanceFinalizer() was called twice per session creation, double-counting the repo in the multi-repo display.
  • Credit: Jacob Massey

3. Refresh preview pane after Ctrl+Q detach

  • Root cause: After pressing Ctrl+Q to detach from an attached tmux session, the onDismiss callback set m.state = stateDefault but never called m.instanceChanged(), so the preview pane stayed frozen showing stale content from before the attach.
  • Fix: Add m.instanceChanged() call after detach, matching the pattern used by every other showHelpScreen callback (checkout, kill, etc.).

Impact

  • UI blocking drops from ~13s to near-zero during metadata ticks
  • Session creation no longer freezes the UI
  • Preview pane correctly refreshes after returning from an attached session

Test plan

  • go build ./... compiles cleanly
  • Manual testing: run cs with 5+ instances on a large repo, verify input is responsive
  • Verify instance status indicators (Running/Ready) still update correctly
  • Verify diff stats in the diff tab still update correctly
  • Verify AutoYes (TapEnter on prompt) still triggers correctly
  • Create a new instance, verify spinner shows during startup and UI stays responsive
  • Attach to a session (Enter), detach (Ctrl+Q), verify preview updates immediately

🤖 Generated with Claude Code

The tickUpdateMetadataMessage handler was running expensive git and tmux
operations synchronously on the Bubble Tea event loop. For large repos
(e.g. 225K files / 30GB), each tick spawned `git add -N .` and
`git diff` for every active instance sequentially — taking 10+ seconds
and blocking all keypress processing during that time.

Move the per-instance metadata work (HasUpdated + diff computation) into
a tea.Cmd that runs in a background goroutine, and parallelize across
instances using a WaitGroup. Results are sent back as a message and
applied on the main goroutine to avoid data races with View.

This reduces UI blocking from ~13s (5 instances × ~2.6s serial) to
near-zero, since the event loop returns immediately and the I/O runs
concurrently.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
@github-actions
Copy link

github-actions bot commented Feb 26, 2026

Thank you for your contribution! Please sign the CLA before we can merge your pull request. You can sign the CLA by just posting a comment following the below format.


I have read the CLA Document and I hereby sign the CLA


0 out of 3 committers have signed the CLA.
@miaemyle
@jacobmassey
@mia hebert
mia hebert seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

@miamachine
Copy link
Author

miamachine commented Feb 27, 2026

I have read the CLA Document and I hereby sign the CLA

(code was co-written with Claude, and this human reviewed the changes thoroughly and tested manually as noted above -- for more context, I've been using this tool with an extremely large monolith and the update loop was making the tool practically unusable for my needs)

@miamachine
Copy link
Author

recheck

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Copy link
Member

@mufeez-amjad mufeez-amjad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@miamachine Thanks for the PR!

I think you can simplify this – this should work with just one self-chaining Cmd:

func tickUpdateMetadataCmd(instances []*session.Instance) tea.Cmd {
    return func() tea.Msg {
        time.Sleep(500 * time.Millisecond)
        // filter active, spawn goroutines, wg.Wait()
        return metadataUpdateDoneMsg{results: results}
    }
}

jacobmassey and others added 2 commits February 27, 2026 17:25
Run instance.Start(true) as an async tea.Cmd instead of blocking the
bubbletea Update handler. This keeps the UI responsive during git
worktree setup, tmux session creation, and trust screen detection
(which can take 30-60+ seconds, especially with CrowdStrike).

Also fixes a bug where newInstanceFinalizer() was called twice per
session creation, double-counting the repo in the multi-repo display.

Co-Authored-By: Claude <noreply@anthropic.com>
After pressing Ctrl+Q to detach from an attached tmux session, the
preview pane was not updated to reflect the current instance state.
The onDismiss callback set m.state = stateDefault but never called
m.instanceChanged(), so the preview stayed frozen until the next
tick happened to fire. This matches the pattern used by every other
showHelpScreen callback (checkout, kill, etc.).

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
@miamachine miamachine changed the title fix: move metadata updates off UI event loop to prevent input lag fix: move expensive operations off UI event loop and fix stale preview pane Feb 28, 2026
@jayshrivastava
Copy link
Collaborator

Similar PR: #249

Per reviewer suggestion: merge tickUpdateMetadataCmd and
runMetadataUpdateCmd into one self-chaining function. The sleep,
filtering, parallel goroutines, and wg.Wait all happen in a single
Cmd closure that returns metadataUpdateDoneMsg directly.

This eliminates the intermediate tickUpdateMetadataMessage type,
the metadataUpdating guard flag, and the two-step dispatch,
making the intent clearer and the control flow simpler.

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Author

@miamachine miamachine left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done @mufeez-amjad !

merged the sleep + filtering + goroutines + wg.Wait() all into a single self-chaining tickUpdateMetadataCmd(instances []*session.Instance) tea.Cmd. This eliminates the intermediate tickUpdateMetadataMessage type, the metadataUpdating guard flag, and the two-step dispatch. Much cleaner — thanks for the suggestion.

resolves conflicts by:
- adopting upstream's instanceStartedMsg for async start (already merged)
- keeping our self-chaining tickUpdateMetadataCmd with parallel goroutines
- adding upstream's CheckAndHandleTrustPrompt to metadata goroutine
- merging upstream's terminal tab feature
- removing duplicate Loading case in list.go

Co-Authored-By: Claude <noreply@anthropic.com>
@miamachine miamachine requested a review from mufeez-amjad March 9, 2026 02:29
@miamachine
Copy link
Author

recheck

@miamachine
Copy link
Author

re: CLA idk why but my username is weird -- @miaemyle and @miamachine point to the same user though not sure why only the former shows up (which might be an issue for the bot to approve this PR)

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.

5 participants