abhigyanpatwari/GitNexus · warning

[cobol-copy-expander] Circular COPY detected: ${cs.target} (

Error message

[cobol-copy-expander] Circular COPY detected: ${cs.target} (${resolvedPath}) includes itself. Skipping expansion.

What it means

During COPY expansion, a copybook resolved to a path already on the active expansion chain (the visited set) — it includes itself, directly or transitively (A COPYs B, B COPYs A). Expansion of that COPY is skipped and the original lines kept; the warning fires once per path per run via the warnedCircular set.

Source

Thrown at gitnexus/src/core/ingestion/cobol/cobol-copy-expander.ts:458

      // Record resolution metadata
      allResolutions.push({
        copyTarget: cs.target,
        resolvedPath,
        line: cs.startLine,
        replacing: cs.replacing,
        library: cs.library,
      });

      // Cannot resolve — keep original lines
      if (resolvedPath === null) {
        continue;
      }

      // Cycle detection
      if (visited.has(resolvedPath)) {
        if (!warnedCircular.has(resolvedPath)) {
          warnedCircular.add(resolvedPath);
          logger.warn(
            `[cobol-copy-expander] Circular COPY detected: ${cs.target} (${resolvedPath}) ` +
              `includes itself. Skipping expansion.`,
          );
        }
        continue;
      }

      // Max depth exceeded — keep unexpanded
      if (depth >= maxDepth) {
        logger.warn(
          `[cobol-copy-expander] Max expansion depth (${maxDepth}) reached for ` +
            `COPY ${cs.target} in ${srcPath}. Skipping expansion.`,
        );
        continue;
      }

      // Guard against exponential breadth amplification (N copybooks each with N COPYs)
      if (++totalExpansions > MAX_TOTAL_EXPANSIONS) {

View on GitHub (pinned to aac7515d2a)

Solutions

  1. Break the cycle: remove the recursive COPY statement from one of the copybooks
  2. Extract the shared fragment into a third, leaf copybook that both A and B COPY
  3. Re-run analysis — the expander keeps original lines so output stays parseable, but verify results for those members

Example fix

* before — CPYB contains: COPY CPYA.  CPYA contains: COPY CPYB.
* after — remove the back-reference from CPYB
       IDENTIFICATION DIVISION.
       ...
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight: DFS the copybook graph and fail on cycles before analysis
function findCopyCycle(adj: Map<string, string[]>): string[] | null {
  const state = new Map<string, 'visiting' | 'done'>();
  const dfs = (node: string, stack: string[]): string[] | null => {
    state.set(node, 'visiting');
    for (const next of adj.get(node) ?? []) {
      if (state.get(next) === 'visiting') return [...stack, next];
      if (!state.has(next)) {
        const cycle = dfs(next, [...stack, next]);
        if (cycle) return cycle;
      }
    }
    state.set(node, 'done');
    return null;
  };
  for (const node of adj.keys()) if (!state.has(node)) {
    const cycle = dfs(node, [node]);
    if (cycle) return cycle;
  }
  return null;
}

Type guard

function isAcyclic(adj: Map<string, string[]>): boolean {
  return findCopyCycle(adj) === null;
}

Prevention

When it happens

Trigger: A copybook chain that loops: copybook A contains COPY B while B contains COPY A, or a copybook COPYs itself.

Common situations: Refactored copybooks that accidentally reference each other; copy-paste introducing a self-COPY. Real COBOL compilers reject such cycles, so this usually surfaces on legacy members that were never compiled as-is.

Related errors


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