April 16, 2026 • Version: 2026.3.28

ClawHub-Bereichspakete scheitern mit ENOENT bei Installation - Scoped npm Packages from ClawHub Fail to Install with ENOENT

Mit npm-Bereichsnamen (@scope/package) veröffentlichte Plugins und Skills werfen während der Installation ENOENT-Fehler, da der temporäre Archivpfad unaufgelöste Verzeichnissegmente enthält.

🔍 Symptome

Primäre Fehleräußerung

Der Versuch, ein beliebiges gescopte npm-Paket von ClawHub zu installieren, führt während der Archiv-Schreibphase zu einem ENOENT-Fehler:

$ openclaw plugins install @axonflow/openclaw@1.2.1

Resolving clawhub:@axonflow/openclaw@1.2.1…
ClawHub code-plugin @axonflow/openclaw@1.2.1 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/ld/8b9xk7n52sg7q5vz7q1l8r840000gn/T/openclaw-clawhub-package-XXXXXX/@axonflow/openclaw.zip'

Diagnostische Beobachtungen

  • Nicht-gescopte Pakete funktionieren: Pakete ohne Scope-Segment werden ohne Fehler installiert
  • $ openclaw plugins install mywallet
    # Output: Successfully installed plugin "mywallet"
  • Fehler tritt versionsübergreifend auf: Alle `@scope/name`-Muster lösen den Fehler aus, unabhängig vom tatsächlichen Scope oder Paketnamen
  • Exit-Code: Prozess wird mit Exit-Code ENOENT beendet (numerisches Äquivalent: 34)

Fehlerobjekt-Details

Das JavaScript-Fehlerobjekt stellt folgende Eigenschaften bereit:

{
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: '/var/folders/.../openclaw-clawhub-package-XXXXXX/@axonflow/openclaw.zip'
}

Die kritische Beobachtung ist, dass der Pfad einen Schrägstrich im Dateinamen-Segment enthält, was die POSIX-Pfadauflösung als Verzeichnistrennzeichen interpretiert.

🧠 Ursache

Architektonische Fehlersequenz

Die Installations-Pipeline in dist/clawhub-CFvPS51z.js folgt dieser Sequenz:

  1. Erstelle ein eindeutiges temporäres Verzeichnis mit fs.mkdtemp()
  2. Konstruiere den Archivpfad durch Verknüpfung des temporären Verzeichnisses mit dem Paketnamen
  3. Schreibe die heruntergeladenen ZIP-Bytes direkt in den konstruierten Pfad

Code-Pfad-Analyse

Der problematische Code (Plugin-Installation, ungefähre Zeile 89):

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);

Das identische Muster existiert im Skills-Installationspfad (downloadClawHubSkillArchive, ungefähre Zeile 232):

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);

Pfadauflösung im Detail

Wenn params.name gleich @axonflow/openclaw ist:

SchrittOperationErgebnis
1path.join(tmpDir, "@axonflow/openclaw.zip")<tmpDir>/@axonflow/openclaw.zip
2path.dirname(archivePath)<tmpDir>/@axonflow
3fs.mkdtemp() erstelltNur <tmpDir>
4fs.writeFile() versucht zu schreiben<tmpDir>/@axonflow/openclaw.zip
5Übergeordnetes Verzeichnis @axonflow existiert nichtENOENT ausgelöst

Warum fs.mkdtemp keine Zwischenverzeichnisse erstellt

fs.mkdtemp(prefix) erstellt genau ein Verzeichnis und gibt dessen absoluten Pfad zurück. Es tut Folgendes nicht:

  • Analysiert das Präfix nach Verzeichnis-Segmenten
  • Erstellt Unterverzeichnisse, die durch die Präfix-Zeichenfolge impliziert werden
  • Validiert, dass der zurückgegebene Pfad mit der Präfix-Struktur übereinstimmt

Daher erzeugt der Aufruf <tmpDir> (z.B. /tmp/openclaw-clawhub-package-abc123), aber der Code versucht anschließend, in <tmpDir>/@axonflow/openclaw.zip zu schreiben, was ein nicht existierendes @axonflow/-Unterverzeichnis erfordert.

npm-Scoping-Konvention

Die @scope/name-Syntax ist die Standard-npm-Konvention für gescopte Pakete. ClawHub unterstützt dieses Namensschema für Plugin- und Skill-Identifikatoren, was diesen Bug zu einem systemischen Blocker für alle Community-Pakete macht, die diese Konvention verwenden.

🛠️ Schritt-für-Schritt-Lösung

Option A: Dateinamen bereinigen (Empfohlen)

Ersetze Schrägstriche im Paketnamen, bevor der Archivpfad konstruiert wird. Dies ist die einfachere Lösung mit minimaler Verhaltensänderung.

Vorher:

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);

Nachher:

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

Dies konvertiert @axonflow/openclaw zu @axonflow_openclaw und erzeugt den gültigen Pfad <tmpDir>/@axonflow_openclaw.zip.

Option B: Sicherstellen, dass das übergeordnete Verzeichnis existiert

Verwende fs.mkdir mit dem recursive-Flag, um alle notwendigen übergeordneten Verzeichnisse vor dem Schreiben zu erstellen. Dies bewahrt die vollständige Verzeichnisstruktur, falls anderer Code davon abhängt.

Vorher:

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);

Nachher:

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

Dies erstellt <tmpDir>/@axonflow/ als Verzeichnis und schreibt dann <tmpDir>/@axonflow/openclaw.zip.

Anwendungsziele

Wenden Sie die Korrektur auf beide Stellen an:

  1. Plugin-Installation: Funktion, die openclaw plugins install behandelt (ca. Zeile 89)
  2. Skill-Installation: downloadClawHubSkillArchive-Funktion (ca. Zeile 232)

Build und Deployment

Nachdem die TypeScript/JavaScript-Quelldateien modifiziert wurden:

# Das betroffene Modul neu bauen
npm run build -- --filter=clawhub

# Oder alle Pakete neu bauen, wenn das Build-System dies erfordert
npm run build

# Tests ausführen, um sicherzustellen, dass keine Regressionen auftreten
npm test -- --grep "clawhub"

🧪 Verifizierung

Funktionelle Verifizierungsschritte

  1. Test der Installation eines gescopten Plugins:
    $ openclaw plugins install @axonflow/openclaw@1.2.1
    

    Resolving clawhub:@axonflow/openclaw@1.2.1… ClawHub code-plugin @axonflow/openclaw@1.2.1 channel=community verification=source-linked Compatibility: pluginApi=>=2026.3.22 minGateway=>=2026.3.22 Downloading package archive… Extracting to plugins directory… Successfully installed plugin “@axonflow/openclaw” (version 1.2.1)

  2. Überprüfen, ob das Plugin erkannt wird:
    $ openclaw plugins list | grep axonflow
    @axonflow/openclaw  1.2.1  enabled
  3. Test der Installation eines gescopten Skills (falls zutreffend):
    $ openclaw skills install @company/enterprise-skill@2.0.0
    Successfully installed skill "@company/enterprise-skill" (version 2.0.0)

Regressionstests

Bestätigen, dass nicht-gescopte Pakete weiterhin funktionieren:

$ openclaw plugins install mywallet
$ openclaw plugins list | grep mywallet
mywallet  1.5.0  enabled

$ openclaw plugins install unpkg-test
$ openclaw plugins list | grep unpkg-test
unpkg-test  0.1.0  enabled

Exit-Code-Verifizierung

$ openclaw plugins install @axonflow/openclaw@1.2.1
$ echo $?
0

Eine erfolgreiche Installation gibt den Exit-Code 0 zurück. Der zuvor fehlschlagende ENOENT hätte einen Nicht-Null-Exit-Code zurückgegeben.

Validierung durch Komponententests

Wenn die Codebasis Komponententests für die ClawHub-Integration enthält:

$ npm test -- --grep "downloadClawHubPlugin"
  downloadClawHubPlugin
    ✓ should handle scoped package names (@scope/name)
    ✓ should handle unscoped package names
    ✓ should handle version specifiers

$ npm test -- --grep "downloadClawHubSkillArchive"
  downloadClawHubSkillArchive
    ✓ should handle scoped skill names
    ✓ should handle nested scope names (@org/team/skill)

⚠️ Häufige Fehler

Umgebungsspezifische Fallen

  • Docker-Container mit tmpfs: Einige Docker-Konfigurationen mounten /tmp als tmpfs mit begrenztem Speicherplatz. Gescopte Pakete mit großen ZIP-Archiven schlagen möglicherweise mit ENOSPC statt mit ENOENT fehl. Überprüfen Sie die tmpfs-Größenallokation:
    df -h /tmp
    # Sicherstellen, dass ausreichend Platz für Plugin-Archive vorhanden ist (Standard-tmpfs oft 64MB)
  • Windows-Pfadtrennzeichen: Obwohl der Bug technisch plattformunabhängig ist, können Windows-Pfade ein unterschiedliches Verhalten bei @-Zeichen in Pfaden aufweisen. Vermeiden Sie Pfade, die @ in sicherheitsrelevanten Kontexten enthalten.
  • Symbolische Links in tmp: Wenn os.tmpdir() zu einem symlink-aufgelösten Pfad aufgelöst wird (üblich in Entwicklungsumgebungen), stellen Sie sicher, dass das Ziel die Verzeichniserstellung unterstützt.

Benutzer-Fehlkonfigurationen

  • Falsches Paketnamensformat: Benutzer lassen manchmal den Scope weg, wenn dieser existiert:
    # Falsch
    openclaw plugins install axonflow/openclaw
    

    Richtig

    openclaw plugins install @axonflow/openclaw

  • Groß-/Kleinschreibung: npm-Scopes sind case-sensitive. @Axonflow und @axonflow sind unterschiedliche Scopes.
  • Version festgelegt, aber Scope fehlt: Wenn Versionen angegeben werden, stellen Sie sicher, dass der gesamte Paketbezeichner in Anführungszeichen steht, um Shell-Interpretation zu verhindern:
    # Anführungszeichen verhindern Shell-Expansionsprobleme
    openclaw plugins install "@axonflow/openclaw@1.2.1"
    

    Ohne Anführungszeichen auf einigen Shells kann @ eine Variablenexpansion auslösen

    openclaw plugins install @axonflow/openclaw@1.2.1 # Kann je nach Shell fehlschlagen

Grenzfälle

  • Doppel-Slash-Injection: Wenn params.name führende oder nachfolgende Schrägstriche enthält, sollte das Bereinigungs-Regex diese behandeln. Option A erweitern:
    const safeName = params.name.replace(/\//g, '_').replace(/^_+|_+$/g, '');
  • Unicode und Emoji in Paketnamen: Obwohl dies keine Standard-npm-Praxis ist, erlauben einige Registries Nicht-ASCII-Zeichen. Testen Sie mit internationalisierten Paketnamen, falls unterstützt.
  • Extrem lange Paketnamen: npm begrenzt Paketnamen auf 214 Zeichen. Sehr lange gescopte Namen können bei einigen Plattformen an die Dateisystem-Pfadlängenlimits heranreichen (typischerweise 255 Bytes pro Pfadkomponente).

Bereinigung temporärer Verzeichnisse

Die von fs.mkdtemp erstellten temporären Verzeichnisse werden bei Prozessabsturz oder SIGKILL nicht automatisch bereinigt. Erwägen Sie, Bereinigungshandler hinzuzufügen:

process.on('SIGINT', () => {
  fs.rm(tmpDir, { recursive: true, force: true }).catch(() => {});
  process.exit(1);
});

🔗 Zugehörige Fehler

Direkt zugehörig

  • ENOENT

Belege & Quellen

Diese Troubleshooting-Anleitung wurde automatisch von der FixClaw Intelligence Pipeline aus Community-Diskussionen synthetisiert.