feat(sync): add timeout message for long-running git operations (fixes #647)

When git push or other git operations hang waiting for credential/browser
auth, show a helpful message to the user after 5 seconds of inactivity
instead of appearing frozen.

Added:
- runCmdWithTimeoutMessage() in internal/syncbranch/worktree.go
- runGitCmdWithTimeoutMsg() in cmd/bd/sync.go
- Both functions print a message after timeout delay with advice
  to check for browser auth prompts

Fixes issue #647 (bd sync frozen waiting for browser auth)
This commit is contained in:
matt wilkie
2025-12-21 12:05:13 -07:00
parent 16518223a4
commit 03118e7226
3 changed files with 185 additions and 173 deletions

View File

@@ -774,6 +774,33 @@ func fetchAndRebaseInWorktree(ctx context.Context, worktreePath, branch, remote
return nil
}
// runCmdWithTimeoutMessage runs a command and prints a helpful message if it takes too long.
// This helps when git operations hang waiting for credential/browser auth.
//
// Parameters:
// - ctx: Context for cancellation
// - timeoutMsg: Message to print when timeout is reached (e.g., "Waiting for Git authentication in browser...")
// - timeoutDelay: Duration to wait before printing message (e.g., 5 seconds)
// - cmd: The command to run
//
// Returns: combined output and error from the command
func runCmdWithTimeoutMessage(ctx context.Context, timeoutMsg string, timeoutDelay time.Duration, cmd *exec.Cmd) ([]byte, error) {
// Start a timer to print a message if the command takes too long
timerChan := make(chan struct{}, 1)
go func() {
select {
case <-time.After(timeoutDelay):
fmt.Fprintf(os.Stderr, "⏳ %s\n", timeoutMsg)
timerChan <- struct{}{}
case <-ctx.Done():
// Context cancelled, don't print message
}
}()
output, err := cmd.CombinedOutput()
return output, err
}
// pushFromWorktree pushes the sync branch from the worktree with retry logic
// for handling concurrent push conflicts (non-fast-forward errors).
func pushFromWorktree(ctx context.Context, worktreePath, branch string) error {
@@ -787,7 +814,14 @@ func pushFromWorktree(ctx context.Context, worktreePath, branch string) error {
// Set BD_SYNC_IN_PROGRESS so pre-push hook knows to skip checks (GH#532)
// This prevents circular error where hook suggests running bd sync
cmd.Env = append(os.Environ(), "BD_SYNC_IN_PROGRESS=1")
output, err := cmd.CombinedOutput()
// Run with timeout message in case of hanging auth
output, err := runCmdWithTimeoutMessage(
ctx,
fmt.Sprintf("Git push is waiting (possibly for authentication). If this hangs, check for a browser auth prompt."),
5*time.Second,
cmd,
)
if err == nil {
return nil // Success