laurent22/joplin · error · Error
tarExtract: Source file does not exist
Error message
tarExtract: Source file does not exist
What it means
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.
Source
Thrown at packages/app-mobile/utils/fs-driver/tarExtract.ts:21
import shim from '@joplin/lib/shim';
import { chunkSize } from './constants';
export interface TarExtractOptions {
cwd: string;
file: string;
}
const tarExtract = async (options: TarExtractOptions) => {
const cwd = options.cwd;
// resolve doesn't correctly handle file:// or content:// URLs. Thus, we don't resolve relative
// to cwd if the source is a URL.
const isSourceUrl = options.file.match(/$[a-z]+:\/\//);
const filePath = isSourceUrl ? options.file : resolve(cwd, options.file);
const fsDriver = shim.fsDriver();
if (!(await fsDriver.exists(filePath))) {
throw new Error('tarExtract: Source file does not exist');
}
const extract = tarStreamExtract({ defaultEncoding: 'base64' });
extract.on('entry', async (header, stream, next) => {
const outPath = fsDriver.resolveRelativePathWithinDir(cwd, header.name);
if (await fsDriver.exists(outPath)) {
throw new Error(`Extracting ${outPath} would overwrite`);
}
// Allows moving to the next item after all data for this entry has been read
// **and** this data has been processed.
// See https://github.com/laurent22/joplin/issues/10285
const streamEndPromise = new Promise<void>((resolve) => {
stream.once('end', () => resolve());
});
View on GitHub (pinned to 2654b33620)
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.
Example fix
// before
tarExtract({ cwd: dirs.DocumentDir, file: 'file:///cache/backup.tar' });
// after — use a plain path the driver can stat
const abs = `${dirs.DocumentDir}/backup.tar`;
await shim.fsDriver().copyFile(uriToPath(uri), abs);
tarExtract({ cwd: dirs.DocumentDir, file: abs }); Defensive patterns
Strategy: validation
Validate before calling
const fsDriver = shim.fsDriver();
const resolved = isUrl(options.file) ? options.file : resolve(options.cwd, options.file);
if (!(await fsDriver.exists(resolved))) {
throw new Error(`tarExtract source missing: ${resolved}`);
}
await tarExtract(options);
// helper: the in-file regex is broken, so detect URLs correctly
function isUrl(s) { return /^([a-z]+):\/\//.test(s); } Type guard
function isTarExtractOptions(v) {
return v && typeof v.cwd === 'string' && typeof v.file === 'string' && v.file.length > 0;
} Try / catch
try {
await tarExtract(options);
} catch (e) {
if (/Source file does not exist/.test(e.message)) {
// re-resolve, re-download, or prompt user
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Error! Destination already exists
- Extracting ${outPath} would overwrite
- Unsupported file system entity type: ${header.type}
- Resource file was not created: ${targetPath}
- No model found at path: ${JSON.stringify(modelFolderPath)}
AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12).
Data as JSON: /api/errors/c75ceec7652b94f2.
Report an issue: GitHub.