shadcn-ui/ui · error · FileBackupError

Could not back up ${filePath}.

Error message

Could not back up ${filePath}.

What it means

Thrown as a FileBackupError when createFileBackup cannot rename a linked workspace's components.json to its .bak sidecar during `shadcn apply`. The apply command backs up every linked workspace config before writing the style/tailwind patch; if the atomic rename inside createFileBackup throws, it returns null and the sync aborts with this error.

Source

Thrown at packages/shadcn/src/commands/apply.ts:486

    const configPath = path.resolve(
      linkedConfig.resolvedPaths.cwd,
      "components.json"
    )
    if (!(await fs.pathExists(configPath))) {
      continue
    }

    workspaceConfigs.push({
      configPath,
      existingConfig: await fs.readJson(configPath),
    })
  }

  try {
    for (const workspaceConfig of workspaceConfigs) {
      const backupPath = createFileBackup(workspaceConfig.configPath)
      if (!backupPath) {
        throw new FileBackupError(workspaceConfig.configPath)
      }
    }

    for (const workspaceConfig of workspaceConfigs) {
      await fs.writeJson(
        workspaceConfig.configPath,
        {
          ...workspaceConfig.existingConfig,
          ...patch,
          tailwind: {
            ...workspaceConfig.existingConfig.tailwind,
            ...patch.tailwind,
          },
        },
        { spaces: 2 }
      )
    }

View on GitHub (pinned to efac598707)

Solutions

  1. Verify write permissions on each linked workspace's components.json AND its parent directory (chmod/chown).
  2. Ensure the working directory and the target config are on the same filesystem (avoid separate /tmp mounts).
  3. Remove any stale components.json.bak files left by a previous crashed run before retrying.
  4. Disable file-locking antivirus / pause IDE watchers for the project directory and retry.
  5. Re-run with adequate privileges and a writable working tree (not a read-only container filesystem).
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight before calling the apply sync path: ensure each linked
// workspace's components.json is on a writable directory.
import fs from "fs-extra"
import path from "path"

async function assertBackupCapable(configPaths: string[]) {
  for (const p of configPaths) {
    const dir = path.dirname(p)
    try {
      await fs.access(dir, fs.constants.W_OK | fs.constants.R_OK)
    } catch {
      throw new Error(`Directory not writable for backup: ${dir}`)
    }
    // Stale .bak from a prior crashed run will block rename on some FSes.
    const bak = `${p}.bak`
    if (await fs.pathExists(bak)) await fs.remove(bak)
  }
}

Type guard

import fs from "fs-extra"
import path from "path"

function isBackupCapableSync(filePath: string): boolean {
  try {
    fs.accessSync(path.dirname(filePath), fs.constants.W_OK | fs.constants.R_OK)
    return true
  } catch {
    return false
  }
}

Try / catch

try {
  await applyWorkspaceSync(config)
} catch (e) {
  if (e instanceof Error && /Could not back up/.test(e.message)) {
    // e.filePath (FileBackupError) holds the offending config path.
    const offending = (e as { filePath?: string }).filePath
    console.error(`Backup failed for ${offending}. Check permissions / clean .bak files.`)
  }
  throw e
}

Prevention

When it happens

Trigger: Running `shadcn apply` in a monorepo where getWorkspaceConfig yields linked configs, and createFileBackup returns null for one of them. The existence of components.json is already verified at apply.ts:472, so null here means fsExtra.renameSync threw — EPERM/EACCES, EXDEV (cross-device rename), or the file vanished between the exists-check and the rename.

Common situations: Read-only filesystem (CI runner, container with read-only mount), permission denied on components.json or its parent dir, /tmp or workspace on a different mount than the project (EXDEV), a stale .bak held open by another process, antivirus/file-locking on Windows, IDE watcher holding the file.

Related errors


AI-assisted analysis of shadcn-ui/ui@efac598707 (2026-08-12). Data as JSON: /api/errors/7ba0916f3394b861. Report an issue: GitHub.