affaan-m/ECC · error · Error

Failed to save package manager config to ${configPath}: ${er

Error message

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

What it means

Thrown by setProjectPackageManager when writeFile(configPath, ...) fails while writing the project-level preference file at <projectDir>/.claude/package-manager.json. Unlike saveConfig (global), this writes directly via utils.writeFile without first ensuring the .claude directory exists, so a missing <projectDir>/.claude is the most common cause (ENOENT). The underlying error and the resolved configPath are included for diagnosis.

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 01e15490f0)

Solutions

  1. Create <projectDir>/.claude before calling: fs.mkdirSync(path.join(projectDir,'.claude'), { recursive:true }).
  2. Pass a projectDir that exists and is writable.
  3. Remove a conflicting directory/file at <projectDir>/.claude/package-manager.json.
  4. Free disk space or remount read-write if the project is on a read-only filesystem.

Example fix

// before: .claude does not exist yet -> writeFile throws ENOENT
setProjectPackageManager('pnpm', projectDir); // throws [299]

// after: ensure the config dir exists first
const fs = require('fs'); const path = require('path');
fs.mkdirSync(path.join(projectDir, '.claude'), { recursive: true });
setProjectPackageManager('pnpm', projectDir);
Defensive patterns

Strategy: try-catch

Validate before calling

const fs = require('fs'); const path = require('path');
function assertProjectConfigWritable(projectDir) {
  const dir = path.join(projectDir, '.claude');
  fs.mkdirSync(dir, { recursive: true });
  const probe = path.join(dir, '.ecc-write-probe');
  fs.writeFileSync(probe, ''); fs.unlinkSync(probe);
}
assertProjectConfigWritable(projectDir);
setProjectPackageManager('pnpm', projectDir);

Type guard

null

Try / catch

try {
  setProjectPackageManager(pmName, projectDir);
} catch (err) {
  if (/Failed to save package manager config to/.test(err.message)) {
    // most common cause: <projectDir>/.claude missing. Create it and retry.
    fs.mkdirSync(path.join(projectDir, '.claude'), { recursive: true });
    setProjectPackageManager(pmName, projectDir);
  } else throw err;
}

Prevention

When it happens

Trigger: Fires in the catch of writeFile inside setProjectPackageManager. Occurs when <projectDir>/.claude does not exist (utils.writeFile may not mkdir parents), when the project dir is read-only, when configPath resolves to an existing directory, on ENOSPC, or when projectDir itself does not exist.

Common situations: First-time project setup where .claude was never created; projectDir passed as a non-existent path; running in a fresh checkout without the .claude dir; read-only project mount; projectDir defaulting to process.cwd() inside a sandbox where cwd is not writable; an existing directory named package-manager.json.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/88d4d4c59d34aa5e. Report an issue: GitHub.