fix: pr-bug-scan validated finding from #4213 (#4311)

* fix: address pr-bug-scan validated finding from #4213

On per-file read/size error, skip the file and continue merging instead of returning found:false; preserve first error for the all-failed case.

* add tests

---------

Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
This commit is contained in:
buf0-bot[bot] 2026-08-09 15:25:29 -07:00 committed by GitHub
parent e20845eedb
commit eacba6654f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 98 additions and 12 deletions

View File

@ -114,6 +114,84 @@ background = #1a1a1a
expect(result.unsupportedKeys).toEqual([])
})
it('keeps successfully imported settings when a later config cannot be read', async () => {
delete process.env.XDG_CONFIG_HOME
const firstPath = '/Users/alice/.config/ghostty/config.ghostty'
const unreadablePath = '/Users/alice/.config/ghostty/config'
statMock.mockImplementation(async (p: string) => {
if (p === firstPath || p === unreadablePath) {
return { isFile: () => true, size: 128 }
}
throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' })
})
readFileMock.mockImplementation(async (p: string) => {
if (p === firstPath) {
return 'font-size = 22\n'
}
throw new Error('EACCES')
})
const result = await previewGhosttyImport(createStore())
expect(result).toMatchObject({
found: true,
configPath: firstPath,
configPaths: [firstPath],
diff: { terminalFontSize: 22 }
})
})
it('keeps successfully imported settings when a later config is too large', async () => {
delete process.env.XDG_CONFIG_HOME
const firstPath = '/Users/alice/.config/ghostty/config.ghostty'
const oversizedPath = '/Users/alice/.config/ghostty/config'
statMock.mockImplementation(async (p: string) => {
if (p === firstPath) {
return { isFile: () => true, size: 128 }
}
if (p === oversizedPath) {
return { isFile: () => true, size: 1_000_001 }
}
throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' })
})
readFileMock.mockResolvedValue('font-size = 22\n')
const result = await previewGhosttyImport(createStore())
expect(result).toMatchObject({
found: true,
configPath: firstPath,
configPaths: [firstPath],
diff: { terminalFontSize: 22 }
})
expect(readFileMock).not.toHaveBeenCalledWith(oversizedPath, 'utf-8')
})
it('returns the first error when every discovered config fails to load', async () => {
delete process.env.XDG_CONFIG_HOME
const configPath = '/Users/alice/.config/ghostty/config.ghostty'
let configPathStatCalls = 0
statMock.mockImplementation(async (p: string) => {
if (p === configPath) {
configPathStatCalls += 1
if (configPathStatCalls === 1) {
return { isFile: () => true, size: 128 }
}
throw new Error('EACCES')
}
throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' })
})
const result = await previewGhosttyImport(createStore())
expect(result).toEqual({
found: false,
diff: {},
unsupportedKeys: [],
error: 'Could not read config: EACCES'
})
})
it('omits values that match current settings', async () => {
statMock.mockImplementation(async (p: string) => {
if (p === '/Users/alice/Library/Application Support/com.mitchellh.ghostty/config') {

View File

@ -107,29 +107,37 @@ export async function previewGhosttyImport(store: Store): Promise<GhosttyImportP
}
const parsed: Record<string, string | string[]> = {}
const readPaths: string[] = []
let firstError: string | null = null
for (const configPath of configPaths) {
let content: string
try {
const info = await stat(configPath)
if (info.size > MAX_CONFIG_BYTES) {
return {
found: false,
diff: {},
unsupportedKeys: [],
error: `Config file is too large to import (${info.size} bytes, limit ${MAX_CONFIG_BYTES}).`
if (firstError === null) {
firstError = `Config file is too large to import (${info.size} bytes, limit ${MAX_CONFIG_BYTES}).`
}
continue
}
content = await readFile(configPath, 'utf-8')
} catch (err) {
const message = err instanceof Error ? err.message : 'Could not read config file'
return {
found: false,
diff: {},
unsupportedKeys: [],
error: `Could not read config: ${message}`
if (firstError === null) {
firstError = `Could not read config: ${message}`
}
continue
}
mergeParsedConfig(parsed, parseGhosttyConfig(content))
readPaths.push(configPath)
}
if (readPaths.length === 0) {
return {
found: false,
diff: {},
unsupportedKeys: [],
error: firstError ?? 'Could not read config'
}
}
const themeUnsupportedKeys = await applyThemeReference(parsed)
@ -153,8 +161,8 @@ export async function previewGhosttyImport(store: Store): Promise<GhosttyImportP
return {
found: true,
configPath: configPaths[0],
configPaths,
configPath: readPaths[0],
configPaths: readPaths,
diff: actualDiff,
unsupportedKeys
}