affaan-m/ECC · error · Error

${label} destination is not writable by the current user: ${

Error message

${label} destination is not writable by the current user: ${candidatePath}. Fix the project ownership or permissions, then retry.

What it means

Thrown by assertManagedDestinationsWritable when fs.accessSync(candidatePath, mode) fails for one or more destination paths (or their nearest-existing ancestor directory). The installer verifies write (and execute, for directories) permission on every destination plus the install-state path before applying; if the current user lacks permission, the apply would fail mid-write, leaving a partial install. The label is 'Kimi' when plan.target === 'kimi', otherwise 'Managed install'.

Source

Thrown at scripts/lib/multi-harness-setup.js:286

  const accessSync = dependencies.accessSync || fs.accessSync;
  const destinationPaths = [
    ...plan.operations.map(operation => operation.destinationPath),
    ...(plan.installStatePath ? [plan.installStatePath] : []),
  ];
  const requirements = new Map();

  for (const destinationPath of destinationPaths) {
    const requirement = writableRequirement(destinationPath);
    const existingMode = requirements.get(requirement.candidatePath) || 0;
    requirements.set(requirement.candidatePath, existingMode | requirement.mode);
  }

  for (const [candidatePath, mode] of requirements) {
    try {
      accessSync(candidatePath, mode);
    } catch (_error) {
      const label = plan.target === 'kimi' ? 'Kimi' : 'Managed install';
      throw new Error(
        `${label} destination is not writable by the current user: ${candidatePath}. `
        + 'Fix the project ownership or permissions, then retry.'
      );
    }
  }
}

function preflightManagedPlan(plan, dependencies = {}) {
  if (!plan || !Array.isArray(plan.operations)) {
    throw new Error('A managed install plan with operations is required.');
  }
  const ownership = readOwnedDestinations(plan, dependencies);
  const operations = plan.operations.map(operation => {
    assertSafeInstallOperation(plan, operation);
    return {
      destinationPath: operation.destinationPath,
      kind: operation.kind,
      classification: classifyManagedOperation(operation, ownership.destinations),

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Fix ownership of candidatePath (and the project root) so the current user owns it: sudo chown -R $USER:$USER <project>.
  2. Restore write/execute permission: chmod -R u+w <project> and chmod u+x on directories.
  3. If the path is on a read-only mount, move the project to a writable location or remount read-write.
  4. Run the install as the user that owns the project rather than mixing users (avoid sudo for one run and non-sudo for the next).

Example fix

// before: .claude created by root, now running as normal user
assertManagedDestinationsWritable(plan); // throws [293]

# after: fix ownership and permissions
sudo chown -R $USER:$USER /home/me/app
chmod -R u+w /home/me/app
find /home/me/app -type d -exec chmod u+x {} \;
Defensive patterns

Strategy: validation

Validate before calling

const fs = require('fs'); const path = require('path');
function writableRequirement(p) {
  if (fs.existsSync(p)) {
    const mode = fs.statSync(p).isDirectory() ? fs.constants.W_OK | fs.constants.X_OK : fs.constants.W_OK;
    return { candidatePath: p, mode };
  }
  let c = path.dirname(p);
  while (!fs.existsSync(c)) { const parent = path.dirname(c); if (parent === c) break; c = parent; }
  return { candidatePath: c, mode: fs.constants.W_OK | fs.constants.X_OK };
}
function assertWritable(destinations) {
  for (const p of destinations) {
    const r = writableRequirement(p);
    fs.accessSync(r.candidatePath, r.mode);
  }
}
assertWritable([...plan.operations.map(o=>o.destinationPath), plan.installStatePath].filter(Boolean));

Type guard

null

Try / catch

try {
  await applyMultiHarnessPlan(plan);
} catch (err) {
  if (/not writable by the current user/.test(err.message)) {
    const m = err.message.match(/user: (.+)\./);
    throw new Error(`Fix permissions on ${m ? m[1] : 'project'} then re-run. Try: sudo chown -R $USER <project>`);
  }
  throw err;
}

Prevention

When it happens

Trigger: Fires inside assertManagedDestinationsWritable (called from preflightManagedPlan) when accessSync throws. The candidate path is either the destination itself (if it exists) or the nearest existing ancestor directory; mode is W_OK for files and W_OK|X_OK for directories. Common when files/dirs are owned by root or another user, when the project lives on a read-only mount, or when permissions were stripped.

Common situations: Running install with sudo such that ECC-created dirs are root-owned and a later non-root run cannot write; project on a read-only filesystem or container mount; chmod -R go-w applied to .claude; files created by a different user (CI vs local); directory without execute permission so traversal fails.

Related errors


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