{"record":{"id":"c75ceec7652b94f2","repo":"laurent22/joplin","slug":"tarextract-source-file-does-not-exist","errorCode":null,"errorMessage":"tarExtract: Source file does not exist","messagePattern":"tarExtract: Source file does not exist","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/app-mobile/utils/fs-driver/tarExtract.ts","lineNumber":21,"sourceCode":"import shim from '@joplin/lib/shim';\nimport { chunkSize } from './constants';\n\nexport interface TarExtractOptions {\n\tcwd: string;\n\tfile: string;\n}\n\nconst tarExtract = async (options: TarExtractOptions) => {\n\tconst cwd = options.cwd;\n\n\t// resolve doesn't correctly handle file:// or content:// URLs. Thus, we don't resolve relative\n\t// to cwd if the source is a URL.\n\tconst isSourceUrl = options.file.match(/$[a-z]+:\\/\\//);\n\tconst filePath = isSourceUrl ? options.file : resolve(cwd, options.file);\n\n\tconst fsDriver = shim.fsDriver();\n\tif (!(await fsDriver.exists(filePath))) {\n\t\tthrow new Error('tarExtract: Source file does not exist');\n\t}\n\n\tconst extract = tarStreamExtract({ defaultEncoding: 'base64' });\n\n\textract.on('entry', async (header, stream, next) => {\n\t\tconst outPath = fsDriver.resolveRelativePathWithinDir(cwd, header.name);\n\n\t\tif (await fsDriver.exists(outPath)) {\n\t\t\tthrow new Error(`Extracting ${outPath} would overwrite`);\n\t\t}\n\n\t\t// Allows moving to the next item after all data for this entry has been read\n\t\t// **and** this data has been processed.\n\t\t// See https://github.com/laurent22/joplin/issues/10285\n\t\tconst streamEndPromise = new Promise<void>((resolve) => {\n\t\t\tstream.once('end', () => resolve());\n\t\t});\n","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/app-mobile/utils/fs-driver/tarExtract.ts#L3-L39","documentation":"Thrown by the mobile tarExtract helper when the source archive passed via options.file cannot be found at the resolved path. The function first computes filePath by checking whether the source is a URL (file:// or content://) and otherwise resolves it relative to cwd, then asserts existence with shim.fsDriver().exists(). Note: the URL-detection regex on line 16, /$[a-z]+:\\/\\//, is malformed ($ is an end-of-string anchor placed at the pattern start) so isSourceUrl is effectively always null, meaning even genuine file:///content:// URLs are passed through resolve(cwd, options.file) which mangles them.","triggerScenarios":"Calling tarExtract({ cwd, file }) where file is a path that does not exist on disk; passing a relative path that does not resolve under cwd; passing a file:// or content:// URI (because the broken regex never classifies it as a URL, resolve() corrupts the URI into a nonsense filesystem path); the file was deleted between the caller's check and the call; wrong cwd on Android where DocumentDir differs from expectation.","commonSituations":"Restoring a Joplin backup on mobile where the backup tar was moved or never fully downloaded; the backup path was constructed from a content:// URI that the FS driver cannot stat; iOS/Android sandbox path differences between the producer and consumer of the tar; a previous extraction partially failed leaving an inconsistent state.","solutions":["Verify the source path exists before calling tarExtract: await shim.fsDriver().exists(options.file) and log the resolved absolute path.","If passing a file:// or content:// URL, be aware the URL-detection regex is broken — pass a plain filesystem path instead, or patch tarExtract to use /^([a-z]+):\\/\\//.","Confirm cwd is the directory you expect (e.g. RNFetchBlob.fs.dirs.DocumentDir) and that file is relative to it or already absolute.","If restoring a backup, re-download or re-copy the tar to a known writable directory and pass that absolute path."],"exampleFix":"// before\ntarExtract({ cwd: dirs.DocumentDir, file: 'file:///cache/backup.tar' });\n// after — use a plain path the driver can stat\nconst abs = `${dirs.DocumentDir}/backup.tar`;\nawait shim.fsDriver().copyFile(uriToPath(uri), abs);\ntarExtract({ cwd: dirs.DocumentDir, file: abs });","handlingStrategy":"validation","validationCode":"const fsDriver = shim.fsDriver();\nconst resolved = isUrl(options.file) ? options.file : resolve(options.cwd, options.file);\nif (!(await fsDriver.exists(resolved))) {\n  throw new Error(`tarExtract source missing: ${resolved}`);\n}\nawait tarExtract(options);\n\n// helper: the in-file regex is broken, so detect URLs correctly\nfunction isUrl(s) { return /^([a-z]+):\\/\\//.test(s); }","typeGuard":"function isTarExtractOptions(v) {\n  return v && typeof v.cwd === 'string' && typeof v.file === 'string' && v.file.length > 0;\n}","tryCatchPattern":"try {\n  await tarExtract(options);\n} catch (e) {\n  if (/Source file does not exist/.test(e.message)) {\n    // re-resolve, re-download, or prompt user\n  }\n  throw e;\n}","preventionTips":["Always pass an absolute filesystem path, not a file:// or content:// URI, given the broken URL regex.","Assert existence with shim.fsDriver().exists immediately before calling tarExtract.","Log the resolved path on failure so path-mangling bugs surface."],"tags":["mobile","filesystem","tar","android","ios","backup"],"backgroundTag":null,"analyzedSha":"2654b33620775080d1d59c552259d41e33dad3d2","analyzedAt":"2026-08-12T14:26:46.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}