paperclipai/paperclip · error · Error

Circular workspace dependency while staging ${packageName}.

Error message

Circular workspace dependency while staging ${packageName}.

What it means

Thrown by resolveGitInstallWorkspacePackages (install.ts:65) when the depth-first visit over @paperclipai/* workspace dependencies re-enters a package already on the current visit stack. Detects a dependency cycle in the pnpm workspace graph during a git-based (source checkout) install.

Source

Thrown at cli/src/commands/install.ts:65

    const stderr = error && typeof error === "object" && "stderr" in error && typeof error.stderr === "string"
      ? error.stderr.trim()
      : "";
    if (!stderr || (error instanceof Error && error.message.includes(stderr))) throw error;
    throw new Error(`${error instanceof Error ? error.message : String(error)}\n${stderr}`, { cause: error });
  }
}

export function resolveGitInstallWorkspacePackages(checkoutPath: string): ReleasePackageEntry[] {
  const manifestPath = path.join(checkoutPath, "scripts", "release-package-manifest.json");
  const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")) as ReleasePackageEntry[];
  const packageByName = new Map(manifest.map((entry) => [entry.name, entry]));
  const visiting = new Set<string>();
  const visited = new Set<string>();
  const ordered: ReleasePackageEntry[] = [];

  const visit = (packageName: string): void => {
    if (visited.has(packageName)) return;
    if (visiting.has(packageName)) throw new Error(`Circular workspace dependency while staging ${packageName}.`);
    const entry = packageByName.get(packageName);
    if (!entry) throw new Error(`Git install cannot stage workspace dependency ${packageName}; it is missing from scripts/release-package-manifest.json.`);
    visiting.add(packageName);
    const packageJson = JSON.parse(fs.readFileSync(path.join(checkoutPath, entry.dir, "package.json"), "utf8")) as Record<string, unknown>;
    for (const section of ["dependencies", "optionalDependencies", "peerDependencies"] as const) {
      const dependencies = packageJson[section];
      if (!dependencies || typeof dependencies !== "object") continue;
      for (const dependencyName of Object.keys(dependencies)) {
        if (dependencyName.startsWith("@paperclipai/")) visit(dependencyName);
      }
    }
    visiting.delete(packageName);
    visited.add(packageName);
    ordered.push(entry);
  };

  visit("@paperclipai/server");
  return ordered;

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Break the cycle in the workspace: move shared code into a third package that both depend on.
  2. If the cycle is in optionalDependencies/peerDependencies, re-evaluate whether it is truly needed.
  3. Re-run the install after fixing the graph; verify with `pnpm why @paperclipai/<pkg>`.

Example fix

// before: packages/a package.json
dependencies: { "@paperclipai/b": "workspace:*" }
// packages/b package.json
dependencies: { "@paperclipai/a": "workspace:*" }
// after: extract shared code into @paperclipai/shared and have both depend on it
Defensive patterns

Strategy: validation

Validate before calling

function detectWorkspaceCycle(manifest: ReleasePackageEntry[], checkoutPath: string): string | null {
  const byName = new Map(manifest.map((e) => [e.name, e]));
  const visit = (name: string, stack: string[]): string[] | null => {
    if (stack.includes(name)) return [...stack, name];
    const entry = byName.get(name);
    if (!entry) return null;
    const pkg = JSON.parse(fs.readFileSync(path.join(checkoutPath, entry.dir, 'package.json'), 'utf8'));
    for (const section of ['dependencies', 'optionalDependencies', 'peerDependencies']) {
      const deps = pkg[section];
      if (!deps) continue;
      for (const dep of Object.keys(deps)) {
        if (dep.startsWith('@paperclipai/')) {
          const cycle = visit(dep, [...stack, name]);
          if (cycle) return cycle;
        }
      }
    }
    return null;
  };
  const cycle = visit('@paperclipai/server', []);
  return cycle ? `Cycle: ${cycle.join(' -> ')}` : null;
}

Try / catch

try {
  const ordered = resolveGitInstallWorkspacePackages(checkoutPath);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Circular workspace dependency')) {
    console.error('A workspace dependency cycle was introduced; break it before installing.');
  }
  throw err;
}

Prevention

When it happens

Trigger: Two @paperclipai packages mutually depend on each other (A depends on B, B depends on A), or a longer cycle, while staging workspace packages in topological order. Triggered during `paperclipai install --ref ...` from a git checkout.

Common situations: A recent change added a circular workspace dependency; the manifest-driven installer cannot order such a graph.

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/9af244b872bbfe14. Report an issue: GitHub.