@@ -28,6 +28,16 @@ function sendCheckpointInitWarn(task: Task, type?: "WAIT_TIMEOUT" | "INIT_TIMEOU
2828 } )
2929}
3030
31+ // Every checkpoint disable in this module is failure-driven (init, save,
32+ // restore, or diff). Route the flip through this helper so checkpointSave
33+ // can tell an earlier failure apart from a user who never enabled
34+ // checkpoints: the persisted setting cannot make that distinction (it can
35+ // change after the task started).
36+ function disableCheckpointsAfterFailure ( task : Task ) : void {
37+ task . enableCheckpoints = false
38+ task . checkpointsDisabledByFailure = true
39+ }
40+
3141export async function getCheckpointService ( task : Task , { interval = 250 } : { interval ?: number } = { } ) {
3242 if ( ! task . enableCheckpoints ) {
3343 return undefined
@@ -59,15 +69,15 @@ export async function getCheckpointService(task: Task, { interval = 250 }: { int
5969
6070 if ( ! workspaceDir ) {
6171 log ( "[Task#getCheckpointService] workspace folder not found, disabling checkpoints" )
62- task . enableCheckpoints = false
72+ disableCheckpointsAfterFailure ( task )
6373 return undefined
6474 }
6575
6676 const globalStorageDir = provider ?. context . globalStorageUri . fsPath
6777
6878 if ( ! globalStorageDir ) {
6979 log ( "[Task#getCheckpointService] globalStorageDir not found, disabling checkpoints" )
70- task . enableCheckpoints = false
80+ disableCheckpointsAfterFailure ( task )
7181 return undefined
7282 }
7383
@@ -101,7 +111,7 @@ export async function getCheckpointService(task: Task, { interval = 250 }: { int
101111 )
102112 if ( ! task ?. checkpointService ) {
103113 sendCheckpointInitWarn ( task , "INIT_TIMEOUT" , task . checkpointTimeout )
104- task . enableCheckpoints = false
114+ disableCheckpointsAfterFailure ( task )
105115 return undefined
106116 } else {
107117 sendCheckpointInitWarn ( task )
@@ -126,7 +136,7 @@ export async function getCheckpointService(task: Task, { interval = 250 }: { int
126136 sendCheckpointInitWarn ( task , "INIT_TIMEOUT" , task . checkpointTimeout )
127137 }
128138 log ( `[Task#getCheckpointService] ${ err . message } ` )
129- task . enableCheckpoints = false
139+ disableCheckpointsAfterFailure ( task )
130140 task . checkpointServiceInitializing = false
131141 return undefined
132142 }
@@ -143,7 +153,7 @@ async function checkGitInstallation(
143153
144154 if ( ! gitInstalled ) {
145155 log ( "[Task#getCheckpointService] Git is not installed, disabling checkpoints" )
146- task . enableCheckpoints = false
156+ disableCheckpointsAfterFailure ( task )
147157 task . checkpointServiceInitializing = false
148158
149159 // Show user-friendly notification
@@ -192,7 +202,7 @@ async function checkGitInstallation(
192202 } catch ( err ) {
193203 log ( "[Task#getCheckpointService] caught unexpected error in on('checkpoint'), disabling checkpoints" )
194204 console . error ( err )
195- task . enableCheckpoints = false
205+ disableCheckpointsAfterFailure ( task )
196206 }
197207 } )
198208
@@ -202,12 +212,12 @@ async function checkGitInstallation(
202212 await service . initShadowGit ( )
203213 } catch ( err ) {
204214 log ( `[Task#getCheckpointService] initShadowGit -> ${ err . message } ` )
205- task . enableCheckpoints = false
215+ disableCheckpointsAfterFailure ( task )
206216 }
207217 } catch ( err ) {
208218 log ( `[Task#getCheckpointService] Unexpected error during Git check: ${ err . message } ` )
209219 console . error ( "Git check error:" , err )
210- task . enableCheckpoints = false
220+ disableCheckpointsAfterFailure ( task )
211221 task . checkpointServiceInitializing = false
212222 }
213223}
@@ -276,27 +286,31 @@ export async function checkpointSave(
276286 // the user expects checkpoints" (throw).
277287 const checkpointsRequested = task . enableCheckpoints
278288
279- const service = await getCheckpointService ( task )
289+ let service = await getCheckpointService ( task )
280290
281291 if ( ! service ) {
282- let requested = checkpointsRequested
283- if ( hasWriteMetadata && ! requested ) {
284- // The task flag also flips off when an EARLIER failure disabled
285- // checkpoints (a failed service init, or the save-failure handlers
286- // below). The persisted setting still carries the user's intent
287- // (unset defaults to enabled, matching Task construction), so a
288- // write-metadata save after such a failure must throw too instead
289- // of silently proceeding with no rollback metadata. Only a user who
290- // disabled checkpoints in settings resolves silently.
291- const state = await task . providerRef . deref ( ) ?. getState ( )
292- requested = state ?. enableCheckpoints ?? true
292+ if ( hasWriteMetadata && ! checkpointsRequested && task . checkpointsDisabledByFailure ) {
293+ // An earlier failure (init or save) disabled checkpoints while the
294+ // user never turned them off. Attempt one recovery so a single
295+ // transient failure (e.g. index.lock contention) does not
296+ // permanently fail every later write in the task: reuse a live
297+ // service handle when one exists (a save failure does not
298+ // invalidate the handle), otherwise retry initialization once. The
299+ // persisted setting cannot make this decision - it can change
300+ // after the task started, and enabling checkpoints mid-task must
301+ // not retroactively fail writes that were never promised a
302+ // checkpoint.
303+ task . enableCheckpoints = true
304+ service = task . checkpointService ?? ( await getCheckpointService ( task ) )
293305 }
294- if ( hasWriteMetadata && requested ) {
295- throw new Error (
296- "Checkpoint save with write metadata could not initialize the checkpoint service; the write has no rollback metadata." ,
297- )
306+ if ( ! service ) {
307+ if ( hasWriteMetadata && ( checkpointsRequested || task . checkpointsDisabledByFailure ) ) {
308+ throw new Error (
309+ "Checkpoint save with write metadata could not initialize the checkpoint service; the write has no rollback metadata." ,
310+ )
311+ }
312+ return
298313 }
299- return
300314 }
301315
302316 // A save that carries write metadata needs a stable journal location for
@@ -334,7 +348,7 @@ export async function checkpointSave(
334348 result = await savePromise
335349 } catch ( err ) {
336350 console . error ( "[Task#checkpointSave] caught unexpected error, disabling checkpoints" , err )
337- task . enableCheckpoints = false
351+ disableCheckpointsAfterFailure ( task )
338352 throw new Error (
339353 `Checkpoint save failed after a file write; the write has no rollback metadata: ${ err instanceof Error ? err . message : String ( err ) } ` ,
340354 { cause : err } ,
@@ -343,7 +357,7 @@ export async function checkpointSave(
343357 } else {
344358 result = await savePromise . catch ( ( err ) => {
345359 console . error ( "[Task#checkpointSave] caught unexpected error, disabling checkpoints" , err )
346- task . enableCheckpoints = false
360+ disableCheckpointsAfterFailure ( task )
347361 } )
348362 }
349363
@@ -518,7 +532,7 @@ export async function checkpointRestore(
518532 provider ?. cancelTask ( )
519533 } catch {
520534 provider ?. log ( "[checkpointRestore] disabling checkpoints for this task" )
521- task . enableCheckpoints = false
535+ disableCheckpointsAfterFailure ( task )
522536 }
523537}
524538
@@ -608,6 +622,6 @@ export async function checkpointDiff(task: Task, { commitHash, mode }: Checkpoin
608622 } catch {
609623 const provider = task . providerRef . deref ( )
610624 provider ?. log ( "[checkpointDiff] disabling checkpoints for this task" )
611- task . enableCheckpoints = false
625+ disableCheckpointsAfterFailure ( task )
612626 }
613627}
0 commit comments