{"record":{"id":"dfa66c3da21699f1","repo":"laurent22/joplin","slug":"timed-out-trying-to-get-sync-target-clock-time","errorCode":null,"errorMessage":"Timed out trying to get sync target clock time","messagePattern":"Timed out trying to get sync target clock time","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"packages/lib/file-api.ts","lineNumber":237,"sourceCode":"\t\treturn !!this.driver().supportsLocks;\n\t}\n\n\tprivate async fetchRemoteDateOffset_() {\n\t\tconst tempFile = `${this.tempDirName()}/timeCheck${Math.round(Math.random() * 1000000)}.txt`;\n\t\tconst startTime = Date.now();\n\t\tawait this.put(tempFile, 'timeCheck');\n\n\t\t// Normally it should be possible to read the file back immediately but\n\t\t// just in case, read it in a loop.\n\t\tconst loopStartTime = Date.now();\n\t\tlet stat = null;\n\t\twhile (Date.now() - loopStartTime < 5000) {\n\t\t\tstat = await this.stat(tempFile);\n\t\t\tif (stat) break;\n\t\t\tawait time.msleep(200);\n\t\t}\n\n\t\tif (!stat) throw new Error('Timed out trying to get sync target clock time');\n\n\t\tvoid this.delete(tempFile); // No need to await for this call\n\n\t\tconst endTime = Date.now();\n\t\tconst expectedTime = Math.round((endTime + startTime) / 2);\n\t\treturn stat.updated_time - expectedTime;\n\t}\n\n\t// Approximates the current time on the sync target. It caches the time offset to\n\t// improve performance.\n\tpublic async remoteDate() {\n\t\tconst shouldSyncTime = () => {\n\t\t\treturn !this.remoteDateNextCheckTime_ || Date.now() > this.remoteDateNextCheckTime_;\n\t\t};\n\n\t\tif (shouldSyncTime()) {\n\t\t\tconst release = await this.remoteDateMutex_.acquire();\n","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/lib/file-api.ts#L219-L255","documentation":"Thrown by fetchRemoteDateOffset_() in the file API. It writes a temp file, then polls stat() in a 200ms loop for up to 5 seconds waiting for the target to acknowledge it. If the file never becomes visible, the remote clock offset can't be computed and the driver gives up. The outer remoteDate() catches this and falls back to the device clock.","triggerScenarios":"During sync startup when the target has eventual consistency (OneDrive, Dropbox) and the just-written temp file isn't readable within 5s; or the target is slow/unreachable mid-operation.","commonSituations":"Slow cloud target with replication lag (OneDrive, Dropbox, Amazon S3); flaky network causing the put to partially succeed; target rate-limiting reads; very first sync on a high-latency connection. Note: remoteDate() swallows this and defaults to the device clock, so it's non-fatal upstream.","solutions":["Usually no action — remoteDate() already catches it and uses the device clock with a 60s retry; timestamp accuracy degrades only slightly.","If timestamp drift causes sync issues, switch to a target with supportsAccurateTimestamp (Joplin Server).","Improve network stability / reduce latency to the target.","Increase the 5000ms loop ceiling in fetchRemoteDateOffset_ if you control the client and the target is known to be slow-but-consistent."],"exampleFix":"// before\nwhile (Date.now() - loopStartTime < 5000) {\n  stat = await this.stat(tempFile);\n  if (stat) break;\n  await time.msleep(200);\n}\nif (!stat) throw new Error('Timed out trying to get sync target clock time');\n// after - longer ceiling for high-lag targets\nconst CEILING_MS = 15000;\nwhile (Date.now() - loopStartTime < CEILING_MS) {\n  stat = await this.stat(tempFile);\n  if (stat) break;\n  await time.msleep(300);\n}\nif (!stat) throw new Error('Timed out trying to get sync target clock time');","handlingStrategy":"fallback","validationCode":"// Check target reachability before relying on remote clock sync.\nconst reachable = await fileApi.stat('.');\nif (!reachable) throw new Error('Sync target not reachable; cannot sync clock.');","typeGuard":"function isClockSyncTimeout(e: any): boolean {\n  return e && typeof e.message === 'string' && e.message.includes('Timed out trying to get sync target clock time');\n}","tryCatchPattern":"// remoteDate() already catches this and falls back to device clock.\ntry {\n  const offset = await fileApi.remoteDate();\n} catch (e) {\n  if (isClockSyncTimeout(e)) { /* acceptable; using device time */ }\n  else throw e;\n}","preventionTips":["Use a sync target with supportsAccurateTimestamp (Joplin Server) when timestamp accuracy matters.","Stabilize network connectivity to the target.","Accept that eventual-consistency targets may occasionally fail this probe; it's non-fatal.","Don't block sync on clock offset; the API already falls back."],"tags":["sync","timestamp","network","clock","fallback"],"backgroundTag":null,"analyzedSha":"2654b33620775080d1d59c552259d41e33dad3d2","analyzedAt":"2026-08-12T14:26:46.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}