vercel/turborepo · error · TransformError

Unable to write .gitignore

Error message

Unable to write .gitignore

What it means

Thrown by the `create-turbo` scaffolding CLI when its git-ignore transform cannot write the default .gitignore file into the freshly created project root. The transform only writes when no .gitignore exists; fs.writeFileSync failing (permissions, read-only filesystem, disk full) triggers this non-fatal TransformError. It aborts that transform step, not necessarily the whole scaffold.

Source

Thrown at packages/create-turbo/src/transforms/git-ignore.ts:24

const meta = {
  name: "git-ignore"
};

// eslint-disable-next-line @typescript-eslint/require-await -- must match transform function signature
export async function transform(args: TransformInput): TransformResult {
  const { prompts } = args;
  const ignorePath = path.join(prompts.root, ".gitignore");
  try {
    if (!fs.existsSync(ignorePath)) {
      fs.writeFileSync(ignorePath, DEFAULT_IGNORE);
    } else {
      return { result: "not-applicable", ...meta };
    }
  } catch (err) {
    // existsSync cannot throw, so we don't need to narrow here and can
    // assume this came from writeFileSync
    throw new TransformError("Unable to write .gitignore", {
      transform: meta.name,
      fatal: false
    });
  }

  return { result: "success", ...meta };
}

View on GitHub (pinned to 9f94a7d215)

Solutions

  1. Re-run create-turbo in a directory you own, e.g. under $HOME (`mkdir -p ~/apps && cd ~/apps && npx create-turbo@latest my-app`)
  2. Fix permissions on the target directory: `chmod u+w <dir>` or `chown $(whoami) <dir>`
  3. Free disk space or raise the quota (`df -h .`) and retry
  4. Temporarily exclude the project dir from antivirus/sync software and retry
  5. Workaround: pre-create an empty .gitignore yourself; the transform then reports 'not-applicable' and skips the write entirely

Example fix

# before
sudo npx create-turbo@latest /opt/apps/my-app   # root-owned dir -> EACCES
# after
mkdir -p ~/apps && cd ~/apps && npx create-turbo@latest my-app
Defensive patterns

Strategy: try-catch

Validate before calling

import fs from 'node:fs';

function ensureWritableDir(dir: string): boolean {
  try {
    fs.accessSync(dir, fs.constants.W_OK);
    return true;
  } catch {
    return false;
  }
}

Try / catch

// When invoking create-turbo programmatically / wrapping the CLI:
try {
  await createApp({ root, example: 'basic' });
} catch (err) {
  if (err instanceof TransformError && err.transform === 'git-ignore' && err.fatal === false) {
    // non-fatal: write the .gitignore manually and continue
    fs.writeFileSync(path.join(root, '.gitignore'), DEFAULT_IGNORE);
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Running create-turbo into a directory where writeFileSync(ignorePath, DEFAULT_IGNORE) throws: EACCES (dir owned by another user), EROFS (read-only mount/volume), ENOSPC (disk/quota full), EMFILE, or a file-locking antivirus/EDR on Windows. If .gitignore already exists the transform returns 'not-applicable' and never throws.

Common situations: Scaffolding into system directories (/var/www, /opt) without ownership; Docker containers with read-only volumes; CI runners with exhausted disk quotas; cloud-sync clients (OneDrive/Dropbox) locking newly created files mid-write.

Related errors


AI-assisted analysis of vercel/turborepo@9f94a7d215 (2026-08-16). Data as JSON: /api/errors/d4d0a9b5b2ee7ad4. Report an issue: GitHub.