can1357/oh-my-pi · error · Error

project-scoped install requires running inside a project dir

Error message

project-scoped install requires running inside a project directory

What it means

MarketplaceManager.#registryPath() returns the file path of the installed-plugins registry for a scope. For the "project" scope it relies on the projectInstalledRegistryPath option, which is only provided when the manager was constructed inside a recognizable project directory. When that option is absent, any project-scoped operation throws this error.

Source

Thrown at packages/coding-agent/src/extensibility/plugins/marketplace/manager.ts:870

		const config = await this.#loadRuntimeConfig(scope);
		delete config.plugins[packageName];
		delete config.settings[packageName];
		await this.#writeRuntimeConfig(scope, config);
	}

	async #setRuntimePluginEnabled(scope: "user" | "project", packageName: string, enabled: boolean): Promise<void> {
		const config = await this.#loadRuntimeConfig(scope);
		const previous = config.plugins[packageName];
		if (!previous) return;

		config.plugins[packageName] = { ...previous, enabled };
		await this.#writeRuntimeConfig(scope, config);
	}

	#registryPath(scope: "user" | "project"): string {
		if (scope === "project") {
			if (!this.#opts.projectInstalledRegistryPath) {
				throw new Error("project-scoped install requires running inside a project directory");
			}
			return this.#opts.projectInstalledRegistryPath;
		}
		return this.#opts.installedRegistryPath;
	}

	async #findInBothRegistries(pluginId: string): Promise<{
		userEntries: InstalledPluginEntry[] | undefined;
		projectEntries: InstalledPluginEntry[] | undefined;
		userReg: InstalledPluginsRegistry;
		projectReg: InstalledPluginsRegistry;
	}> {
		const [userReg, projectReg] = await Promise.all([
			readInstalledPluginsRegistry(this.#opts.installedRegistryPath),
			this.#opts.projectInstalledRegistryPath
				? readInstalledPluginsRegistry(this.#opts.projectInstalledRegistryPath)
				: Promise.resolve({ version: 2 as const, plugins: {} as Record<string, InstalledPluginEntry[]> }),
		]);

View on GitHub (pinned to 9690622007)

Solutions

  1. cd into a project directory (one that yields a project registry path) and retry
  2. Use user scope instead: {scope: "user"}
  3. If embedding the manager, pass projectInstalledRegistryPath in the constructor options

Example fix

// before
await manager.installPlugin("myplugin", "community", { scope: "project" }); // run in ~/
// after
cd ~/my-project   # or:
await manager.installPlugin("myplugin", "community", { scope: "user" });
Defensive patterns

Strategy: fallback

Validate before calling

// ensure a project registry path is available before project-scope ops
const inProject = Boolean(projectRoot && projectInstalledRegistryPath);
const scope = inProject ? 'project' : 'user';

Type guard

null

Try / catch

try {
  await manager.installPlugin(name, marketplace, { scope: 'project' });
} catch (err) {
  if (err.message.includes('requires running inside a project directory')) {
    await manager.installPlugin(name, marketplace, { scope: 'user' });
  } else throw err;
}

Prevention

When it happens

Trigger: Calling installPlugin(name, marketplace, {scope:"project"}), uninstall with project scope, or any code path that resolves the project registry while the manager was constructed without a project directory (e.g. in a home directory, tmp, or SDK embedding without projectInstalledRegistryPath).

Common situations: Running a project-scoped install from outside a git/project root; CI jobs executing in a bare workspace without project detection; embedding the MarketplaceManager programmatically without passing projectInstalledRegistryPath.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/f3841fe1b6d08979. Report an issue: GitHub.