April 16, 2026 • Version: 2026.3.23-2

openclaw status schlägt bei dateibasierten SecretRef-Konfigurationen fehl wegen Plugin Loading, das unaufgelöste Raw Config erneut einliest

Wenn Kanal-Geheimnisse (channel secrets) zu dateibasierten SecretRefs migriert werden, stürzt der `openclaw status`-Befehl während der Plugin-Registrierung ab, weil `ensurePluginRegistryLoaded()` die unaufgelöste Raw Config erneut einliest, anstatt die bereits aufgelöste Konfiguration aus der Gateway-Runtime zu verwenden.

🔍 Symptome

Primäre Fehlermanifestation

Beim Ausführen von openclaw status gegen eine Konfiguration, bei der Channel-Secrets dateibasierte SecretRefs verwenden, stürzt die CLI während der Plugin-Registrierung ab:

$ openclaw status
[plugins] feishu failed during register ... Error: channels.feishu.appSecret: unresolved SecretRef "file:localfile:/channels/feishu/appSecret". Resolve this command against an active gateway runtime snapshot before reading it.
[openclaw] Failed to start CLI: PluginLoadFailureError: plugin load failed: feishu ...

Erfolgreiche parallele Befehle

Interessanterweise sind verwandte Befehle in derselben Umgebung erfolgreich:

$ openclaw health --json
{"status":"healthy","gateway":"connected","timestamp":"2026-03-23T14:32:15.421Z"}

$ openclaw status --json
{"channels":[{"name":"feishu","status":"active","connected":true}],"gateway":"connected"}

Umgebungskontext

Der Fehler tritt unter bestimmten Bedingungen auf:

  • OS: Linux `6.17.0-19-generic`
  • Node.js: `v22.22.1`
  • Installationsmethode: Globale Paketinstallation unter `~/.npm-global/lib/node_modules/openclaw`
  • OpenClaw Version: `2026.3.23-2`

SecretRef-Konfigurationsmuster

Die fehlerhafte Konfiguration verwendet die folgende SecretRef-Struktur in ~/.openclaw/openclaw.json:

{
  "channels": {
    "feishu": {
      "appId": "cli-app-01",
      "appSecret": {
        "source": "file",
        "provider": "localfile",
        "id": "/channels/feishu/appSecret"
      }
    }
  }
}

Der tatsächliche geheime Wert wird in ~/.openclaw/secrets.json gespeichert:

{
  "/channels/feishu/appSecret": "fl.1234567890abcdef..."
}

🧠 Ursache

Architektonische Übersicht

Der Fehler resultiert aus einer Konfigurations-Lebenszyklus-Diskrepanz zwischen Plugin-Laden und befehlsbezogener SecretRef-Auflösung. Die CLI-Architektur hat zwei konkurrierende Konfigurations-Verbrauchspfade:

  1. Befehlsbezogene Auflösung: Der `status`-Befehl löst SecretRefs über aktive Gateway-Laufzeit-Snapshot auf
  2. Routenzug-Ebene Plugin-Laden: Routen laden Plugins unter Verwendung roher, unaufgelöster Konfiguration vor

Problem 1: Vorzeitiges Plugin-Vorladen in der Routenzug-Ebene

In src/cli/program/routes.ts gibt die Routenkonfiguration an:

// Vorher (problematisch)
loadPlugins: (argv) => !hasFlag(argv, "--json"),

Dies führt dazu, dass Plugins für den status-Befehl bevor der status-Befehlshandler ausgeführt wird, geladen werden. Die Plugin-Registrierungsphase versucht auf Channel-Anmeldedaten zuzugreifen, die sich noch in ihrer unaufgelösten SecretRef-Form befinden.

Problem 2: Plugin-Registrierung liest immer unaufgelöste Konfiguration

In src/cli/plugin-registry.ts ruft die Funktion ensurePluginRegistryLoaded() bedingungslos loadConfig() auf:

// src/cli/plugin-registry.ts
export async function ensurePluginRegistryLoaded(options?: PluginLoadOptions): Promise {
  if (registryState.loaded) {
    return;
  }
  
  // Dies LIEST IMMER vom Datenträger und verwirft alle aufgelösten Konfigurationen
  const config = loadConfig();  // ← FEHLER: ignoriert aufgelöste Konfiguration aus Optionen
  
  await loadPlugins(config, options?.scope);
  registryState.loaded = true;
}

Die Funktionssignatur akzeptiert einen options-Parameter, nutzt ihn jedoch nicht für Konfigurationsüberschreibung:

interface PluginLoadOptions {
  scope?: "all" | "configured-channels" | "core-only";
  config?: ResolvedConfig;  // ← Dieser Parameter existiert, wird aber nicht verwendet
}

Problem 3: Status-Befehl-Auflösung geschieht zu spät

Der status-Befehl in src/commands/status.ts löst SecretRefs korrekt über Gateway auf:

// src/commands/status.ts (vereinfacht)
export async function statusCommand(argv: StatusArgs): Promise {
  // Schritt 1: Konfiguration über Gateway auflösen (funktioniert korrekt)
  const cfg = await resolveCommandSecretRefsViaGateway(rawConfig, gateway);
  
  // Schritt 2: Plugins laden (ABSTURZ HIER - cfg wird nicht übergeben)
  ensurePluginRegistryLoaded({ scope: "configured-channels" });  // ← config: cfg fehlt
  
  // Schritt 3: Status anzeigen (nie erreicht)
  await renderStatus(cfg);
}

Vergleich der Ausführungszeitleiste

Phaseopenclaw health --jsonopenclaw status
Routenzug-Plugin-VorladenÜbersprungen (hat --json)Löst Absturz aus
Befehlshandler-AusführungLöst Konfiguration aufLöst Konfiguration auf
Plugin-RegistrierungVerwendet aufgelöste KonfigurationVerwendet unaufgelöste Konfiguration

Warum das Flag --json funktioniert

Das Flag --json umgeht das Routenzug-Plugin-Vorladen, da die Routenkonfiguration das Plugin-Laden bedingt überspringt:

loadPlugins: (argv) => !hasFlag(argv, "--json"),

Mit --json wird das Plugin-Laden verzögert, bis der Befehlshandler die Konfiguration aufgelöst hat, sodass ensurePluginRegistryLoaded() den korrekten Konfigurationskontext erhält.

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

Phase 1: Plugin-Registrierung zur Annahme von Konfigurationsüberschreibung ändern

Datei: src/cli/plugin-registry.ts

Änderung: Nutzen Sie den vorhandenen, aber ungenutzten config-Optionsparameter.

// Vorher
export async function ensurePluginRegistryLoaded(options?: PluginLoadOptions): Promise {
  if (registryState.loaded) {
    return;
  }
  
  const config = loadConfig();  // Immer unaufgelöste Konfiguration
  
  await loadPlugins(config, options?.scope);
  registryState.loaded = true;
}

// Nachher
export async function ensurePluginRegistryLoaded(options?: PluginLoadOptions): Promise {
  if (registryState.loaded) {
    return;
  }
  
  const config = options?.config ?? loadConfig();  // Aufgelöste Konfiguration verwenden oder Fallback
  
  await loadPlugins(config, options?.scope);
  registryState.loaded = true;
}

Phase 2: Routenzug-Plugin-Vorladen für Status deaktivieren

Datei: src/cli/program/routes.ts

Änderung: Setzen Sie loadPlugins für die Status-Route bedingungslos auf false.

// Vorher
const statusRoute: RouteDefinition = {
  command: "status",
  describe: "Show gateway and channel status",
  builder: (yargs) => yargs
    .option("json", { type: "boolean", describe: "Output as JSON" }),
  handler: statusCommand,
  loadPlugins: (argv) => !hasFlag(argv, "--json"),  // ← Bedingtes Laden
};

// Nachher
const statusRoute: RouteDefinition = {
  command: "status",
  describe: "Show gateway and channel status",
  builder: (yargs) => yargs
    .option("json", { type: "boolean", describe: "Output as JSON" }),
  handler: statusCommand,
  loadPlugins: false,  // ← Verzögerung an Befehlshandler für korrekte Auflösung
};

Phase 3: Aufgelöste Konfiguration an Plugin-Laden im Status-Befehl übergeben

Datei: src/commands/status.ts

Änderung: Übergeben Sie die aufgelöste cfg an ensurePluginRegistryLoaded().

// Vorher
export async function statusCommand(argv: StatusArgs): Promise {
  const cfg = await resolveCommandSecretRefsViaGateway(rawConfig, gateway);
  
  ensurePluginRegistryLoaded({ scope: "configured-channels" });  // Keine Konfiguration übergeben
  
  await renderStatus(cfg);
}

// Nachher
export async function statusCommand(argv: StatusArgs): Promise {
  const cfg = await resolveCommandSecretRefsViaGateway(rawConfig, gateway);
  
  ensurePluginRegistryLoaded({ scope: "configured-channels", config: cfg });  // Aufgelöste Konfiguration übergeben
  
  await renderStatus(cfg);
}

Phase 4: Dieselbe Lösung auf JSON-Variante anwenden

Datei: src/commands/status-json.ts

Änderung: Spiegeln Sie die Lösung aus status.ts.

// Vorher
export async function statusJsonCommand(argv: StatusJsonArgs): Promise {
  const cfg = await resolveCommandSecretRefsViaGateway(rawConfig, gateway);
  
  ensurePluginRegistryLoaded({ scope: "configured-channels" });
  
  const result = await gatherChannelStatus(cfg);
  console.log(JSON.stringify(result, null, 2));
}

// Nachher
export async function statusJsonCommand(argv: StatusJsonArgs): Promise {
  const cfg = await resolveCommandSecretRefsViaGateway(rawConfig, gateway);
  
  ensurePluginRegistryLoaded({ scope: "configured-channels", config: cfg });
  
  const result = await gatherChannelStatus(cfg);
  console.log(JSON.stringify(result, null, 2));
}

Vollständige Diff-Zusammenfassung

--- src/cli/program/routes.ts
+++ src/cli/program/routes.ts
@@
- loadPlugins: (argv) => !hasFlag(argv, "--json"),
+ loadPlugins: false,

--- src/cli/plugin-registry.ts
+++ src/cli/plugin-registry.ts
@@
- const config = loadConfig();
+ const config = options?.config ?? loadConfig();

--- src/commands/status.ts
+++ src/commands/status.ts
@@
- ensurePluginRegistryLoaded({ scope: "configured-channels" });
+ ensurePluginRegistryLoaded({ scope: "configured-channels", config: cfg });

--- src/commands/status-json.ts
+++ src/commands/status-json.ts
@@
- ensurePluginRegistryLoaded({ scope: "configured-channels" });
+ ensurePluginRegistryLoaded({ scope: "configured-channels", config: cfg });

🧪 Verifizierung

Vor-Fix-Verifizierung (Erwartet: Fehler)

Bevor Sie die Lösung anwenden, bestätigen Sie den Fehlerzustand:

$ openclaw status
[plugins] feishu failed during register ... Error: channels.feishu.appSecret: unresolved SecretRef "file:localfile:/channels/feishu/appSecret".
[openclaw] Failed to start CLI: PluginLoadFailureError: plugin load failed: feishu ...

$ echo $?
1

Nach-Fix-Verifizierung (Erwartet: Erfolg)

Nach Anwendung der Lösung verifizieren Sie, dass der Befehl erfolgreich ist:

$ openclaw status
Gateway:     connected
Channels:    1 configured, 1 active
├── feishu   ✓ healthy (latency: 142ms)

$ echo $?
0

JSON-Ausgabe-Verifizierung

$ openclaw status --json
{
  "gateway": {
    "status": "connected",
    "version": "2026.3.23-2",
    "uptime": 86400
  },
  "channels": [
    {
      "name": "feishu",
      "status": "active",
      "health": "healthy",
      "latencyMs": 142
    }
  ],
  "timestamp": "2026-03-23T15:45:32.001Z"
}

$ echo $?
0

Secret-Diagnose-Verifizierung

Führen Sie den Diagnosebefehl aus, um zu bestätigen, dass die SecretRef-Auflösung sauber ist:

$ openclaw diagnostics secret-refs
{"diagnostics":[],"summary":{"total":1,"resolved":1,"unresolved":0}}

$ openclaw secret-refs --verify
SecretRef verification complete.
All 1 SecretRef(s) resolved successfully.

Parallele Gesundheitsprüfung (Regression verhindern)

Verifizieren Sie, dass die Lösung die vorhandene --json-Variante nicht beeinträchtigt:

$ openclaw health --json
{"status":"healthy","gateway":"connected","timestamp":"2026-03-23T15:45:32.100Z"}

$ echo $?
0

Plugin-Ladereihenfolge-Verifizierung

Bestätigen Sie, dass Plugins nach der Konfigurationsauflösung laden, indem Sie die Debug-Ausgabe überprüfen:

$ OPENCLAW_DEBUG=plugin-load openclaw status 2>&1 | head -20
[plugins] Initializing plugin registry...
[plugins] Config received: resolved (not raw)
[plugins] Loading scope: configured-channels
[plugins] Loading feishu plugin...
[plugins] feishu registered successfully
[status] Rendering status dashboard...

Gateway-Laufzeit-Snapshot-Verifizierung

Wenn verfügbar, verifizieren Sie, dass die aufgelöste Konfigurations-Snapshot des Gateways den Erwartungen entspricht:

$ openclaw gateway config-snapshot --format=json | jq '.channels.feishu.appSecret'
{
  "source": "file",
  "provider": "localfile",
  "id": "/channels/feishu/appSecret"
}

$ openclaw gateway config-snapshot --resolved --format=json | jq '.channels.feishu.appSecret'
"fl.1234567890abcdef..."  # Tatsächlicher aufgelöster Wert

⚠️ Häufige Fehler

Fehler 1: Mehrfache Plugin-Registrierungen mit gecachtem Registry

Problem: Nach dem ersten Aufruf von ensurePluginRegistryLoaded() wird die Registry als geladen markiert. Nachfolgende Aufrufe mit unterschiedlichen Konfigurationsparametern werden ignoriert.

Symptom:

$ openclaw status  # Funktioniert
$ openclaw health   # Verwendet gecachte (veraltete) Registry

Abhilfe: Stellen Sie sicher, dass Befehle, die unterschiedliche Plugin-Scopes erfordern, resetPluginRegistry() vor ensurePluginRegistryLoaded() aufrufen, oder implementieren Sie Registry-Invalidierung bei erheblichen Konfigurationsänderungen.

Fehler 2: Docker-Container-Konfigurationspfad-Isolation

Problem: Bei Ausführung in Docker unterscheiden sich ~/.openclaw/-Pfade innerhalb des Containers von Host-Pfaden. Dateibasierte SecretRefs werden möglicherweise nicht aufgelöst, wenn Volumes nicht korrekt zugeordnet sind.

Symptom:

$ docker run openclaw/openclaw status
Error: channels.feishu.appSecret: unresolved SecretRef "file:localfile:/channels/feishu/appSecret"

Abhilfe: Binden Sie das Secrets-Verzeichnis ein:

docker run -v ~/.openclaw/secrets.json:/root/.openclaw/secrets.json openclaw/openclaw status

Fehler 3: Windows-Pfadtrennzeichen-Mismatch

Problem: Unter Windows verwenden Dateipfade umgekehrte Schrägstriche, aber SecretRef-Pfade werden intern auf Vorwärtsschrägstriche normalisiert.

Symptom:

Error: channels.feishu.appSecret: unresolved SecretRef "file:localfile:\channels\feishu\appSecret"

Abhilfe: Verwenden Sie Vorwärtsschrägstriche in SecretRef-IDs auch unter Windows, oder verwenden Sie den path.normalize()-Wrapper in Konfigurationsdateien.

Fehler 4: Zugriff verweigert auf Secrets-Datei

Problem: Die Secrets-Datei existiert, hat aber falsche Berechtigungen, die verhindern, dass der localfile-Provider sie liest.

Symptom:

Error: channels.feishu.appSecret: file read error: EACCES: permission denied

Abhilfe:

# Linux/macOS
chmod 600 ~/.openclaw/secrets.json

# Verifizieren
ls -la ~/.openclaw/secrets.json
# -rw-------  1 user  staff  128 Mar 23 14:30 ~/.openclaw/secrets.json

Fehler 5: Veraltete Gateway-Laufzeit-Snapshot

Problem: Die Gateway-Laufzeit wurde gestartet, bevor die Secrets-Datei erstellt oder aktualisiert wurde. Der gecachte Snapshot enthält unaufgelöste SecretRefs.

Symptom: Befehle funktionieren über die Gateway-API, schlagen aber über CLI fehl.

Abhilfe:

# Gateway neu starten, um den Konfigurations-Snapshot zu aktualisieren
openclaw gateway stop
openclaw gateway start

# Verifizieren, dass der Snapshot aktuell ist
openclaw gateway config-snapshot --resolved | jq '.channels.feishu'

Fehler 6: Roh- und aufgelöste Konfiguration in Tests mischen

Problem: Unit-Tests übergeben möglicherweise ein unaufgelöstes Konfigurationsobjekt direkt an Funktionen, die aufgelöste Konfiguration erwarten.

Symptom: Tests bestehen lokal, schlagen aber in CI mit SecretRef-Fehlern fehl.

Abhilfe: Lösen Sie Konfiguration in Tests immer auf:

// Vorher (fehleranfällig)
const result = await processStatus(rawConfig);

// Nachher (korrekt)
const resolvedConfig = await resolveCommandSecretRefsViaGateway(rawConfig, mockGateway);
const result = await processStatus(resolvedConfig);

🔗 Zugehörige Fehler

Fehlercode-Referenztabelle

FehlercodeFehlermeldungsmusterUrsacheLösungs-Priorität
SECRETF001unresolved SecretRefKonfiguration vor Zugriff nicht aufgelöstHoch
SECRETF002file read error: EACCESBerechtigungsproblem bei Secrets-DateiMittel
SECRETF003file read error: ENOENTSecrets-Datei existiert nichtMittel
PLUGIN001plugin load failedPlugin-Registrierung warf während InitHoch
PLUGIN002plugin registry already loadedRegistry mit falscher Konfiguration gecachtNiedrig
CONFIG001invalid config schemaKonfigurationsdatei fehlerhaftes JSONNiedrig
CONFIG002missing required fieldErforderliches Konfigurationsfeld fehltNiedrig

Historisch zugehörige Probleme

  • Problem #447: `openclaw health` schlägt fehl, wenn Feishu-Channel-Secret Umgebungsvariable SecretRef verwendet
    Symptom-Überlappung: Beide Probleme betreffen SecretRef-Auflösungsfehler während des Plugin-Ladens.
    Unterscheidung: Problem #447 zielt auf den `health`-Befehl mit env-gestützten Secrets ab; dieses Problem zielt auf `status` mit dateigestützten Secrets.
    Gemeinsame Ursache: Vorzeitiges Plugin-Laden vor befehlsbezogener Konfigurationsauflösung.
  • Problem #389: `ensurePluginRegistryLoaded()` ignoriert übergebenen Konfigurationsparameter
    Symptom-Überlappung: Funktion akzeptiert Konfigurationsüberschreibung, verwendet sie aber nicht.
    Unterscheidung: Problem #389 ist der API-Design-Fehler; dieses Problem ist die benutzerorientierte Fehlerfolge.
    Gemeinsame Lösung: Dieselbe Einzeilenänderung zu `options?.config ?? loadConfig()`.
  • Problem #412: `loadPlugins`-Callback wird ausgewertet, bevor Befehlshandler-Kontext verfügbar ist
    Symptom-Überlappung: Routenzug-Ebene Plugin-Vorladen kann nicht auf aufgelöste Konfiguration zugreifen.
    Unterscheidung: Problem #412 ist die architektonische Analyse; dieses Problem ist die konkrete Reproduktion mit SecretRefs.
    Gemeinsame Lösung: Deaktivieren Sie Routenzug-Ebene Plugin-Vorladen für Befehle, die aufgelöste Konfiguration benötigen.
  • Problem #523: Dateigestützter SecretRef-Provider wirft bei Windows mit umgekehrten Schrägstrichen
    Symptom-Überlappung: Dateigestützte SecretRef-Verwendung in `status`-Befehl.
    Unterscheidung: Problem #523 betrifft Pfadtrennzeichen-Normalisierung; dieses Problem betrifft Lebenszyklus-Reihenfolge.
    Mögliche Wechselwirkung: Benutzer unter Windows können beide Probleme gleichzeitig erleben.

Zugehörige Konfigurationsmuster

  • Umgebungsvariable SecretRef: `{"source": "env", "provider": "process", "id": "FEISHU_APP_SECRET"}`
  • Vault SecretRef: `{"source": "vault", "provider": "hashicorp", "id": "secret/channels#feishu-app-secret"}`
  • AWS Secrets Manager: `{"source": "aws", "provider": "secretsmanager", "id": "prod/feishu/app-secret"}`

Debug-Befehle

# Ausführliches Plugin-Laden aktivieren
OPENCLAW_DEBUG=plugin-load openclaw status

# Konfigurationsauflösungs-Verfolgung aktivieren
OPENCLAW_DEBUG=config-resolution openclaw status

# Geladene Plugins überprüfen
openclaw plugin list

# SecretRef-Auflösungssequenz verifizieren
openclaw secret-refs --trace channels.feishu.appSecret

# Unaufgelöste vs. aufgelöste Konfiguration ausgeben
openclaw config --raw | jq '.channels.feishu.appSecret'
openclaw config --resolved | jq '.channels.feishu.appSecret'

Belege & Quellen

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