fix(ipc): keep the class-prefix strip case-sensitive

`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.
This commit is contained in:
Brennan Benson
2026-08-29 20:12:30 -07:00
committed by Merge Sim
parent ac484307c9
commit bc53c7ab1a
3 changed files with 62 additions and 2 deletions
@@ -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', () => {
+35 -1
View File
@@ -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'
)
})
})
+6 -1
View File
@@ -33,9 +33,14 @@ export function stripIpcInvokeEnvelope(message: string): string | null {
* `Error.prototype.toString()` renders "Error: <message>", 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]*/, '')
}
/**