Skip to content

Scoped npm packages from ClawHub fail to install with ENOENT #66618

Description

@saurabhjain1592

Summary

openclaw plugins install <scoped-package> fails with ENOENT for any package whose name contains a slash (i.e., all @scope/name npm-style packages on ClawHub). Non-scoped packages install fine.

Reproduction

openclaw plugins install @axonflow/[email protected]

Output:

Resolving clawhub:@axonflow/[email protected]…
ClawHub code-plugin @axonflow/[email protected] channel=community verification=source-linked
Compatibility: pluginApi=>=2026.3.22 minGateway=2026.3.22
ClawHub package "@axonflow/openclaw" is community; review source and verification before enabling.
ENOENT: no such file or directory, open '/var/folders/.../openclaw-clawhub-package-XXXXXX/@axonflow/openclaw.zip'

For comparison, a non-scoped package installs successfully:

openclaw plugins install mywallet  # works

Root cause

In dist/clawhub-CFvPS51z.js (built file, source is presumably similar):

const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-clawhub-package-"));
const archivePath = path.join(tmpDir, `${params.name}.zip`);
await fs.writeFile(archivePath, bytes);

When params.name is @axonflow/openclaw, path.join resolves archivePath to <tempdir>/@axonflow/openclaw.zip — i.e., a path containing @axonflow/ as a subdirectory. fs.mkdtemp only creates <tempdir>, not the @axonflow/ child. fs.writeFile does not auto-create parent directories, so it throws ENOENT.

The same bug pattern exists in downloadClawHubSkillArchive for skills (line ~232).

Fix

Either:

// Option A: sanitize the filename
const archivePath = path.join(tmpDir, `${params.name.replace(/\//g, '_')}.zip`);

or:

// Option B: ensure parent dir exists before writing
const archivePath = path.join(tmpDir, `${params.name}.zip`);
await fs.mkdir(path.dirname(archivePath), { recursive: true });
await fs.writeFile(archivePath, bytes);

Option B preserves the existing path layout if anything else relies on it.

Impact

This affects every scoped npm package on ClawHub. Any plugin published as @scope/name is currently uninstallable via openclaw plugins install <name>. The package upload, metadata, and zip artifact are all stored correctly on ClawHub — only the install/download path is broken.

Workaround for users

Until fixed:

npm pack @scope/name
openclaw plugins install ./scope-name-VERSION.tgz

Environment

  • OpenClaw: 2026.3.28 (f9b1079)
  • macOS, Node 24
  • ClawHub CLI: v0.9.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions