abhigyanpatwari/GitNexus · error · Error

Refusing to register

Error message

Refusing to register ${resolved}: external storage metadata must bind storagePath to the selected directory.

What it means

When an explicit (external) storagePath is selected, registerRepo requires the metadata receipt to bind storagePath to that directory. If meta.storagePath is absent and the storage directory is not the repo's default .gitnexus location, the write is refused — otherwise the registry would claim external storage while the metadata records none, breaking later lookups.

Solutions

  1. Re-run analysis so metadata is written into the selected external storage directory with storagePath recorded.
  2. Point opts.storagePath at the directory the metadata actually names (or the repo's default .gitnexus).
  3. Clear the stale metadata in the external directory and re-analyze.
  4. Keep GITNEXUS_STORAGE_PATH consistent between the analyze and register invocations.

Example fix

// before
await registerRepo(repo, { storagePath: "/external/slot" }); // meta.storagePath undefined
// after
await analyze({ storagePath: "/external/slot" }); // regenerate meta there
await registerRepo(repo, { storagePath: "/external/slot" });
Defensive patterns

Strategy: validation

Validate before calling

const meta = JSON.parse(await readFile(path.join(storagePath, "index-meta.json"), "utf8"));
const isDefault = path.resolve(storagePath) === path.resolve(repoPath, ".gitnexus");
if (!isDefault && (meta.storagePath === undefined || path.resolve(meta.storagePath) !== path.resolve(storagePath))) {
  throw new Error("metadata not bound to selected external storage");
}

Try / catch

try {
  await registerRepo(repoPath, { storagePath });
} catch (err) {
  if (/external storage metadata must bind storagePath/.test(String(err))) {
    await analyze({ repoPath, storagePath }); // regenerate metadata bound to the slot
    await registerRepo(repoPath, { storagePath });
  } else throw err;
}

Prevention

When it happens

Trigger: Calling registerRepo with opts.storagePath set to a non-default directory while the metadata file in that directory has no storagePath field (e.g. metadata created by a default-location analyze).

Common situations: Analyze first ran with default storage (.gitnexus in-repo), then registration is attempted with GITNEXUS_STORAGE_PATH or an explicit storagePath pointing elsewhere; metadata was copied from a default-storage repo into an external slot.

Understand the failure class

Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@ac9a4e9abd (2026-09-15). Data as JSON: /api/errors/21bcf986c716ce0b. Report an issue: GitHub.

Appendix: source

Thrown at gitnexus/src/storage/repo-manager.ts:995

  // Production write paths pass the storage slot they already validated. Do
  // not let a changed environment/registry redirect their registry entry, and
  // require the metadata receipt to describe that same repository and slot.
  // The omitted-option path deliberately retains legacy direct-call behavior.
  if (opts?.storagePath !== undefined) {
    if (!registryPathEquals(canonicalizePath(meta.repoPath), canonicalInput)) {
      throw new Error(
        `Refusing to register ${resolved}: metadata belongs to ${meta.repoPath}, not this repository.`,
      );
    }
    if (
      meta.storagePath === undefined &&
      !registryPathEquals(
        canonicalizePath(storagePath),
        canonicalizePath(defaultStoragePath(resolved)),
      )
    ) {
      throw new Error(
        `Refusing to register ${resolved}: external storage metadata must bind storagePath to the selected directory.`,
      );
    }
    if (
      meta.storagePath !== undefined &&
      !registryPathEquals(canonicalizePath(meta.storagePath), canonicalizePath(storagePath))
    ) {
      throw new Error(
        `Refusing to register ${resolved}: metadata storagePath does not match the selected storage directory.`,
      );
    }
  }

  // Mutating writes must not treat an unreadable/truncated registry as empty
  // (#3094): lenient `readRegistry()` returns `[]` on parse failure and would
  // replace the machine-wide file with only this entry. ENOENT stays empty.
  const entries = await readRegistryStrict();
  const existingIdx = entries.findIndex((e) => {

View on GitHub (pinned to ac9a4e9abd)