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
- Re-run create-turbo in a directory you own, e.g. under $HOME (`mkdir -p ~/apps && cd ~/apps && npx create-turbo@latest my-app`)
- Fix permissions on the target directory: `chmod u+w <dir>` or `chown $(whoami) <dir>`
- Free disk space or raise the quota (`df -h .`) and retry
- Temporarily exclude the project dir from antivirus/sync software and retry
- 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
- Always scaffold into a directory owned by the invoking user (under $HOME), never system paths
- Pre-create a .gitignore yourself so the transform takes its 'not-applicable' path and never writes
- Keep free disk space on CI runners; quota-exhausted runners are a recurring cause of writeFileSync failures
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
- Unable to write package.json
- Unable to read package.json
- Unable to update README.md
- Generator config directory already exists at ${configDirecto
- isErrorLike(reason) ? reason.message : String(reason)
AI-assisted analysis of vercel/turborepo@9f94a7d215 (2026-08-16).
Data as JSON: /api/errors/d4d0a9b5b2ee7ad4.
Report an issue: GitHub.