abhigyanpatwari/GitNexus · error · UnsafeStoragePathError
Refusing to remove storage path for safety: expected "${expe
Error message
Refusing to remove storage path for safety: expected "${expectedStoragePath}" under the repo's .gitnexus subfolder, but the registry entry has "${actualStoragePath}". This usually means the registry entry is corrupted or was hand-edited. Delete the entry manually from ~/.gitnexus/registry.json and re-run analyze. What it means
Destructive commands (gitnexus remove, clean --all) delete the entry's storagePath from disk. As a final safety gate, assertSafeStoragePath requires storagePath to be exactly <entry.path>/.gitnexus (Windows case-insensitive, POSIX case-sensitive). Any other value — a hand-edited or corrupted registry entry — aborts deletion so an arbitrary directory can never be recursively removed.
Source
Thrown at gitnexus/src/storage/repo-manager.ts:1282
* the registry field. But `clean --all` DOES iterate the registry
* and trust each entry's stored storagePath (same shape as
* `remove`), so this helper must be wired into that loop too.
* - `server/api.ts` recomputes storagePath from `getStoragePath(entry.path)`
* and so is likewise safe-by-construction.
*
* Pure string check — does NOT require the paths to exist on disk.
* Windows: case-insensitive; POSIX: case-sensitive. Matches the
* comparison shape used elsewhere in this module.
*/
export const assertSafeStoragePath = (entry: RegistryEntry): void => {
const expected = path.join(path.resolve(entry.path), '.gitnexus');
const actual = path.resolve(entry.storagePath);
const matches =
process.platform === 'win32'
? expected.toLowerCase() === actual.toLowerCase()
: expected === actual;
if (!matches) {
throw new UnsafeStoragePathError(entry, expected, actual);
}
};
/**
* Resolve a user-supplied target string (from `gitnexus remove <target>`
* or equivalent MCP tool argument) to a single registry entry.
*
* Match precedence (first hit wins, subsequent tiers are only tried if
* the prior tier produces zero matches):
* 1. Exact resolved-path match (Windows: case-insensitive).
* Paths are unique by registry construction, so a path match can
* never be ambiguous.
* 2. Exact `name` match (case-insensitive). If ≥ 2 entries share the
* name — only possible via `--allow-duplicate-name` (#829) —
* throws {@link RegistryAmbiguousTargetError}.
*
* No fuzzy / partial matching — unambiguous, scriptable behaviour is
* more important than convenience for destructive commands.View on GitHub (pinned to aac7515d2a)
Solutions
- Open ~/.gitnexus/registry.json and fix the entry (set storagePath to <entry.path>/.gitnexus), or delete the bogus entry manually as the message instructs, then re-run
- Re-register cleanly: remove the broken entry, run gitnexus analyze on the repo again
- Never point storagePath at shared/external directories — this guard exists precisely to keep deletion inside the repo's .gitnexus
Example fix
// ~/.gitnexus/registry.json — before
{ "name": "app", "path": "/srv/app", "storagePath": "/srv" }
// after
{ "name": "app", "path": "/srv/app", "storagePath": "/srv/app/.gitnexus" } Defensive patterns
Strategy: validation
Validate before calling
import path from 'node:path';
function isSafeStorageEntry(entry: { path: string; storagePath: string }): boolean {
const expected = path.join(path.resolve(entry.path), '.gitnexus');
const actual = path.resolve(entry.storagePath);
return process.platform === 'win32'
? expected.toLowerCase() === actual.toLowerCase()
: expected === actual;
} Prevention
- Never hand-edit storagePath in ~/.gitnexus/registry.json
- Validate registry entries with this exact check before running destructive commands in automation
- Re-register via gitnexus analyze instead of patching registry JSON
When it happens
Trigger: registry.json was edited by hand so storagePath points somewhere else (or entry.path changed without storagePath); a mangled/migrated registry; then running gitnexus remove or clean --all.
Common situations: Users hand-editing ~/.gitnexus/registry.json to 'fix' paths; backup/sync tools rewriting home files; registry formats from incompatible versions.
Related errors
- Registry name "${registryName}" is already used by "${existi
- Analysis did not finalize for ${repoPath}: registry entry fo
- ${source}: branch name must not contain a backtick (it would
- ${source} must be a boolean (true/false).
- ${name} must be a positive integer, got "${raw}"
AI-assisted analysis of abhigyanpatwari/GitNexus@aac7515d2a (2026-08-20).
Data as JSON: /api/errors/4da9c0e2834edd38.
Report an issue: GitHub.