diff --git a/src/renderer/src/components/right-sidebar/AiVaultScanIssueBanners.test.tsx b/src/renderer/src/components/right-sidebar/AiVaultScanIssueBanners.test.tsx
new file mode 100644
index 00000000000..df15ba89029
--- /dev/null
+++ b/src/renderer/src/components/right-sidebar/AiVaultScanIssueBanners.test.tsx
@@ -0,0 +1,42 @@
+// @vitest-environment happy-dom
+import { cleanup, render, screen } from '@testing-library/react'
+import { afterEach, expect, it } from 'vitest'
+import type { AiVaultListResult, AiVaultSession } from '../../../../shared/ai-vault-types'
+import { AiVaultScanIssueBanners } from './AiVaultScanIssueBanners'
+
+afterEach(() => {
+ cleanup()
+})
+
+it('keeps many partial-read notices in a bounded scroll area above the session list', () => {
+ const notices = Array.from({ length: 40 }, (_, index) => ({
+ executionHostId: 'ssh:box' as const,
+ agent: 'codex' as const,
+ kind: 'notice' as const,
+ path: `/home/u/.codex/sessions/rollout-${index}.jsonl`,
+ message: `Skipped 1 oversized transcript record over the 10.0 MiB limit (12.0 MiB at byte ${index}). The rest of the session was read.`
+ }))
+ const scanResult: AiVaultListResult = {
+ // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: banners only read the session count.
+ sessions: [{} as AiVaultSession],
+ issues: [...notices, { agent: 'codex', path: '/broken.jsonl', message: 'Unreadable' }],
+ scannedAt: '2026-10-15T00:00:00.000Z'
+ }
+
+ render()
+
+ const region = screen.getByTestId('ai-vault-scan-notices')
+ expect(region.className).toContain('max-h-24')
+ expect(region.className).toContain('overflow-y-auto')
+ expect(region.children).toHaveLength(42)
+ expect(region.textContent).toContain('1 transcript skipped')
+})
+
+it('renders no notice area for a clean scan', () => {
+ render(
+
+ )
+ expect(screen.queryByTestId('ai-vault-scan-notices')).toBeNull()
+})
diff --git a/src/renderer/src/components/right-sidebar/AiVaultScanIssueBanners.tsx b/src/renderer/src/components/right-sidebar/AiVaultScanIssueBanners.tsx
index 46171da9cb8..93c76e0437b 100644
--- a/src/renderer/src/components/right-sidebar/AiVaultScanIssueBanners.tsx
+++ b/src/renderer/src/components/right-sidebar/AiVaultScanIssueBanners.tsx
@@ -18,6 +18,10 @@ export function AiVaultScanIssueBanners({
const blocking = blockingAiVaultScanIssue(scanResult)
const skippedTranscriptCount = skippedAiVaultTranscriptCount(scanResult)
+ const notices = aiVaultScanNoticeIssues(scanResult)
+ const reasons = skippedAiVaultTranscriptReasons(scanResult)
+ const hasNoticeRows = notices.length > 0 || skippedTranscriptCount > 0 || reasons.length > 0
+
return (
<>
{blocking ? (
@@ -25,35 +29,43 @@ export function AiVaultScanIssueBanners({
{blocking.message}
) : null}
- {aiVaultScanNoticeIssues(scanResult).map((issue) => (
+ {hasNoticeRows ? (
+ // Why bounded: one notice per partly-read session can outgrow the panel and hide the list.
- {issue.message}
-
- ))}
- {skippedTranscriptCount > 0 ? (
-
- {translate(
- 'auto.components.right.sidebar.AiVaultPanel.transcriptsSkipped',
- '{{count}} transcript skipped',
- { count: skippedTranscriptCount }
- )}
+ {notices.map((issue) => (
+
+ {issue.message}
+
+ ))}
+ {skippedTranscriptCount > 0 ? (
+
+ {translate(
+ 'auto.components.right.sidebar.AiVaultPanel.transcriptsSkipped',
+ '{{count}} transcript skipped',
+ { count: skippedTranscriptCount }
+ )}
+
+ ) : null}
+ {reasons.map((reason) => (
+
+ {reason}
+
+ ))}
) : null}
- {skippedAiVaultTranscriptReasons(scanResult).map((reason) => (
-
- {reason}
-
- ))}
>
)
}