diff --git a/src/renderer/src/components/sidebar/CommentMarkdown.github-attachment-image.test.tsx b/src/renderer/src/components/sidebar/CommentMarkdown.github-attachment-image.test.tsx
new file mode 100644
index 000000000..49bf77d7e
--- /dev/null
+++ b/src/renderer/src/components/sidebar/CommentMarkdown.github-attachment-image.test.tsx
@@ -0,0 +1,64 @@
+// @vitest-environment happy-dom
+
+import { act } from 'react'
+import { createRoot, type Root } from 'react-dom/client'
+import { afterEach, beforeEach, describe, expect, it } from 'vitest'
+import CommentMarkdown from './CommentMarkdown'
+
+const attachmentUrl =
+ 'https://github.com/user-attachments/assets/ce11040a-fb66-4289-927f-547b16dfc488'
+
+let root: Root | null = null
+let container: HTMLDivElement | null = null
+
+function renderCommentMarkdown(content: string): HTMLDivElement {
+ container = document.createElement('div')
+ document.body.appendChild(container)
+ root = createRoot(container)
+ act(() => {
+ root?.render()
+ })
+ return container
+}
+
+describe('CommentMarkdown GitHub attachment images', () => {
+ beforeEach(() => {
+ globalThis.IS_REACT_ACT_ENVIRONMENT = true
+ })
+
+ afterEach(() => {
+ if (root) {
+ act(() => root?.unmount())
+ }
+ document.body.replaceChildren()
+ root = null
+ container = null
+ })
+
+ it('renders GitHub user attachment document images as openable links', () => {
+ const mounted = renderCommentMarkdown(``)
+
+ const link = mounted.querySelector(`a[href="${attachmentUrl}"]`)
+ const image = link?.querySelector('img')
+
+ expect(link).not.toBeNull()
+ expect(link?.className).toContain('inline-block')
+ expect(image?.src).toBe(attachmentUrl)
+ expect(image?.alt).toBe('Private issue screenshot')
+ })
+
+ it('falls back to a text link when a GitHub user attachment image cannot load', () => {
+ const mounted = renderCommentMarkdown(``)
+ const image = mounted.querySelector(`img[src="${attachmentUrl}"]`)
+
+ expect(image).not.toBeNull()
+ act(() => {
+ image?.dispatchEvent(new window.Event('error'))
+ })
+
+ expect(mounted.querySelector(`img[src="${attachmentUrl}"]`)).toBeNull()
+ const fallback = mounted.querySelector(`a[href="${attachmentUrl}"]`)
+ expect(fallback?.textContent).toBe('Private issue screenshot')
+ expect(fallback?.className).toContain('underline')
+ })
+})
diff --git a/src/renderer/src/components/sidebar/comment-markdown-element-renderers.tsx b/src/renderer/src/components/sidebar/comment-markdown-element-renderers.tsx
index 5f2e0b972..7940d6278 100644
--- a/src/renderer/src/components/sidebar/comment-markdown-element-renderers.tsx
+++ b/src/renderer/src/components/sidebar/comment-markdown-element-renderers.tsx
@@ -2,9 +2,11 @@ import React from 'react'
import type { Components } from 'react-markdown'
import { isMermaidFence, isMermaidPre, renderMermaidFence } from './comment-mermaid-fence'
import {
+ GitHubUserAttachmentImage,
GitHubUserAttachmentVideo,
+ isGitHubUserAttachmentUrl,
isGitHubUserAttachmentVideoLink
-} from './comment-markdown-github-attachment-video'
+} from './comment-markdown-github-attachment-media'
export type CommentMarkdownLinkClickHandler = (
event: React.MouseEvent,
@@ -230,6 +232,12 @@ export function createDocumentCommentMarkdownComponents(
),
img: ({ alt, src }) => {
+ if (isGitHubUserAttachmentUrl(src)) {
+ // Why: private-repo attachment images fail as cross-origin loads; a
+ // top-level link opens them in a GitHub-authenticated tab, and falls
+ // back to a text link when the image itself can't render.
+ return
+ }
const imageClassName = [
'my-3 max-h-96 max-w-full rounded-md object-contain',
'outline outline-1 outline-black/10 dark:outline-white/10',
diff --git a/src/renderer/src/components/sidebar/comment-markdown-github-attachment-video.tsx b/src/renderer/src/components/sidebar/comment-markdown-github-attachment-media.tsx
similarity index 55%
rename from src/renderer/src/components/sidebar/comment-markdown-github-attachment-video.tsx
rename to src/renderer/src/components/sidebar/comment-markdown-github-attachment-media.tsx
index 448493389..7f94e907e 100644
--- a/src/renderer/src/components/sidebar/comment-markdown-github-attachment-video.tsx
+++ b/src/renderer/src/components/sidebar/comment-markdown-github-attachment-media.tsx
@@ -1,6 +1,6 @@
import React from 'react'
-function isGitHubUserAttachmentUrl(href: string | undefined): href is string {
+export function isGitHubUserAttachmentUrl(href: string | undefined): href is string {
if (!href) {
return false
}
@@ -67,3 +67,48 @@ export function GitHubUserAttachmentVideo({
)
}
+
+export function GitHubUserAttachmentImage({
+ src,
+ alt
+}: {
+ src: string
+ alt: string | undefined
+}): React.ReactElement {
+ const [failed, setFailed] = React.useState(false)
+ const label = alt?.trim() || src
+
+ // Why: private-repo attachment images can't load cross-origin without the
+ // user's GitHub session cookies, so wrap in a top-level link (opening the
+ // URL where that session exists) and drop to a text link on load error.
+ if (failed) {
+ return (
+ e.stopPropagation()}
+ >
+ {label}
+
+ )
+ }
+
+ return (
+ e.stopPropagation()}
+ >
+
setFailed(true)}
+ />
+
+ )
+}