remix-run/react-router · error · CopyTemplateError

There was a problem extracting the file from the provided…

Error message

There was a problem extracting the file from the provided template.  Template filepath: `${tarballPath}`  Destination directory: `${destPath}`  ${error}

What it means

extractLocalTarball() streams the local .tar.gz through gunzip-maybe and tar-fs into the destination directory via stream.pipeline; this error wraps any failure in that pipeline. The original error text is appended to the message, which tells you whether it was a gzip error (file not actually gzip-compressed), a tar parse error (corrupt archive), or a filesystem error (permissions, missing destination).

Solutions

  1. Verify the file is really gzip: 'file template.tar.gz' should report 'gzip compressed data'
  2. Verify integrity: 'tar -tzf template.tar.gz' should list entries without errors
  3. Re-create the tarball from the source directory: tar -czf template.tar.gz -C template-dir .
  4. Check write permissions and free space on the destination volume

Example fix

# before: renamed zip fails
mv template.zip template.tar.gz
npx create-react-router my-app --template ./template.tar.gz

# after: real gzip tarball
tar -czf template.tar.gz -C template .
npx create-react-router my-app --template ./template.tar.gz
Defensive patterns

Strategy: try-catch

Validate before calling

import { execSync } from "node:child_process";

function verifyLocalTarball(p: string): void {
  // Lists entries; non-zero exit means corrupt or not gzip
  execSync(`tar -tzf ${JSON.stringify(p)}`, { stdio: "pipe" });
}

Try / catch

try {
  await copyTemplate(tarballPath, dest, { onError: (e) => { throw e; } });
} catch (e) {
  if (e instanceof CopyTemplateError && /problem extracting/.test(e.message)) {
    // The original cause is appended to the message
    const cause = /gzip/.test(e.message) ? "not gzip data"
      : /tar/.test(e.message) ? "corrupt archive"
      : /EACCES|permission/i.test(e.message) ? "destination not writable"
      : "truncated tarball";
    console.error(`Extraction failed (${cause}); re-create with tar -czf`);
    process.exit(1);
  }
  throw e;
}

Prevention

When it happens

Trigger: A file named *.tar.gz that is actually a renamed zip or plain tar; a tarball truncated by an interrupted download; an unwritable or deleted destination directory; disk-full conditions while extracting.

Common situations: Manually renaming archives to .tar.gz without converting them; partial downloads; tarballs produced on Windows with non-gzip compression; restricted OS temp directories.

Related errors


AI-assisted analysis of remix-run/react-router@6beaca3952 (2026-08-18). Data as JSON: /api/errors/afe56b50272aee00. Report an issue: GitHub.

Appendix: source

Thrown at packages/create-react-router/copy-template.ts:157

  throw new CopyTemplateError(
    "The provided template is not a valid local directory or tarball.",
  );
}

const pipeline = promisify(stream.pipeline);

async function extractLocalTarball(
  tarballPath: string,
  destPath: string,
): Promise<void> {
  try {
    await pipeline(
      fs.createReadStream(tarballPath),
      gunzip(),
      tar.extract(destPath, { strip: 1 }),
    );
  } catch (error: unknown) {
    throw new CopyTemplateError(
      "There was a problem extracting the file from the provided template." +
        `  Template filepath: \`${tarballPath}\`` +
        `  Destination directory: \`${destPath}\`` +
        `  ${error}`,
    );
  }
}

interface TarballDownloadOptions {
  debug?: boolean;
  filePath?: string | null;
  token?: string;
}

async function downloadAndExtractRepoTarball(
  repo: RepoInfo,
  destPath: string,
  options: TarballDownloadOptions,

View on GitHub (pinned to 6beaca3952)