affaan-m/ECC · error

Failed to save package manager config to

Error message

Failed to save package manager config to ${configPath}: ${err.message}

What it means

After building the project config object, setProjectPackageManager writes it to <projectDir>/.claude/package-manager.json via writeFile; any write failure is rethrown as 'Failed to save package manager config to <path>: <reason>'. This keeps the original I/O error message attached to a descriptive wrapper.

Solutions

  1. Read the wrapped err.message for the root cause (ENOENT/EACCES/ENOSPC).
  2. Create the directory first: fs.mkdirSync(path.join(projectDir, '.claude'), { recursive: true }) then retry setProjectPackageManager.
  3. Ensure projectDir points at the actual project root and the current user has write permission.
  4. In CI, run in a writable workspace or persist the choice another way if the repo is read-only.

Example fix

// before
setProjectPackageManager('pnpm', projectDir); // throws ENOENT
// after
fs.mkdirSync(path.join(projectDir, '.claude'), { recursive: true });
setProjectPackageManager('pnpm', projectDir);
Defensive patterns

Strategy: try-catch

Validate before calling

import fs from 'node:fs';
import path from 'node:path';
const dir = path.join(projectDir, '.claude');
fs.mkdirSync(dir, { recursive: true });
fs.accessSync(dir, fs.constants.W_OK);

Type guard

null

Try / catch

try {
  setProjectPackageManager('pnpm', projectDir);
} catch (e) {
  if (e.message.startsWith('Failed to save package manager config')) {
    // message includes configPath + cause (ENOENT/EACCES/ENOSPC); remediate and retry
    console.error(e.message);
  } else throw e;
}

Prevention

When it happens

Trigger: The .claude directory does not exist (ENOENT since writeFile doesn't mkdir), the project directory is read-only, path permissions deny the write (EACCES), or disk is full (ENOSPC).

Common situations: Fresh clones with no .claude directory yet; project dirs mounted read-only in containers; wrong projectDir argument pointing to a non-writable location; running as a user without write access to the repo.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/88d4d4c59d34aa5e. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/package-manager.js:281

 * Set project's preferred package manager
 */
function setProjectPackageManager(pmName, projectDir = process.cwd()) {
  if (!PACKAGE_MANAGERS[pmName]) {
    throw new Error(`Unknown package manager: ${pmName}`);
  }

  const configDir = path.join(projectDir, '.claude');
  const configPath = path.join(configDir, 'package-manager.json');

  const config = {
    packageManager: pmName,
    setAt: new Date().toISOString()
  };

  try {
    writeFile(configPath, JSON.stringify(config, null, 2));
  } catch (err) {
    throw new Error(`Failed to save package manager config to ${configPath}: ${err.message}`);
  }
  return config;
}

// Allowed characters in script/binary names: alphanumeric, dash, underscore, dot, slash, @
// This prevents shell metacharacter injection while allowing scoped packages (e.g., @scope/pkg)
const SAFE_NAME_REGEX = /^[@a-zA-Z0-9_./-]+$/;

/**
 * Get the command to run a script
 * @param {string} script - Script name (e.g., "dev", "build", "test")
 * @param {object} options - { projectDir }
 * @throws {Error} If script name contains unsafe characters
 */
function getRunCommand(script, options = {}) {
  if (!script || typeof script !== 'string') {
    throw new Error('Script name must be a non-empty string');
  }

View on GitHub (pinned to 8321021c54)