From 1711ae3e512eba77f6cb285bc495c05b238d3a71 Mon Sep 17 00:00:00 2001 From: Justin Leveck Date: Thu, 6 Aug 2026 07:29:40 -0700 Subject: [PATCH] Don't fail repository fetch when remote is unreachable git fetch was not wrapped in its own error handling, so a dead remote, expired SSH key, or offline network took down the entire "fetch changes" request even though the local repo (which is all the analytics endpoints actually read) was perfectly fine. Catch the fetch failure specifically, skip the pull step when fetch didn't succeed, and fall back to reporting on local data with a fetchError message - the client already surfaces pullError the same way, so this is an additive, non-breaking response field. --- server/src/routes/repositories.ts | 41 ++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/server/src/routes/repositories.ts b/server/src/routes/repositories.ts index ddb9d1e..d5a58e9 100644 --- a/server/src/routes/repositories.ts +++ b/server/src/routes/repositories.ts @@ -160,24 +160,34 @@ router.post('/:id/fetch', async (req: Request, res: Response) => { // Get current commit hash before fetch const beforeHash = await git.revparse(['HEAD']); - // Fetch from all remotes - await git.fetch(['--all', '--prune']); + // Fetch from all remotes (gracefully handle unreachable remotes) + let fetched = false; + let fetchError: string | null = null; + try { + await git.fetch(['--all', '--prune']); + fetched = true; + } catch (error: any) { + fetchError = error.message || 'Fetch failed'; + console.warn(`Remote fetch failed for ${repository.name}, continuing with local data: ${fetchError}`); + } // Get current branch const branch = await git.revparse(['--abbrev-ref', 'HEAD']); - // Try to pull changes for current branch (if it has an upstream) + // Try to pull changes for current branch (only if fetch succeeded) let pulled = false; let pullError: string | null = null; - try { - const pullResult = await git.pull(); - pulled = - pullResult.summary.changes > 0 || - pullResult.summary.insertions > 0 || - pullResult.summary.deletions > 0; - } catch (error: any) { - // Pull might fail if there's no upstream or local changes - pullError = error.message || 'Pull failed'; + if (fetched) { + try { + const pullResult = await git.pull(); + pulled = + pullResult.summary.changes > 0 || + pullResult.summary.insertions > 0 || + pullResult.summary.deletions > 0; + } catch (error: any) { + // Pull might fail if there's no upstream or local changes + pullError = error.message || 'Pull failed'; + } } // Get commit hash after fetch/pull @@ -199,17 +209,20 @@ router.post('/:id/fetch', async (req: Request, res: Response) => { path: repository.path, }, changes: { - fetched: true, + fetched, pulled, hasChanges, branch: branch.trim(), beforeHash: beforeHash.trim(), afterHash: afterHash.trim(), }, + fetchError, pullError, message: hasChanges ? `Successfully fetched and updated ${repository.name}` - : `Repository ${repository.name} is already up to date`, + : fetched + ? `Repository ${repository.name} is already up to date` + : `Remote unreachable for ${repository.name}, using local data`, }); } catch (error: any) { console.error('Error fetching repository:', error);