abhigyanpatwari/GitNexus · error · Error

DiskBackedScopeTree.byId is unsupported (INGESTION_EMIT_SCOP

Error message

DiskBackedScopeTree.byId is unsupported (INGESTION_EMIT_SCOPES is incompatible with GITNEXUS_DISK_SCOPE_INDEX).

What it means

With GITNEXUS_DISK_SCOPE_INDEX set (default off), scopes are persisted to per-file JSON shards and only a resident skeleton (scopeId → shard/parent/childIds) stays in memory — the design that cuts the ~17-20 GB resident scope payload on kernel-scale repos. Consumers must fetch full Scope objects via getScope(id) point lookups; the byId map, which would materialize every Scope at once, is deliberately unsupported in that mode, so accessing it throws immediately, naming the incompatible flag combination.

Source

Thrown at gitnexus/src/storage/scope-index-store.ts:149

  /** Decoded shards, most-recently-used last (Map preserves insertion order). */
  private readonly lru = new Map<string, Map<ScopeId, Scope>>();

  constructor(
    storagePath: string,
    skeleton: ReadonlyMap<ScopeId, ScopeSkeletonEntry>,
    maxResidentShards = 64,
  ) {
    this.dir = getScopeIndexStoreDir(storagePath);
    this.skeleton = skeleton;
    this.maxResidentShards = Math.max(1, maxResidentShards);
  }

  get size(): number {
    return this.skeleton.size;
  }

  get byId(): ReadonlyMap<ScopeId, Scope> {
    throw new Error(
      'DiskBackedScopeTree.byId is unsupported (INGESTION_EMIT_SCOPES is incompatible with GITNEXUS_DISK_SCOPE_INDEX).',
    );
  }

  has(id: ScopeId): boolean {
    return this.skeleton.has(id);
  }

  getChildren(id: ScopeId): readonly ScopeId[] {
    return this.skeleton.get(id)?.childIds ?? EMPTY;
  }

  getScope(id: ScopeId): Scope | undefined {
    const meta = this.skeleton.get(id);
    if (meta === undefined) return undefined;
    return this.loadShard(meta.shard).get(id);
  }

View on GitHub (pinned to aac7515d2a)

Solutions

  1. Unset GITNEXUS_DISK_SCOPE_INDEX for runs that need the full resident scope map
  2. Refactor the consumer to the supported surface: has(id), getChildren(id), and getScope(id) point lookups
  3. Gate byId access behind a mode check so both storage modes work
  4. Keep INGESTION_EMIT_SCOPES expectations in sync with the storage mode when configuring both

Example fix

// before — throws under GITNEXUS_DISK_SCOPE_INDEX
const all = [...scopeTree.byId.values()];

// after — works in both modes
const ids: ScopeId[] = [];
const walk = (id: ScopeId): void => {
  ids.push(id);
  scopeTree.getChildren(id).forEach(walk);
};
rootIds.forEach(walk);
const all = ids.map((id) => scopeTree.getScope(id)!);
Defensive patterns

Strategy: validation

Validate before calling

if (process.env.GITNEXUS_DISK_SCOPE_INDEX) {
  throw new Error(
    'scopeTree.byId is unavailable under GITNEXUS_DISK_SCOPE_INDEX — use getScope()/getChildren()',
  );
}
const all = [...scopeTree.byId.values()];

Type guard

function supportsById(tree: ScopeTree): boolean {
  return (tree as { dir?: unknown }).dir === undefined; // DiskBackedScopeTree carries .dir
}

Try / catch

try {
  useAllScopes([...scopeTree.byId.values()]);
} catch (e) {
  if (e instanceof Error && e.message.includes('DiskBackedScopeTree.byId')) {
    useAllScopes(allScopeIds().map((id) => scopeTree.getScope(id)!));
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: GITNEXUS_DISK_SCOPE_INDEX is set (memory-constrained indexing of a huge repo) and a code path calls scopeTree.byId expecting the resident Map<ScopeId, Scope> that the default buildScopeTree path provides.

Common situations: Enabling the disk scope index for memory, then running an older tool, plugin, or script that iterates [...tree.byId.values()]; a version upgrade introducing a new byId consumer while the env flag stays set in CI.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@aac7515d2a (2026-08-20). Data as JSON: /api/errors/fcbf86a05595f98b. Report an issue: GitHub.