@@ -329,11 +329,7 @@ export class RemoteConnectionService {
329329 this . clearAutomaticReconnectBudget ( targetId ) ;
330330 return projects ;
331331 } catch ( error ) {
332- this . mergeStatus ( targetId , {
333- state : "error" ,
334- lastError : this . recordImplicitFailure ( targetId , error ) ,
335- lastAttemptedAt : Date . now ( ) ,
336- } ) ;
332+ this . markCallFailure ( targetId , error ) ;
337333 throw error ;
338334 }
339335 }
@@ -350,11 +346,7 @@ export class RemoteConnectionService {
350346 this . clearAutomaticReconnectBudget ( targetId ) ;
351347 return project ;
352348 } catch ( error ) {
353- this . mergeStatus ( targetId , {
354- state : "error" ,
355- lastError : this . recordImplicitFailure ( targetId , error ) ,
356- lastAttemptedAt : Date . now ( ) ,
357- } ) ;
349+ this . markCallFailure ( targetId , error ) ;
358350 throw error ;
359351 }
360352 }
@@ -368,11 +360,7 @@ export class RemoteConnectionService {
368360 try {
369361 return await this . pool . ensureLocalPortForward ( target . id , request ) ;
370362 } catch ( error ) {
371- this . mergeStatus ( targetId , {
372- state : "error" ,
373- lastError : this . recordImplicitFailure ( targetId , error ) ,
374- lastAttemptedAt : Date . now ( ) ,
375- } ) ;
363+ this . markCallFailure ( targetId , error ) ;
376364 throw error ;
377365 }
378366 }
@@ -482,11 +470,7 @@ export class RemoteConnectionService {
482470 this . clearAutomaticReconnectBudget ( targetId ) ;
483471 return result ;
484472 } catch ( error ) {
485- this . mergeStatus ( targetId , {
486- state : "error" ,
487- lastError : this . recordImplicitFailure ( targetId , error ) ,
488- lastAttemptedAt : Date . now ( ) ,
489- } ) ;
473+ this . markCallFailure ( targetId , error ) ;
490474 throw error ;
491475 }
492476 }
@@ -511,11 +495,7 @@ export class RemoteConnectionService {
511495 this . clearAutomaticReconnectBudget ( targetId ) ;
512496 return result ;
513497 } catch ( error ) {
514- this . mergeStatus ( targetId , {
515- state : "error" ,
516- lastError : this . recordImplicitFailure ( targetId , error ) ,
517- lastAttemptedAt : Date . now ( ) ,
518- } ) ;
498+ this . markCallFailure ( targetId , error ) ;
519499 throw error ;
520500 }
521501 }
@@ -544,11 +524,7 @@ export class RemoteConnectionService {
544524 this . clearAutomaticReconnectBudget ( targetId ) ;
545525 return cleanup ;
546526 } catch ( error ) {
547- this . mergeStatus ( targetId , {
548- state : "error" ,
549- lastError : this . recordImplicitFailure ( targetId , error ) ,
550- lastAttemptedAt : Date . now ( ) ,
551- } ) ;
527+ this . markCallFailure ( targetId , error ) ;
552528 throw error ;
553529 }
554530 }
@@ -571,11 +547,7 @@ export class RemoteConnectionService {
571547 this . clearAutomaticReconnectBudget ( targetId ) ;
572548 return registry ;
573549 } catch ( error ) {
574- this . mergeStatus ( targetId , {
575- state : "error" ,
576- lastError : this . recordImplicitFailure ( targetId , error ) ,
577- lastAttemptedAt : Date . now ( ) ,
578- } ) ;
550+ this . markCallFailure ( targetId , error ) ;
579551 throw error ;
580552 }
581553 }
@@ -602,11 +574,7 @@ export class RemoteConnectionService {
602574 this . clearAutomaticReconnectBudget ( targetId ) ;
603575 return result ;
604576 } catch ( error ) {
605- this . mergeStatus ( targetId , {
606- state : "error" ,
607- lastError : this . recordImplicitFailure ( targetId , error ) ,
608- lastAttemptedAt : Date . now ( ) ,
609- } ) ;
577+ this . markCallFailure ( targetId , error ) ;
610578 throw error ;
611579 }
612580 }
@@ -670,11 +638,7 @@ export class RemoteConnectionService {
670638 this . clearAutomaticReconnectBudget ( target . id ) ;
671639 return result ;
672640 } catch ( error ) {
673- this . mergeStatus ( target . id , {
674- state : "error" ,
675- lastError : this . recordImplicitFailure ( target . id , error ) ,
676- lastAttemptedAt : Date . now ( ) ,
677- } ) ;
641+ this . markCallFailure ( target . id , error ) ;
678642 throw error ;
679643 }
680644 }
@@ -711,6 +675,25 @@ export class RemoteConnectionService {
711675 this . automaticReconnectPausedTargetIds . delete ( targetId ) ;
712676 }
713677
678+ /**
679+ * Classify a failure from an RPC call made over an already-established
680+ * connection. If the response came back over a live channel, the host is
681+ * reachable and the error is application-level (e.g. a host-side SQL or
682+ * validation failure). Such errors must NOT flip the connection to
683+ * "error"/reconnecting — doing so surfaces a false "host is unreachable"
684+ * toast and a reconnect loop for what is really a per-action failure. Only
685+ * genuine transport failures update the connection status and reconnect
686+ * budget; everything else is left to rethrow to the caller untouched.
687+ */
688+ private markCallFailure ( targetId : string , error : unknown ) : void {
689+ if ( ! isImplicitConnectionFailure ( error ) ) return ;
690+ this . mergeStatus ( targetId , {
691+ state : "error" ,
692+ lastError : this . recordImplicitFailure ( targetId , error ) ,
693+ lastAttemptedAt : Date . now ( ) ,
694+ } ) ;
695+ }
696+
714697 private recordImplicitFailure ( targetId : string , error : unknown ) : string {
715698 if ( ! isImplicitConnectionFailure ( error ) ) return errorMessage ( error ) ;
716699 if ( isConnectionBackoffThrottle ( error ) ) return errorMessage ( error ) ;
0 commit comments