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/[email protected]
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/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
ENOENTbeendet (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:
- Erstelle ein eindeutiges temporäres Verzeichnis mit
fs.mkdtemp() - Konstruiere den Archivpfad durch Verknüpfung des temporären Verzeichnisses mit dem Paketnamen
- 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:
| Schritt | Operation | Ergebnis |
|---|---|---|
| 1 | path.join(tmpDir, "@axonflow/openclaw.zip") | <tmpDir>/@axonflow/openclaw.zip |
| 2 | path.dirname(archivePath) | <tmpDir>/@axonflow |
| 3 | fs.mkdtemp() erstellt | Nur <tmpDir> |
| 4 | fs.writeFile() versucht zu schreiben | <tmpDir>/@axonflow/openclaw.zip |
| 5 | Übergeordnetes Verzeichnis @axonflow existiert nicht | ENOENT 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:
- Plugin-Installation: Funktion, die
openclaw plugins installbehandelt (ca. Zeile 89) - 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
- Test der Installation eines gescopten Plugins:
$ openclaw plugins install @axonflow/[email protected]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 Downloading package archive… Extracting to plugins directory… Successfully installed plugin “@axonflow/openclaw” (version 1.2.1)
- Überprüfen, ob das Plugin erkannt wird:
$ openclaw plugins list | grep axonflow @axonflow/openclaw 1.2.1 enabled - Test der Installation eines gescopten Skills (falls zutreffend):
$ openclaw skills install @company/[email protected] 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 enabledExit-Code-Verifizierung
$ openclaw plugins install @axonflow/[email protected]
$ echo $?
0Eine 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
/tmpals tmpfs mit begrenztem Speicherplatz. Gescopte Pakete mit großen ZIP-Archiven schlagen möglicherweise mitENOSPCstatt mitENOENTfehl. Ü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/openclawRichtig
openclaw plugins install @axonflow/openclaw
- Groß-/Kleinschreibung: npm-Scopes sind case-sensitive.
@Axonflowund@axonflowsind 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/[email protected]"Ohne Anführungszeichen auf einigen Shells kann @ eine Variablenexpansion auslösen
openclaw plugins install @axonflow/[email protected] # Kann je nach Shell fehlschlagen
Grenzfälle
- Doppel-Slash-Injection: Wenn
params.namefü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