{"record":{"id":"386ed3024f45486f","repo":"abhigyanpatwari/GitNexus","slug":"unsafe-storage-path-for-entry-path-expected-error","errorCode":null,"errorMessage":"Unsafe storage path for ${entry.path}: expected ${error.expectedStoragePath}, actual ${error.actualStoragePath}","messagePattern":"Unsafe storage path for (.+?): expected (.+?), actual (.+?)","errorType":"exception","errorClass":"UnsafeStoragePathError","httpStatus":null,"severity":"error","filePath":"gitnexus/src/storage/repo-manager.ts","lineNumber":1553,"sourceCode":" *     re-derives storagePath from `findRepo(cwd)` and never trusts\n *     the registry field. But `clean --all` DOES iterate the registry\n *     and trust each entry's stored storagePath (same shape as\n *     `remove`), so this helper must be wired into that loop too.\n *   - An external slot is intentionally not constrained under a checkout. Its\n *     own metadata must bind both the source checkout path and the resolved\n *     storage path before it may be removed.\n *\n * The resolver preserves the legacy local-path allowance while also rejecting\n * foreign or malformed metadata. External slots additionally require metadata\n * ownership so a hand-edited registry cannot redirect a destructive command to\n * an arbitrary directory.\n */\nexport const assertSafeStoragePath = async (entry: RegistryEntry): Promise<void> => {\n  try {\n    await requireDeletableStoragePath(entry);\n  } catch (error) {\n    if (error instanceof StorageDeletionError) {\n      throw new UnsafeStoragePathError(entry, error.expectedStoragePath, error.actualStoragePath);\n    }\n    throw error;\n  }\n};\n\n/**\n * Resolve a user-supplied target string (from `gitnexus remove <target>`\n * or equivalent MCP tool argument) to a single registry entry.\n *\n * Match precedence (first hit wins, subsequent tiers are only tried if\n * the prior tier produces zero matches):\n *   1. Exact resolved-path match (Windows: case-insensitive).\n *      Paths are unique by registry construction, so a path match can\n *      never be ambiguous.\n *   2. Exact `name` match (case-insensitive). If ≥ 2 entries share the\n *      name — only possible via `--allow-duplicate-name` (#829) —\n *      throws {@link RegistryAmbiguousTargetError}.\n *","sourceCodeStart":1535,"sourceCodeEnd":1571,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/ac9a4e9abd8fd3058c070b72c23402a4f887929a/gitnexus/src/storage/repo-manager.ts#L1535-L1571","documentation":"UnsafeStoragePathError is thrown by assertSafeStoragePath when the underlying requireDeletableStoragePath check raises StorageDeletionError. It means the registry entry's on-disk storage slot does not match the expected slot path computed for that repository entry (expectedStoragePath vs actualStoragePath). GitNexus refuses to delete or mutate storage when the resolved path is not exactly the isolated slot it computed, to prevent deleting the wrong directory.","triggerScenarios":"Calling assertSafeStoragePath(entry) (or a deletion flow that uses it) where the RegistryEntry's stored/derived storagePath differs from the canonical expected slot path, e.g. the registry's storagePath was moved, renamed, or populated by an older version of the resolver.","commonSituations":"Manually editing .gitnexus registry rows or moving the .gitnexus directory; a repo re-registered from a different canonical path casing/symlink; an upgrade changed slot naming so legacy rows carry stale paths.","solutions":["Print error.expectedStoragePath and error.actualStoragePath and reconcile the registry entry's storagePath to the expected value (re-run `gitnexus analyze` / re-register the repo).","Verify the on-disk directory at actualStoragePath: if it is the real data, move it to expectedStoragePath rather than editing code.","Re-check symlink/case canonicalization of the repository path with path.resolve(realpathSync(repoPath)) so canonicalRepoPath matches.","As a last resort remove the stale slot and re-index from scratch with `node .gitnexus/run.cjs analyze --index-only`."],"exampleFix":"// before\nawait assertSafeStoragePath({ ...entry, storagePath: '/external/repos/repo-a-abc123' });\n// after\nconst canonical = await canonicalRepoPath(entry.repoPath);\nawait assertSafeStoragePath({ ...entry, storagePath: storagePathFromRoot(storageRoot, canonical) });","handlingStrategy":"try-catch","validationCode":"const expected = storagePathFromRoot(storageRoot, await canonicalRepoPath(entry.repoPath));\nif (entry.storagePath !== undefined && path.resolve(entry.storagePath) !== path.resolve(expected)) {\n  throw new Error(`registry storagePath ${entry.storagePath} != expected ${expected}; re-register repo`);\n}","typeGuard":"const isRegistryEntry = (e: unknown): e is RegistryEntry =>\n  typeof e === 'object' && e !== null && typeof (e as any).repoPath === 'string';","tryCatchPattern":"try {\n  await assertSafeStoragePath(entry);\n} catch (e) {\n  if (e instanceof UnsafeStoragePathError) {\n    console.error(`storage slot mismatch for ${entry.repoPath}: expected ${e.expectedStoragePath}, found ${e.actualStoragePath} — re-register before deleting`);\n  }\n  throw e;\n}","preventionTips":["Never hand-edit .gitnexus registry rows; use the CLI to re-register.","After moving a .gitnexus directory, always re-run analyze to refresh storagePath.","Canonicalize repo paths (realpath) before registering so slot derivation is stable."],"tags":["storage","path-validation","safety-check"],"backgroundTag":"invalid-config-value","analyzedSha":"ac9a4e9abd8fd3058c070b72c23402a4f887929a","analyzedAt":"2026-09-15T23:29:44.066Z","contentChangedAt":"2026-09-15T23:29:44.066Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}