@@ -13,6 +13,40 @@ const tar = require('tar')
1313
1414const packageName = 'codecane'
1515
16+ /**
17+ * Terminal escape sequences to reset terminal state after the child process exits.
18+ * When the binary is SIGKILL'd, it can't clean up its own terminal state.
19+ * The wrapper (this process) survives and must reset these modes.
20+ *
21+ * Keep in sync with TERMINAL_RESET_SEQUENCES in cli/src/utils/renderer-cleanup.ts
22+ */
23+ const TERMINAL_RESET_SEQUENCES =
24+ '\x1b[?1049l' + // Exit alternate screen buffer
25+ '\x1b[?1000l' + // Disable X10 mouse mode
26+ '\x1b[?1002l' + // Disable button event mouse mode
27+ '\x1b[?1003l' + // Disable any-event mouse mode (all motion)
28+ '\x1b[?1006l' + // Disable SGR extended mouse mode
29+ '\x1b[?1004l' + // Disable focus reporting
30+ '\x1b[?2004l' + // Disable bracketed paste mode
31+ '\x1b[?25h' // Show cursor
32+
33+ function resetTerminal ( ) {
34+ try {
35+ if ( process . stdin . isTTY && process . stdin . setRawMode ) {
36+ process . stdin . setRawMode ( false )
37+ }
38+ } catch {
39+ // stdin may be closed
40+ }
41+ try {
42+ if ( process . stdout . isTTY ) {
43+ process . stdout . write ( TERMINAL_RESET_SEQUENCES )
44+ }
45+ } catch {
46+ // stdout may be closed
47+ }
48+ }
49+
1650function createConfig ( packageName ) {
1751 const homeDir = os . homedir ( )
1852 const configDir = path . join ( homeDir , '.config' , 'manicode' )
@@ -527,18 +561,24 @@ async function checkForUpdates(runningProcess, exitListener) {
527561 term . clearLine ( )
528562
529563 runningProcess . removeListener ( 'exit' , exitListener )
530- runningProcess . kill ( 'SIGTERM' )
531564
532565 await new Promise ( ( resolve ) => {
533- runningProcess . on ( 'exit' , resolve )
566+ let exited = false
567+ runningProcess . once ( 'exit' , ( ) => {
568+ exited = true
569+ resolve ( )
570+ } )
571+ runningProcess . kill ( 'SIGTERM' )
534572 setTimeout ( ( ) => {
535- if ( ! runningProcess . killed ) {
573+ if ( ! exited ) {
536574 runningProcess . kill ( 'SIGKILL' )
575+ // Safety: resolve after giving SIGKILL time to take effect
576+ setTimeout ( ( ) => resolve ( ) , 1000 )
537577 }
538- resolve ( )
539578 } , 5000 )
540579 } )
541580
581+ resetTerminal ( )
542582 console . log ( `Update available: ${ currentVersion } → ${ latestVersion } ` )
543583
544584 await downloadBinary ( latestVersion )
@@ -548,8 +588,15 @@ async function checkForUpdates(runningProcess, exitListener) {
548588 detached : false ,
549589 } )
550590
551- newChild . on ( 'exit' , ( code ) => {
552- process . exit ( code || 0 )
591+ newChild . on ( 'exit' , ( code , signal ) => {
592+ resetTerminal ( )
593+ printCrashDiagnostics ( code , signal )
594+ process . exit ( signal ? 1 : ( code || 0 ) )
595+ } )
596+
597+ newChild . on ( 'error' , ( err ) => {
598+ console . error ( 'Failed to start codecane:' , err . message )
599+ process . exit ( 1 )
553600 } )
554601
555602 return new Promise ( ( ) => { } )
@@ -559,6 +606,54 @@ async function checkForUpdates(runningProcess, exitListener) {
559606 }
560607}
561608
609+ function printCrashDiagnostics ( code , signal ) {
610+ // Windows NTSTATUS codes (unsigned DWORD)
611+ const unsignedCode = code != null && code < 0 ? ( code >>> 0 ) : code
612+ const isIllegalInstruction =
613+ signal === 'SIGILL' ||
614+ ( process . platform === 'win32' && unsignedCode === 0xC000001D )
615+ const isAccessViolation =
616+ signal === 'SIGSEGV' ||
617+ ( process . platform === 'win32' && unsignedCode === 0xC0000005 )
618+ const isBusError = signal === 'SIGBUS'
619+ const isAbort =
620+ signal === 'SIGABRT' ||
621+ ( process . platform === 'win32' && unsignedCode === 0xC0000409 )
622+
623+ if ( ! isIllegalInstruction && ! isAccessViolation && ! isBusError && ! isAbort ) return
624+
625+ const exitInfo = signal ? `signal ${ signal } ` : `code ${ code } `
626+ console . error ( '' )
627+ console . error ( `❌ ${ packageName } exited immediately (${ exitInfo } )` )
628+ console . error ( '' )
629+
630+ if ( isIllegalInstruction ) {
631+ console . error ( 'Your CPU may not support the required instruction set (AVX2).' )
632+ console . error ( 'This typically affects CPUs from before 2013.' )
633+ console . error ( 'Unfortunately, this binary is not compatible with your system.' )
634+ console . error ( '' )
635+ } else if ( isAccessViolation ) {
636+ console . error ( 'The binary crashed with an access violation.' )
637+ console . error ( '' )
638+ } else if ( isBusError ) {
639+ console . error ( 'The binary crashed with a bus error.' )
640+ console . error ( 'This may indicate a platform compatibility issue.' )
641+ console . error ( '' )
642+ } else if ( isAbort ) {
643+ console . error ( 'The binary crashed with an abort signal.' )
644+ console . error ( '' )
645+ }
646+
647+ console . error ( 'System info:' )
648+ console . error ( ` Platform: ${ process . platform } ${ process . arch } ` )
649+ console . error ( ` Node: ${ process . version } ` )
650+ console . error ( ` Binary: ${ CONFIG . binaryPath } ` )
651+ console . error ( '' )
652+ console . error ( 'Please report this issue at:' )
653+ console . error ( ' https://github.com/CodebuffAI/codebuff/issues' )
654+ console . error ( '' )
655+ }
656+
562657async function main ( ) {
563658 console . log ( '\x1b[1m\x1b[91m' + '=' . repeat ( 60 ) + '\x1b[0m' )
564659 console . log ( '\x1b[1m\x1b[93m❄️ CODECANE STAGING ENVIRONMENT ❄️\x1b[0m' )
@@ -574,12 +669,19 @@ async function main() {
574669 stdio : 'inherit' ,
575670 } )
576671
577- const exitListener = ( code ) => {
578- process . exit ( code || 0 )
672+ const exitListener = ( code , signal ) => {
673+ resetTerminal ( )
674+ printCrashDiagnostics ( code , signal )
675+ process . exit ( signal ? 1 : ( code || 0 ) )
579676 }
580677
581678 child . on ( 'exit' , exitListener )
582679
680+ child . on ( 'error' , ( err ) => {
681+ console . error ( 'Failed to start codecane:' , err . message )
682+ process . exit ( 1 )
683+ } )
684+
583685 setTimeout ( ( ) => {
584686 checkForUpdates ( child , exitListener )
585687 } , 100 )
0 commit comments