From bc53c7ab1a1a3301de9ec6aac655fe737e8e5cd4 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Sat, 29 Aug 2026 11:09:20 -0700 Subject: [PATCH] fix(ipc): keep the class-prefix strip case-sensitive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `stripErrorClassPrefix` took a case-insensitive flag from the two AccountsPane copies it replaced, which widened `LinuxPackageInstallRecoveryCard` — that site matched `/^Error:\s*/` on main. The only thing this trims is `Error.prototype.toString()` output, which V8 always writes as `Error: `, so the flag could never match a real class prefix the case-sensitive pattern misses; it could only eat the lowercase severity marker git, rpm, dpkg and pip put in front of a real reason. `IPC_ENVELOPE`'s own `\w*Error:` group in the same module is already case-sensitive. All three call sites now agree, and the card matches main again. --- .../LinuxPackageInstallRecoveryCard.test.tsx | 21 +++++++++++ src/shared/ipc-invoke-envelope.test.ts | 36 ++++++++++++++++++- src/shared/ipc-invoke-envelope.ts | 7 +++- 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/components/LinuxPackageInstallRecoveryCard.test.tsx b/src/renderer/src/components/LinuxPackageInstallRecoveryCard.test.tsx index 19ae96fdff1..fe8dd02d328 100644 --- a/src/renderer/src/components/LinuxPackageInstallRecoveryCard.test.tsx +++ b/src/renderer/src/components/LinuxPackageInstallRecoveryCard.test.tsx @@ -613,6 +613,27 @@ describe('LinuxPackageInstallRecoveryCard reveal', () => { // Why: a reveal failure is not a command-build failure, so the copy path must survive it. expect(button('Copy Install Command')).toBeTruthy() }) + + /** + * Why: package tooling writes its severity marker in lowercase — `error: Failed dependencies:` + * is rpm's own wording, not a stringified Error. Trimming it would leave the line reading as if + * the install had merely reported dependencies. This is the sensitivity this card had before the + * canonical stripper took the job over. + */ + it('keeps a lowercase severity marker in the reason it shows', async () => { + showLinuxPackage.mockRejectedValue( + new Error( + "Error invoking remote method 'updater:showLinuxPackage': " + + 'error: Failed dependencies: libc.so.6 is needed by orca-1.4.200' + ) + ) + renderCard() + + fireEvent.click(button('Show Package')) + await flushActions() + + expect(footnoteText()).toBe('error: Failed dependencies: libc.so.6 is needed by orca-1.4.200') + }) }) describe('LinuxPackageInstallRecoveryCard without a usable command', () => { diff --git a/src/shared/ipc-invoke-envelope.test.ts b/src/shared/ipc-invoke-envelope.test.ts index 5c2ab169463..a182ea04c2f 100644 --- a/src/shared/ipc-invoke-envelope.test.ts +++ b/src/shared/ipc-invoke-envelope.test.ts @@ -1,5 +1,9 @@ import { describe, expect, it } from 'vitest' -import { stripIpcInvokeEnvelope, stripIpcInvokeEnvelopeFrom } from './ipc-invoke-envelope' +import { + stripErrorClassPrefix, + stripIpcInvokeEnvelope, + stripIpcInvokeEnvelopeFrom +} from './ipc-invoke-envelope' describe('stripIpcInvokeEnvelope', () => { it('returns the reason behind the invoke envelope', () => { @@ -80,3 +84,33 @@ describe('stripIpcInvokeEnvelopeFrom', () => { expect(stripIpcInvokeEnvelopeFrom(null)).toBeNull() }) }) + +describe('stripErrorClassPrefix', () => { + it('removes the class prefix Error.prototype.toString() writes', () => { + expect(stripErrorClassPrefix('Error: updater is not initialized')).toBe( + 'updater is not initialized' + ) + }) + + /** + * Why case-sensitive: the only thing this trims is `Error.prototype.toString()` output, and V8 + * writes the constructor name, so the prefix is always exactly `Error: `. A lowercase `error: ` + * is never that — it is the severity marker git, rpm, dpkg and pip put in front of a real + * reason, and eating it drops the word the user is reading the line for. + */ + it('keeps a lowercase severity marker, which is a reason and not a class name', () => { + expect( + stripErrorClassPrefix("error: pathspec 'v2' did not match any file(s) known to git") + ).toBe("error: pathspec 'v2' did not match any file(s) known to git") + expect(stripErrorClassPrefix('ERROR: Could not install packages due to an OSError')).toBe( + 'ERROR: Could not install packages due to an OSError' + ) + }) + + it('leaves a message that carries no class prefix alone', () => { + expect(stripErrorClassPrefix('Access denied')).toBe('Access denied') + expect(stripErrorClassPrefix('Error 500: upstream unavailable')).toBe( + 'Error 500: upstream unavailable' + ) + }) +}) diff --git a/src/shared/ipc-invoke-envelope.ts b/src/shared/ipc-invoke-envelope.ts index bfa00c4bbb3..25577426f1b 100644 --- a/src/shared/ipc-invoke-envelope.ts +++ b/src/shared/ipc-invoke-envelope.ts @@ -33,9 +33,14 @@ export function stripIpcInvokeEnvelope(message: string): string | null { * `Error.prototype.toString()` renders "Error: ", so a rejection that was stringified * rather than read through `.message` arrives with a class prefix that is not part of the reason. * Separate from the envelope: a message can carry this prefix without ever crossing IPC. + * + * Case-sensitive, like the class name inside `IPC_ENVELOPE` above: V8 writes the constructor + * name, so the prefix is always exactly `Error: `. A lowercase `error: ` is git's, rpm's and + * pip's severity marker in front of a real reason, and trimming it would drop the word the line + * is being read for. */ export function stripErrorClassPrefix(text: string): string { - return text.replace(/^Error:[ \t]*/i, '') + return text.replace(/^Error:[ \t]*/, '') } /**