@@ -74,7 +74,9 @@ type Manager struct {
7474 isOurProcessFunc func (pid int , binaryPath string ) bool
7575 stopByPIDFunc func (pid int ) error
7676
77- pendingHelperVersion string
77+ pendingHelperVersion string
78+ updateFailures int
79+ abandonedVersion string // version we gave up updating to
7880}
7981
8082// New creates a new helper Manager.
@@ -304,8 +306,23 @@ var minConfigFlagVersion = [3]int{0, 14, 0}
304306
305307func (m * Manager ) ensureRunningSession (state * sessionState ) error {
306308 if state .pid > 0 && m .isOurProcessFunc (state .pid , m .binaryPath ) {
309+ state .resetCrashes ()
307310 return nil
308311 }
312+
313+ // If we recently spawned and the process is already gone, count it as a crash.
314+ if ! state .lastSpawnTime .IsZero () && state .pid > 0 {
315+ state .recordCrash ()
316+ if state .inCooldown () {
317+ log .Warn ("breeze assist keeps crashing, backing off" ,
318+ "session" , state .key ,
319+ "crashes" , state .spawnCrashes ,
320+ "cooldownUntil" , state .cooldownUntil .Format ("15:04:05" ),
321+ )
322+ return fmt .Errorf ("in cooldown after %d crashes" , state .spawnCrashes )
323+ }
324+ }
325+
309326 var pid int
310327 var err error
311328 if m .helperSupportsConfigFlag () {
@@ -317,6 +334,7 @@ func (m *Manager) ensureRunningSession(state *sessionState) error {
317334 return err
318335 }
319336 state .pid = pid
337+ state .recordSpawn ()
320338 return nil
321339}
322340
@@ -409,9 +427,14 @@ func (m *Manager) downloadAndInstall() error {
409427func (m * Manager ) CheckUpdate (targetVersion string ) {
410428 m .mu .Lock ()
411429 defer m .mu .Unlock ()
430+ if targetVersion == m .abandonedVersion {
431+ return // already failed for this version, don't retry
432+ }
412433 if m .pendingHelperVersion != targetVersion {
413434 log .Info ("helper update pending" , "targetVersion" , targetVersion )
414435 m .pendingHelperVersion = targetVersion
436+ m .updateFailures = 0
437+ m .abandonedVersion = ""
415438 }
416439}
417440
@@ -445,6 +468,19 @@ func (m *Manager) applyPendingUpdate() {
445468 if installed := m .installedVersionLocked (); installed == m .pendingHelperVersion {
446469 log .Info ("helper already at target version, clearing pending update" , "version" , installed )
447470 m .pendingHelperVersion = ""
471+ m .updateFailures = 0
472+ return
473+ }
474+
475+ const maxUpdateFailures = 3
476+ if m .updateFailures >= maxUpdateFailures {
477+ log .Warn ("helper update abandoned after repeated failures, clearing pending update" ,
478+ "targetVersion" , m .pendingHelperVersion ,
479+ "failures" , m .updateFailures ,
480+ )
481+ m .abandonedVersion = m .pendingHelperVersion
482+ m .pendingHelperVersion = ""
483+ m .updateFailures = 0
448484 return
449485 }
450486
@@ -472,7 +508,8 @@ func (m *Manager) applyPendingUpdate() {
472508 }
473509
474510 if err := m .downloadAndInstall (); err != nil {
475- log .Error ("failed to install helper update" , "error" , err .Error ())
511+ m .updateFailures ++
512+ log .Error ("failed to install helper update" , "error" , err .Error (), "failures" , m .updateFailures )
476513 if restoreErr := restoreBackup (backupPath , m .binaryPath ); restoreErr != nil {
477514 log .Error ("failed to rollback helper" , "error" , restoreErr .Error ())
478515 }
@@ -488,6 +525,7 @@ func (m *Manager) applyPendingUpdate() {
488525
489526 for _ , state := range stopped {
490527 state .pid = 0
528+ state .resetCrashes () // new binary — give it a fresh chance
491529 if err := m .ensureRunningSession (state ); err != nil {
492530 log .Error ("failed to start updated helper" , "session" , state .key , "error" , err .Error ())
493531 if restoreErr := restoreBackup (backupPath , m .binaryPath ); restoreErr != nil {
0 commit comments