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
- Fix ownership of candidatePath (and the project root) so the current user owns it: sudo chown -R $USER:$USER <project>.
- Restore write/execute permission: chmod -R u+w <project> and chmod u+x on directories.
- If the path is on a read-only mount, move the project to a writable location or remount read-write.
- 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
- Run ECC as the user that owns the project; avoid mixing sudo and non-sudo runs.
- Ensure project dirs have u+rwx and files have u+rw before install.
- Do not place the project on a read-only mount.
- If a prior root-owned run created .claude, chown it back to your user first.
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
- Refusing to write ${operation.destinationPath}: destination
- Refusing to trust managed install-state at ${plan.installSta
- Refusing to trust managed install-state at ${plan.installSta
- Refusing to trust managed install-state at ${plan.installSta
- Refusing to trust non-managed ownership from install-state a
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/63e6d801de69ddea.
Report an issue: GitHub.