upstash/context7 · warning

Failed to write to ${skillPath}: ${error.message}

Error message

Failed to write to ${skillPath}: ${error.message}

What it means

During skill-file installation in `generate`, write failures are classified: EACCES/EPERM routes to the permission-denied flow with `chown` hints, while every other errno (ENOSPC, EROFS, EISDIR, EMFILE...) is logged as a warning with the failing path and message, and that target is silently skipped — the command continues to the other targets.

Source

Thrown at packages/cli/src/commands/generate.ts:533

  for (const targetDir of targetDirs) {
    let finalDir = targetDir;
    if (options.output && !targetDir.includes("/.config/") && !targetDir.startsWith(homedir())) {
      finalDir = targetDir.replace(process.cwd(), options.output);
    }
    const skillDir = join(finalDir, skillName);
    const skillPath = join(skillDir, "SKILL.md");

    try {
      await mkdir(skillDir, { recursive: true });
      await writeFile(skillPath, generatedContent!, "utf-8");
    } catch (err) {
      const error = err as NodeJS.ErrnoException;
      if (error.code === "EACCES" || error.code === "EPERM") {
        permissionError = true;
        failedDirs.add(skillDir);
      } else {
        log.warn(`Failed to write to ${skillPath}: ${error.message}`);
      }
    }
  }

  if (permissionError) {
    writeSpinner.fail(pc.red("Permission denied"));
    log.blank();
    console.log(pc.yellow("Fix permissions with:"));
    for (const dir of failedDirs) {
      const parentDir = join(dir, "..");
      console.log(pc.dim(`  sudo chown -R $(whoami) "${parentDir}"`));
    }
    log.blank();
    return;
  }

  writeSpinner.succeed(pc.green(`Created skill in ${targetDirs.length} location(s)`));
  trackEvent("gen_install");

View on GitHub (pinned to 5284672feb)

Solutions

  1. Check the errno in the logged message: ENOSPC → free disk space; EROFS → write to a writable location via `--output`
  2. Remove any directory conflicting with the SKILL.md file path
  3. Raise the file-descriptor limit (ulimit -n) for EMFILE
  4. Re-run generate after fixing and confirm the file exists at the printed path

Example fix

# before: read-only mount, warn + skipped
context7 generate  # Failed to write to .../SKILL.md: EROFS: read-only file system

# after: direct output to a writable dir
context7 generate --output ./out
Defensive patterns

Strategy: validation

Validate before calling

import { access } from 'fs/promises';
async function isWritable(dir: string): Promise<boolean> {
  try { await access(dir === '' ? '.' : dir); return true; }
  catch { return false; }
}
const ok = await isWritable(skillDir).catch(() => false);
if (!ok) throw new Error(`target not writable: ${skillDir}`);

Type guard

function isPermissionErrno(e: unknown): e is NodeJS.ErrnoException {
  return (e as NodeJS.ErrnoException)?.code === 'EACCES' || (e as NodeJS.ErrnoException)?.code === 'EPERM';
}

Try / catch

try {
  await mkdir(skillDir, { recursive: true });
  await writeFile(skillPath, content, 'utf-8');
} catch (err) {
  const code = (err as NodeJS.ErrnoException).code;
  if (code === 'ENOSPC') throw new Error('disk full — free space or use --output');
  if (code === 'EROFS') throw new Error('read-only filesystem — pass --output to a writable dir');
  if (code === 'EISDIR') throw new Error(`remove the directory at ${skillPath} first`);
  throw err;
}

Prevention

When it happens

Trigger: Disk full (ENOSPC); read-only filesystem such as a container volume or CI workspace (EROFS); an existing directory occupying the SKILL.md path (EISDIR); file-descriptor exhaustion (EMFILE).

Common situations: CI runners with full disks; Docker read-only mounts; a previous run or manual operation leaving a directory named SKILL.md; aggressive ulimits.

Related errors


AI-assisted analysis of upstash/context7@5284672feb (2026-08-18). Data as JSON: /api/errors/acbe289b9d490f98. Report an issue: GitHub.