diff --git a/apps/bootstrap-installer/src-tauri/src/bootstrap.rs b/apps/bootstrap-installer/src-tauri/src/bootstrap.rs index 783916cd74..8d6d81cb56 100644 --- a/apps/bootstrap-installer/src-tauri/src/bootstrap.rs +++ b/apps/bootstrap-installer/src-tauri/src/bootstrap.rs @@ -583,7 +583,7 @@ async fn run_bootstrap( let err = format!( "install.ps1 -Manifest failed: exit {:?}\n{}", manifest_result.exit_code, - manifest_result.stderr.trim() + crate::events::strip_ansi(manifest_result.stderr.trim()) ); emit_event( &app, diff --git a/apps/desktop/electron/bootstrap-runner.test.ts b/apps/desktop/electron/bootstrap-runner.test.ts index 19fb679fd8..5cc6f92f23 100644 --- a/apps/desktop/electron/bootstrap-runner.test.ts +++ b/apps/desktop/electron/bootstrap-runner.test.ts @@ -289,3 +289,27 @@ test('installer log lines reach the emitter without escape sequences; \\r redraw // Plain multi-byte text is untouched. assert.equal(cleanInstallerLogLine('Ready — café ✓ 中文'), 'Ready — café ✓ 中文') }) + +test.skipIf(process.platform === 'win32')( + 'a manifest-step failure surfaces the installer tail without escape sequences', + async () => { + const home = mkTmpHome() + fs.mkdirSync(path.join(home, 'scripts')) + fs.writeFileSync( + path.join(home, 'scripts', 'install.sh'), + '#!/usr/bin/env bash\nprintf "\\033[0;31m\\xe2\\x9c\\x97\\033[0m manifest broke\\n" >&2\nexit 3\n' + ) + + const result = await runBootstrap({ + installStamp: null, + activeRoot: home, + sourceRepoRoot: home, + hermesHome: home, + logRoot: home, + onEvent: () => {} + }) + + assert.equal(result.ok, false) + assert.equal(result.error, 'install.sh --manifest failed: exit 3\n✗ manifest broke') + } +) diff --git a/apps/desktop/electron/bootstrap-runner.ts b/apps/desktop/electron/bootstrap-runner.ts index dce1713a5f..90b3f55731 100644 --- a/apps/desktop/electron/bootstrap-runner.ts +++ b/apps/desktop/electron/bootstrap-runner.ts @@ -737,9 +737,11 @@ async function fetchManifest({ }) if (result.code !== 0) { - throw new Error( - `${isPosix ? 'install.sh --manifest' : 'install.ps1 -Manifest'} failed: exit ${result.code}\n${result.stderr || result.stdout}` - ) + // The tail lands in the Setup failure banner, not the log ring, so strip + // the installer's colour/OSC bytes here too (#112675). + const tail = stripAnsi(result.stderr || result.stdout).trim() + + throw new Error(`${isPosix ? 'install.sh --manifest' : 'install.ps1 -Manifest'} failed: exit ${result.code}\n${tail}`) } // The manifest is the LAST JSON line on stdout (install.ps1 may print