From 7c4122420d58d348a9a86c4656dd9ea42db0f24d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 08:25:00 +0000 Subject: [PATCH 1/5] Initial plan From 0c4e539da969928a24987be203363996b81d7011 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 08:28:16 +0000 Subject: [PATCH 2/5] Fix TUI breaking on checkout/sync failure by suppressing error output in non-verbose mode Co-authored-by: Dids <3455449+Dids@users.noreply.github.com> --- internal/sync/sync.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/sync/sync.go b/internal/sync/sync.go index 0fa00d2..c2b3269 100644 --- a/internal/sync/sync.go +++ b/internal/sync/sync.go @@ -400,7 +400,9 @@ func (s *Syncer) syncSingleRepo(ctx context.Context, repo *gh.Repository, result if err != nil { result.Failed[repoName] = err s.reportProgress(repoName, ProgressFailed, err.Error()) - fmt.Printf("Failed to update %s: %v\n", repoName, err) + if s.opts.Verbose { + fmt.Printf("Failed to update %s: %v\n", repoName, err) + } } else { if status == ProgressUpToDate { result.UpToDate = append(result.UpToDate, repoName) @@ -421,7 +423,9 @@ func (s *Syncer) syncSingleRepo(ctx context.Context, repo *gh.Repository, result if err := s.cloneRepo(ctx, repo, localPath); err != nil { result.Failed[repoName] = err s.reportProgress(repoName, ProgressFailed, err.Error()) - fmt.Printf("Failed to clone %s: %v\n", repoName, err) + if s.opts.Verbose { + fmt.Printf("Failed to clone %s: %v\n", repoName, err) + } } else { result.Cloned = append(result.Cloned, repoName) s.reportProgress(repoName, ProgressCloned, "") From 3449276be2ceaf0f208697a87be7bb2bd521cde5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 08:30:17 +0000 Subject: [PATCH 3/5] Guard dry-run output with verbose checks for consistency Co-authored-by: Dids <3455449+Dids@users.noreply.github.com> --- internal/sync/sync.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/internal/sync/sync.go b/internal/sync/sync.go index c2b3269..6abfdc8 100644 --- a/internal/sync/sync.go +++ b/internal/sync/sync.go @@ -303,11 +303,15 @@ func (s *Syncer) syncWorker(ctx context.Context, jobs <-chan *gh.Repository, res if s.opts.DryRun { if s.git.IsGitRepo(localPath) { - fmt.Printf("[DRY RUN] Would update: %s\n", repoName) + if s.opts.Verbose { + fmt.Printf("[DRY RUN] Would update: %s\n", repoName) + } s.reportProgress(repoName, ProgressUpdated, "dry-run") results <- syncResult{repoName: repoName, status: ProgressUpdated} } else { - fmt.Printf("[DRY RUN] Would clone: %s\n", repoName) + if s.opts.Verbose { + fmt.Printf("[DRY RUN] Would clone: %s\n", repoName) + } s.reportProgress(repoName, ProgressCloned, "dry-run") results <- syncResult{repoName: repoName, status: ProgressCloned} } @@ -379,11 +383,15 @@ func (s *Syncer) syncSingleRepo(ctx context.Context, repo *gh.Repository, result if s.opts.DryRun { if s.git.IsGitRepo(localPath) { - fmt.Printf("[DRY RUN] Would update: %s\n", repoName) + if s.opts.Verbose { + fmt.Printf("[DRY RUN] Would update: %s\n", repoName) + } s.reportProgress(repoName, ProgressUpdated, "dry-run") result.Updated = append(result.Updated, repoName) } else { - fmt.Printf("[DRY RUN] Would clone: %s\n", repoName) + if s.opts.Verbose { + fmt.Printf("[DRY RUN] Would clone: %s\n", repoName) + } s.reportProgress(repoName, ProgressCloned, "dry-run") result.Cloned = append(result.Cloned, repoName) } From 2bc7085b3ae6c017dd78257799f1f4fe2464c7a1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 08:36:47 +0000 Subject: [PATCH 4/5] Display error messages for failed repos in TUI completion summary Co-authored-by: Dids <3455449+Dids@users.noreply.github.com> --- internal/tui/screens/sync_progress.go | 50 +++++++++++++++++++++------ 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/internal/tui/screens/sync_progress.go b/internal/tui/screens/sync_progress.go index 6bf2382..8428deb 100644 --- a/internal/tui/screens/sync_progress.go +++ b/internal/tui/screens/sync_progress.go @@ -76,6 +76,9 @@ type SyncProgressScreen struct { // Current repo being synced currentRepo string + + // Failed repositories with error messages + failedRepos map[string]string // map[repoName]errorMessage } type syncProgressItem struct { @@ -97,15 +100,16 @@ func NewSyncProgress(ctx context.Context, app *tui.App) *SyncProgressScreen { s.Style = tui.GetStyles().Spinner return &SyncProgressScreen{ - ctx: ctx, - app: app, - styles: tui.GetStyles(), - keys: tui.GetKeyMap(), - progress: p, - spinner: s, - width: 80, - height: 24, - loading: true, + ctx: ctx, + app: app, + styles: tui.GetStyles(), + keys: tui.GetKeyMap(), + progress: p, + spinner: s, + width: 80, + height: 24, + loading: true, + failedRepos: make(map[string]string), } } @@ -295,6 +299,10 @@ func (s *SyncProgressScreen) Update(msg tea.Msg) (tea.Model, tea.Cmd) { s.collecting = false s.failed++ s.reposCompleted++ + // Store error message if provided + if msg.update.err != nil && msg.update.repoName != "" { + s.failedRepos[msg.update.repoName] = msg.update.err.Error() + } case "archived": s.collecting = false s.archived++ @@ -473,6 +481,16 @@ func (s *SyncProgressScreen) View() string { } if s.failed > 0 { content.WriteString(fmt.Sprintf(" %s Failed: %d\n", s.styles.Error.Render("●"), s.failed)) + // Show detailed error messages for failed repos + if len(s.failedRepos) > 0 { + content.WriteString("\n") + content.WriteString(s.styles.Error.Render("Failed repositories:")) + content.WriteString("\n") + for repoName, errMsg := range s.failedRepos { + content.WriteString(fmt.Sprintf(" • %s\n", repoName)) + content.WriteString(fmt.Sprintf(" %s\n", s.styles.Muted.Render(errMsg))) + } + } } if s.archived > 0 { content.WriteString(fmt.Sprintf(" %s Archived: %d (preserved locally, no longer on remote)\n", s.styles.Info.Render("●"), s.archived)) @@ -688,6 +706,7 @@ func (s *SyncProgressScreen) runSyncInBackground() { results := make(chan struct { status string idx int + err error }, len(allRepos)) // Track completed count for progress @@ -731,11 +750,18 @@ func (s *SyncProgressScreen) runSyncInBackground() { result, err := syncer.SyncRepoWithData(s.ctx, repo) status := "skipped" + var syncErr error if err != nil { status = "failed" + syncErr = err } else if result != nil { if len(result.Failed) > 0 { status = "failed" + // Get the error from the Failed map + for _, failErr := range result.Failed { + syncErr = failErr + break // Use the first error + } } else if len(result.Cloned) > 0 { status = "cloned" } else if len(result.Updated) > 0 { @@ -754,7 +780,8 @@ func (s *SyncProgressScreen) runSyncInBackground() { results <- struct { status string idx int - }{status: status, idx: idx} + err error + }{status: status, idx: idx, err: syncErr} } }() } @@ -794,6 +821,7 @@ func (s *SyncProgressScreen) runSyncInBackground() { status: res.status, current: int(completedCount), total: total, + err: res.err, } } @@ -866,7 +894,7 @@ type profileSyncProgressUpdate struct { status string // "collecting", "syncing", "cloned", "updated", "up-to-date", "skipped", "failed", "complete" current int total int - err error // Only set when status="complete" and there was an error + err error // Set when status="complete" for overall errors, or status="failed" for individual repo errors } type profileSyncProgressMsg struct { From 584e1514a54b9a86c2c9686adc47ad818687fd92 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 08:38:20 +0000 Subject: [PATCH 5/5] Sort failed repos alphabetically for consistent display Co-authored-by: Dids <3455449+Dids@users.noreply.github.com> --- internal/tui/screens/sync_progress.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/internal/tui/screens/sync_progress.go b/internal/tui/screens/sync_progress.go index 8428deb..170107d 100644 --- a/internal/tui/screens/sync_progress.go +++ b/internal/tui/screens/sync_progress.go @@ -3,6 +3,7 @@ package screens import ( "context" "fmt" + "sort" "strings" "time" @@ -486,7 +487,16 @@ func (s *SyncProgressScreen) View() string { content.WriteString("\n") content.WriteString(s.styles.Error.Render("Failed repositories:")) content.WriteString("\n") - for repoName, errMsg := range s.failedRepos { + + // Sort repo names for consistent display order + repoNames := make([]string, 0, len(s.failedRepos)) + for repoName := range s.failedRepos { + repoNames = append(repoNames, repoName) + } + sort.Strings(repoNames) + + for _, repoName := range repoNames { + errMsg := s.failedRepos[repoName] content.WriteString(fmt.Sprintf(" • %s\n", repoName)) content.WriteString(fmt.Sprintf(" %s\n", s.styles.Muted.Render(errMsg))) }