remix-run/react-router · error · CopyTemplateError

There was a problem fetching the file

Error message

There was a problem fetching the file${isGithubUrl ? " from GitHub" : ""}. The request responded with a ${response.status} status. Please try again later.

What it means

The tarball download (codeload.github.com tarball, api.github.com tarball redirect, or generic URL) failed with a non-200 status and no token was supplied. Because no credentials were involved, this points at the resource itself or transient conditions: 404 for a nonexistent or private repo/branch, 403 from GitHub API rate limiting, or 5xx during a GitHub incident.

Solutions

  1. If the repo is private, pass --token with a PAT that can read it
  2. Verify the repo and branch exist by opening the URL in a browser
  3. Check rate limits: curl https://api.github.com/rate_limit
  4. Retry after a few minutes for transient failures or incidents

Example fix

# before: private repo, no token -> 404
npx create-react-router my-app --template https://github.com/acme/private-tpl/tree/main

# after: supply a token with read access
npx create-react-router my-app --token "$GH_TOKEN" \
  --template https://github.com/acme/private-tpl/tree/main
Defensive patterns

Strategy: retry

Validate before calling

async function repoIsPubliclyFetchable(owner: string, repo: string, branch?: string) {
  const res = await fetch(
    `https://api.github.com/repos/${owner}/${repo}` + (branch ? `/branches/${branch}` : ""),
  );
  if (res.status === 404) {
    throw new Error("Repo/branch missing or private — pass --token");
  }
  if (res.status === 403) {
    throw new Error("Rate limited — export GITHUB_TOKEN or retry later");
  }
  return res.ok;
}

Try / catch

async function scaffoldWithBackoff(args: string[], attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    try {
      return await createReactRouter(args);
    } catch (e) {
      const msg = e instanceof Error ? e.message : String(e);
      const retryable = /problem fetching the file/.test(msg) && !/404/.test(msg);
      if (!retryable || i === attempts - 1) throw e;
      await new Promise((r) => setTimeout(r, 2 ** i * 5_000));
    }
  }
}

Prevention

When it happens

Trigger: Scaffolding from a private repo without --token (404); a deleted or renamed branch in a /tree/:branch URL; unauthenticated GitHub API quota exhausted (60 requests/hour per IP); transient GitHub outage or network failure.

Common situations: Template repos moved or deleted; typos in branch names; CI runners on shared IPs exhausting the unauthenticated quota; flaky networks.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

          "found at that url. Please try again later.",
      );
    }
    resourceUrl = `https://api.github.com/repos/${info.owner}/${info.name}/releases/assets/${assetId}`;
    headers.Accept = "application/octet-stream";
  }
  let response = await fetch(resourceUrl, { headers });

  if (!response.body || response.status !== 200) {
    if (token) {
      throw new CopyTemplateError(
        `There was a problem fetching the file${
          isGithubUrl ? " from GitHub" : ""
        }. The request ` +
          `responded with a ${response.status} status. Perhaps your \`--token\`` +
          "is expired or invalid.",
      );
    }
    throw new CopyTemplateError(
      `There was a problem fetching the file${
        isGithubUrl ? " from GitHub" : ""
      }. The request ` +
        `responded with a ${response.status} status. Please try again later.`,
    );
  }

  // file paths returned from GitHub are always unix style
  if (filePath) {
    filePath = filePath.split(path.sep).join(path.posix.sep);
  }

  let filePathHasFiles = false;

  try {
    let input = new stream.PassThrough();
    // Start reading stream into passthrough, don't await to avoid buffering
    writeReadableStreamToWritable(response.body, input);

View on GitHub (pinned to 6beaca3952)