Skip to content

Commit 102dc48

Browse files
Copilotalexr00
andauthored
Fix avatar fetch crashes and return generic user icon for TLS certificate issues (#7496)
* Initial plan * Initial analysis and plan for avatar fetch error handling Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Fix avatar fetch error handling for TLS certificate issues - Wrap second doFetch() call in try-catch to prevent crashes - Add proper error logging for avatar fetch failures - Return undefined gracefully when avatar fetching fails - Improve user identification in error messages for both accounts and teams - Add test to verify the fix handles TLS errors correctly * Return generic user icon instead of undefined for failed avatar fetches Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Use proper icon and better color --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com>
1 parent d2c1020 commit 102dc48

1 file changed

Lines changed: 19 additions & 2 deletions

File tree

src/common/uri.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import fetch from 'cross-fetch';
1111
import * as vscode from 'vscode';
1212
import { Repository } from '../api/api';
1313
import { EXTENSION_ID } from '../constants';
14-
import { IAccount, ITeam, reviewerId } from '../github/interface';
14+
import { IAccount, isTeam, ITeam, reviewerId } from '../github/interface';
1515
import { PullRequestModel } from '../github/pullRequestModel';
1616
import { GitChangeType } from './file';
1717
import Logger from './logger';
@@ -268,6 +268,16 @@ export namespace DataUri {
268268
return asImageDataURI(contents);
269269
}
270270

271+
function genericUserIconAsImageDataURI(width: number, height: number): vscode.Uri {
272+
// The account icon
273+
const foreground = vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark ? '#FFFFFF' : '#000000';
274+
const svgContent = `<svg width="${width}" height="${height}" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
275+
<path d="M16 7.992C16 3.58 12.416 0 8 0S0 3.58 0 7.992c0 2.43 1.104 4.62 2.832 6.09.016.016.032.016.032.032.144.112.288.224.448.336.08.048.144.111.224.175A7.98 7.98 0 0 0 8.016 16a7.98 7.98 0 0 0 4.48-1.375c.08-.048.144-.111.224-.16.144-.111.304-.223.448-.335.016-.016.032-.016.032-.032 1.696-1.487 2.8-3.676 2.8-6.106zm-8 7.001c-1.504 0-2.88-.48-4.016-1.279.016-.128.048-.255.08-.383a4.17 4.17 0 0 1 .416-.991c.176-.304.384-.576.64-.816.24-.24.528-.463.816-.639.304-.176.624-.304.976-.4A4.15 4.15 0 0 1 8 10.342a4.185 4.185 0 0 1 2.928 1.166c.368.368.656.8.864 1.295.112.288.192.592.24.911A7.03 7.03 0 0 1 8 14.993zm-2.448-7.4a2.49 2.49 0 0 1-.208-1.024c0-.351.064-.703.208-1.023.144-.32.336-.607.576-.847.24-.24.528-.431.848-.575.32-.144.672-.208 1.024-.208.368 0 .704.064 1.024.208.32.144.608.336.848.575.24.24.432.528.576.847.144.32.208.672.208 1.023 0 .368-.064.704-.208 1.023a2.84 2.84 0 0 1-.576.848 2.84 2.84 0 0 1-.848.575 2.715 2.715 0 0 1-2.064 0 2.84 2.84 0 0 1-.848-.575 2.526 2.526 0 0 1-.56-.848zm7.424 5.306c0-.032-.016-.048-.016-.08a5.22 5.22 0 0 0-.688-1.406 4.883 4.883 0 0 0-1.088-1.135 5.207 5.207 0 0 0-1.04-.608 2.82 2.82 0 0 0 .464-.383 4.2 4.2 0 0 0 .624-.784 3.624 3.624 0 0 0 .528-1.934 3.71 3.71 0 0 0-.288-1.47 3.799 3.799 0 0 0-.816-1.199 3.845 3.845 0 0 0-1.2-.8 3.72 3.72 0 0 0-1.472-.287 3.72 3.72 0 0 0-1.472.288 3.631 3.631 0 0 0-1.2.815 3.84 3.84 0 0 0-.8 1.199 3.71 3.71 0 0 0-.288 1.47c0 .352.048.688.144 1.007.096.336.224.64.4.927.16.288.384.544.624.784.144.144.304.271.48.383a5.12 5.12 0 0 0-1.04.624c-.416.32-.784.703-1.088 1.119a4.999 4.999 0 0 0-.688 1.406c-.016.032-.016.064-.016.08C1.776 11.636.992 9.91.992 7.992.992 4.14 4.144.991 8 .991s7.008 3.149 7.008 7.001a6.96 6.96 0 0 1-2.032 4.907z" fill="${foreground}"/>
276+
</svg>`;
277+
const contents = Buffer.from(svgContent);
278+
return asImageDataURI(contents);
279+
}
280+
271281
export async function avatarCirclesAsImageDataUris(context: vscode.ExtensionContext, users: (IAccount | ITeam)[], height: number, width: number, localOnly?: boolean): Promise<(vscode.Uri | undefined)[]> {
272282
let cacheLogOrder: string[];
273283
const cacheLog = cacheLogUri(context);
@@ -308,7 +318,14 @@ export namespace DataUri {
308318
await doFetch();
309319
} catch (e) {
310320
// We retry once.
311-
await doFetch();
321+
try {
322+
await doFetch();
323+
} catch (retryError) {
324+
// Log the error and return a generic user icon instead of crashing
325+
const userIdentifier = isTeam(user) ? `${user.org}/${user.slug}` : user.login || 'unknown';
326+
Logger.error(`Failed to fetch avatar after retry for user ${userIdentifier}: ${retryError}`, 'avatarCirclesAsImageDataUris');
327+
return genericUserIconAsImageDataURI(width, height);
328+
}
312329
}
313330
}
314331
if (!innerImageContents) {

0 commit comments

Comments
 (0)