affaan-m/ECC · error · Error
Unsafe state-store path
Error message
Unsafe state-store path '${currentPath}': an intermediate component is not a directory What it means
The state-store creates its database directory (and all intermediate directories) with restrictive permissions (mode 0700). Before creating each segment it lstats the path and rejects any component that exists but is not a directory (e.g. a regular file, FIFO, device node), with an exception only for macOS's root-level /var, /tmp, /etc symlinks to /private/*. This is a path-safety guard: writing a database through a path that traverses a non-directory would fail unpredictably or could be an attacker-planted structure, so the library fails fast with an explicit error naming the offending component.
Solutions
- Inspect the path named in the error (ls -la) and remove or rename the offending non-directory component, e.g. mv /home/me/.claude /home/me/.claude.bak then mkdir -p /home/me/.claude
- Verify you passed the database FILE path as dbPath (not a directory path, and not dirname of the db), letting resolveStateStorePath place it under ~/.claude/ecc/state.db
- If a mount/volume shadowed the directory, unmount or fix the mount so the component is a real directory
- If HOME points somewhere unexpected (CI, containers), set HOME or pass options.homeDir so the default path resolves to a writable tree of real directories
Example fix
// before: ~/.claude exists as a regular file, crashes
createStateStore({});
// after: detect and move the file aside before opening the store
import fs from 'fs';
if (fs.existsSync(homeClaude) && fs.lstatSync(homeClaude).isFile()) {
fs.renameSync(homeClaude, homeClaude + '.backup');
}
const store = await createStateStore({ homeDir: process.env.HOME }); Defensive patterns
Strategy: validation
Validate before calling
import fs from 'fs';
import path from 'path';
export function assertDirectoryChainUsable(dirPath) {
let current = path.resolve(dirPath);
const parts = current.split(path.sep).filter(Boolean);
let acc = path.parse(current).root;
for (const part of parts) {
acc = path.join(acc, part);
const st = fs.lstatSync(acc, { throwIfNoEntry: false });
if (st && !st.isDirectory() && !st.isSymbolicLink()) {
throw new Error(`Cannot use state store: '${acc}' exists and is not a directory`);
}
}
}
// before creating the store:
assertDirectoryChainUsable(path.dirname(dbPath)); Type guard
function isDirectory(p) {
const st = fs.lstatSync(p, { throwIfNoEntry: false });
return st !== undefined && st.isDirectory();
} Try / catch
try {
const store = await createStateStore({ dbPath });
} catch (error) {
if (error.message.includes('intermediate component is not a directory')) {
const offender = error.message.match(/'([^']+)'/)[1];
fs.renameSync(offender, offender + '.not-a-directory.bak');
// retry once
} else {
throw error;
}
} Prevention
- Never create plain files at paths you also intend to use as directories (especially dotfiles like ~/.claude)
- Pass the database FILE path as dbPath and the directory via its parent, never the directory itself as dbPath
- Pin HOME/homeDir explicitly in CI and containers so the default store path resolves predictably
- Check mounts and volumes that might shadow a directory with a file before initializing the store
When it happens
Trigger: Calling createStateStore({ dbPath }) or any write that calls ensurePrivateDirectory(dirname(dbPath)) when some intermediate component of the resolved directory path already exists as a non-directory: e.g. dbPath '/home/me/.claude/ecc/state.db' where /home/me/.claude is a regular file (or the directory itself is a file such as '/home/me/.claude'), so the loop hits that segment with stats.isFile() true.
Common situations: A dotfile like ~/.claude exists as a plain file (some tools write ~/.claude as a config file), a leftover state.db file is reused as the directory path (dbPath accidentally passed as the directory), mount points or Docker volume mounts replaced a directory with a file, or a package/tool created a file at a path later expected to be a directory.
Related errors
- Unsafe state-store path
- Refusing to : missing destination path.
- all overlays must be readable local files
- all takes must be readable local files
- artifact path must stay beneath output root
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/4e415169459e485f.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/state-store/index.js:95
for (const segment of segments) {
currentPath = path.join(currentPath, segment);
let stats = lstatIfPresent(currentPath);
assertNotSymlink(currentPath, stats);
if (!stats) {
try {
fs.mkdirSync(currentPath, { mode: PRIVATE_DIRECTORY_MODE });
} catch (error) {
if (!error || error.code !== 'EEXIST') {
throw error;
}
}
stats = fs.lstatSync(currentPath);
assertNotSymlink(currentPath, stats);
}
if (!stats.isDirectory() && !isAllowedPlatformSymlink(currentPath, stats)) {
throw stateStorePathError(currentPath, 'an intermediate component is not a directory');
}
}
return absolutePath;
}
function assertSafeDatabaseFile(dbPath) {
const stats = lstatIfPresent(dbPath);
assertNotSymlink(dbPath, stats);
if (stats && !stats.isFile()) {
throw stateStorePathError(dbPath, 'database path is not a regular file');
}
return stats;
}
function readDatabaseFile(dbPath) {
assertSafeDatabaseFile(dbPath);
const noFollow = fs.constants.O_NOFOLLOW || 0;View on GitHub (pinned to 8321021c54)