@@ -48,6 +48,7 @@ import {
4848import { isLikelyContextOverflowError } from "./pi-embedded-helpers/errors.js" ;
4949import type { FailoverReason } from "./pi-embedded-helpers/types.js" ;
5050import { resolveSessionSuspensionReason , suspendSession } from "./session-suspension.js" ;
51+ import { isSessionWriteLockTimeoutError } from "./session-write-lock-error.js" ;
5152
5253const log = createSubsystemLogger ( "model-fallback" ) ;
5354
@@ -116,6 +117,23 @@ function shouldRethrowAbort(err: unknown): boolean {
116117 return isFallbackAbortError ( err ) && ! isTimeoutError ( err ) ;
117118}
118119
120+ function isEmbeddedAttemptSessionTakeoverError ( err : unknown ) : boolean {
121+ if ( ! err || typeof err !== "object" ) {
122+ return false ;
123+ }
124+ const name = "name" in err ? String ( err . name ) : "" ;
125+ if ( name === "EmbeddedAttemptSessionTakeoverError" ) {
126+ return true ;
127+ }
128+ return formatErrorMessage ( err ) . includes (
129+ "session file changed while embedded prompt lock was released" ,
130+ ) ;
131+ }
132+
133+ function shouldRethrowSessionOwnershipError ( err : unknown ) : boolean {
134+ return isEmbeddedAttemptSessionTakeoverError ( err ) || isSessionWriteLockTimeoutError ( err ) ;
135+ }
136+
119137function createModelCandidateCollector ( allowlist : Set < string > | null | undefined ) : {
120138 candidates : ModelCandidate [ ] ;
121139 addExplicitCandidate : ( candidate : ModelCandidate ) => void ;
@@ -231,6 +249,9 @@ async function runFallbackCandidate<T>(params: {
231249 if ( isCommandLaneTaskTimeoutError ( err ) ) {
232250 throw err ;
233251 }
252+ if ( shouldRethrowSessionOwnershipError ( err ) ) {
253+ throw err ;
254+ }
234255 // Normalize abort-wrapped rate-limit errors (e.g. Google Vertex RESOURCE_EXHAUSTED)
235256 // so they become FailoverErrors and continue the fallback loop instead of aborting.
236257 const normalizedFailover = coerceToFailoverError ( err , {
0 commit comments