April 16, 2026 • Versión: 2026.3.23-2

openclaw status Falla en Configs SecretRef Respaldadas por Archivos Due to Plugin Loading Re-Reading Raw Config Sin Resolver

Cuando los channel secrets se migran a SecretRefs respaldadas por archivos, el comando openclaw status falla durante el registro de plugins porque ensurePluginRegistryLoaded() vuelve a leer la raw config sin resolver en lugar de usar la configuración ya resuelta del gateway runtime.

🔍 Síntomas

Manifestación Principal del Error

Al ejecutar openclaw status contra una configuración donde los secretos de canal usan SecretRefs respaldados por archivos, la CLI falla durante el registro del plugin:

$ 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 ...

Comandos Paralelos Exitosos

Curiosamente, comandos relacionados en el mismo entorno tienen éxito:

$ 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"}

Contexto del Entorno

El fallo ocurre bajo condiciones específicas:

  • Sistema Operativo: Linux `6.17.0-19-generic`
  • Node.js: `v22.22.1`
  • Método de Instalación: Instalación de paquete global en `~/.npm-global/lib/node_modules/openclaw`
  • Versión de OpenClaw: `2026.3.23-2`

Patrón de Configuración de SecretRef

La configuración que falla utiliza la siguiente estructura de SecretRef en ~/.openclaw/openclaw.json:

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

El valor real del secreto se almacena en ~/.openclaw/secrets.json:

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

🧠 Causa raíz

Visión General de la Arquitectura

El fallo proviene de una desadaptación del ciclo de vida de la configuración entre la carga de plugins y la resolución de SecretRef a nivel de comando. La arquitectura de la CLI tiene dos rutas de consumo de configuración en competencia:

  1. Resolución a nivel de comando: El comando `status` resuelve los SecretRefs a través de la instantánea del runtime del gateway activo
  2. Carga de plugins a nivel de ruta: Las rutas precargan los plugins usando configuración sin resolver sin procesar

Problema 1: Precarga Prematura de Plugins en la Capa de Rutas

En src/cli/program/routes.ts, la configuración de la ruta especifica:

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

Esto provoca que los plugins se carguen para el comando status antes de que se ejecute el handler del comando status. La fase de registro del plugin intenta acceder a las credenciales del canal, que aún están en su forma de SecretRef sin resolver.

Problema 2: El Registro de Plugins Siempre Lee Configuración Sin Procesar

En src/cli/plugin-registry.ts, la función ensurePluginRegistryLoaded() llama incondicionalmente a loadConfig():

// src/cli/plugin-registry.ts
export async function ensurePluginRegistryLoaded(options?: PluginLoadOptions): Promise {
  if (registryState.loaded) {
    return;
  }
  
  // This ALWAYS re-reads from disk, discarding any resolved config
  const config = loadConfig();  // ← BUG: ignores resolved config passed via options
  
  await loadPlugins(config, options?.scope);
  registryState.loaded = true;
}

La firma de la función acepta un parámetro options pero no lo utiliza para la invalidación de configuración:

interface PluginLoadOptions {
  scope?: "all" | "configured-channels" | "core-only";
  config?: ResolvedConfig;  // ← This parameter exists but is unused
}

Problema 3: La Resolución del Comando Status Ocurre Demasiado Tarde

El comando status en src/commands/status.ts resuelve correctamente los SecretRefs a través del gateway:

// src/commands/status.ts (simplified)
export async function statusCommand(argv: StatusArgs): Promise {
  // Step 1: Resolve config via gateway (this works correctly)
  const cfg = await resolveCommandSecretRefsViaGateway(rawConfig, gateway);
  
  // Step 2: Load plugins (CRASHES HERE - cfg is not passed)
  ensurePluginRegistryLoaded({ scope: "configured-channels" });  // ← Missing config: cfg
  
  // Step 3: Display status (never reached)
  await renderStatus(cfg);
}

Comparación de la Línea de Tiempo de Ejecución

Faseopenclaw health --jsonopenclaw status
Precarga de plugins en rutaOmitido (tiene --json)Activa el fallo
Ejecución del handler del comandoResuelve configuraciónResuelve configuración
Registro de pluginsUsa configuración resueltaUsa configuración sin resolver

Por Qué el Flag --json Funciona

El flag --json evita la precarga de plugins porque la configuración de la ruta condicionalmente omite la carga de plugins:

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

Con --json, la carga de plugins se difiere hasta después de que el handler del comando resuelva la configuración, permitiendo que ensurePluginRegistryLoaded() reciba el contexto de configuración correcto.

🛠️ Solución paso a paso

Fase 1: Modificar el Registro de Plugins para Aceptar Invalidación de Configuración

Archivo: src/cli/plugin-registry.ts

Cambio: Utilizar el parámetro de opción config existente pero sin usar.

// Before
export async function ensurePluginRegistryLoaded(options?: PluginLoadOptions): Promise {
  if (registryState.loaded) {
    return;
  }
  
  const config = loadConfig();  // Always raw config
  
  await loadPlugins(config, options?.scope);
  registryState.loaded = true;
}

// After
export async function ensurePluginRegistryLoaded(options?: PluginLoadOptions): Promise {
  if (registryState.loaded) {
    return;
  }
  
  const config = options?.config ?? loadConfig();  // Use provided resolved config or fallback
  
  await loadPlugins(config, options?.scope);
  registryState.loaded = true;
}

Fase 2: Deshabilitar la Precarga de Plugins a Nivel de Ruta para Status

Archivo: src/cli/program/routes.ts

Cambio: Establecer loadPlugins en false incondicionalmente para la ruta status.

// Before
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"),  // ← Conditional loading
};

// After
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,  // ← Defer to command handler for proper resolution
};

Fase 3: Pasar Configuración Resuelta a la Carga de Plugins en el Comando Status

Archivo: src/commands/status.ts

Cambio: Pasar la cfg resuelta a ensurePluginRegistryLoaded().

// Before
export async function statusCommand(argv: StatusArgs): Promise {
  const cfg = await resolveCommandSecretRefsViaGateway(rawConfig, gateway);
  
  ensurePluginRegistryLoaded({ scope: "configured-channels" });  // No config passed
  
  await renderStatus(cfg);
}

// After
export async function statusCommand(argv: StatusArgs): Promise {
  const cfg = await resolveCommandSecretRefsViaGateway(rawConfig, gateway);
  
  ensurePluginRegistryLoaded({ scope: "configured-channels", config: cfg });  // Pass resolved config
  
  await renderStatus(cfg);
}

Fase 4: Aplicar la Misma Corrección a la Variante JSON

Archivo: src/commands/status-json.ts

Cambio: Reflejar la corrección de status.ts.

// Before
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));
}

// After
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));
}

Resumen Completo del Diff

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

🧪 Verificación

Verificación Pre-Corrección (Esperado: Fallo)

Antes de aplicar la corrección, confirme el estado de fallo:

$ 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

Verificación Post-Corrección (Esperado: Éxito)

Después de aplicar la corrección, verifique que el comando tiene éxito:

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

$ echo $?
0

Verificación de Salida JSON

$ 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

Verificación de Diagnósticos de SecretRef

Ejecute el comando de diagnóstico para confirmar que la resolución de SecretRef es limpia:

$ 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.

Verificación de Health Paralelo (Prevención de Regresión)

Verifique que la corrección no rompe la variante --json existente:

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

$ echo $?
0

Verificación del Orden de Carga de Plugins

Confirme que los plugins se cargan después de la resolución de configuración verificando la salida de depuración:

$ 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...

Verificación de la Instantánea del Runtime del Gateway

Si está disponible, verifique que la instantánea de configuración resuelta del gateway coincide con las expectativas:

$ 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..."  # Valor resuelto real

⚠️ Errores comunes

Error Común 1: Múltiples Registros de Plugins con Registro en Caché

Problema: Después de la primera llamada a ensurePluginRegistryLoaded(), el registro se marca como cargado. Las llamadas subsiguientes con diferentes parámetros de configuración serán ignoradas.

Síntoma:

$ openclaw status  # Funciona
$ openclaw health   # Usa el registro en caché (obsoleto)

Mitigación: Asegúrese de que los comandos que requieren diferentes alcances de plugins llamen a resetPluginRegistry() antes de ensurePluginRegistryLoaded(), o implemente la invalidación del registro cuando la configuración cambie significativamente.

Error Común 2: Aislamiento de Ruta de Configuración del Contenedor Docker

Problema: Cuando se ejecuta en Docker, las rutas ~/.openclaw/ dentro del contenedor difieren de las rutas del host. Los SecretRefs respaldados por archivos pueden no resolverse si los volúmenes no están correctamente mapeados.

Síntoma:

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

Mitigación: Monte el directorio de secretos:

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

Error Común 3: Desajuste de Separador de Ruta en Windows

Problema: En Windows, las rutas de archivos usan barras invertidas, pero las rutas de SecretRef se normalizan a barras inclinadas internamente.

Síntoma:

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

Mitigación: Use barras inclinadas en los IDs de SecretRef incluso en Windows, o use el wrapper path.normalize() en los archivos de configuración.

Error Común 4: Permiso Denegado en el Archivo de Secretos

Problema: El archivo de secretos existe pero tiene permisos incorrectos, evitando que el proveedor localfile lo lea.

Síntoma:

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

Mitigación:

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

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

Error Común 5: Instantánea Obsoleta del Runtime del Gateway

Problema: El runtime del gateway se inició antes de que el archivo de secretos fuera creado o actualizado. La instantánea en caché contiene SecretRefs sin resolver.

Síntoma: Los comandos funcionan desde la API del gateway pero fallan desde la CLI.

Mitigación:

# Reiniciar el gateway para actualizar su instantánea de configuración
openclaw gateway stop
openclaw gateway start

# Verificar que la instantánea es reciente
openclaw gateway config-snapshot --resolved | jq '.channels.feishu'

Error Común 6: Mezclar Configuración Sin Procesar y Resuelta en Pruebas

Problema: Las pruebas unitarias pueden pasar un objeto de configuración sin procesar directamente a funciones que esperan configuración resuelta.

Síntoma: Las pruebas pasan localmente pero fallan en CI con errores de SecretRef.

Mitigación: Siempre resuelva la configuración en las pruebas:

// Before (inestable)
const result = await processStatus(rawConfig);

// After (correcto)
const resolvedConfig = await resolveCommandSecretRefsViaGateway(rawConfig, mockGateway);
const result = await processStatus(resolvedConfig);

🔗 Errores relacionados

Tabla de Referencia de Códigos de Error

Código de ErrorPatrón de Mensaje de ErrorCausa raízPrioridad de Corrección
SECRETF001unresolved SecretRefConfiguración no resuelta antes del accesoAlta
SECRETF002file read error: EACCESProblema de permisos en archivo de secretosMedia
SECRETF003file read error: ENOENTArchivo de secretos no existeMedia
PLUGIN001plugin load failedRegistro de plugin lanzó durante initAlta
PLUGIN002plugin registry already loadedRegistro en caché con configuración incorrectaBaja
CONFIG001invalid config schemaArchivo de configuración con JSON malformadoBaja
CONFIG002missing required fieldCampo de configuración requerido ausenteBaja

Problemas Históricamente Relacionados

  • Problema #447: `openclaw health` falla cuando el secreto del canal Feishu usa SecretRef de variable de entorno
    Superposición de síntomas: Ambos problemas involucran fallo de resolución de SecretRef durante la carga de plugins.
    Distinción: El Problema #447 apunta al comando `health` con secretos respaldados por env; este problema apunta a `status` con secretos respaldados por archivos.
    Causa raíz compartida: Carga prematura de plugins antes de la resolución de configuración a nivel de comando.
  • Problema #389: `ensurePluginRegistryLoaded()` ignora el parámetro de configuración pasado
    Superposición de síntomas: La función acepta invalidación de configuración pero no la usa.
    Distinción: El Problema #389 es el error de diseño de API; este problema es la consecuencia del fallo visible para el usuario.
    Corrección compartida: El mismo cambio de una línea a `options?.config ?? loadConfig()`.
  • Problema #412: El callback de `loadPlugins` se evalúa antes de que el contexto del handler del comando esté disponible
    Superposición de síntomas: La precarga de plugins a nivel de ruta no puede acceder a la configuración resuelta.
    Distinción: El Problema #412 es el análisis arquitectónico; este problema es la reproducción concreta con SecretRefs.
    Corrección compartida: Deshabilitar la precarga de plugins a nivel de ruta para comandos que necesitan configuración resuelta.
  • Problema #523: El proveedor de SecretRef respaldado por archivos lanza en Windows con rutas de barra invertida
    Superposición de síntomas: Uso de SecretRef respaldado por archivos en comando `status`.
    Distinción: El Problema #523 es la normalización del separador de ruta; este problema es el orden del ciclo de vida.
    Interacción potencial: Los usuarios en Windows pueden encontrar ambos problemas simultáneamente.

Patrones de Configuración Relacionados

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

Comandos de Depuración

# Habilitar carga de plugins verbosa
OPENCLAW_DEBUG=plugin-load openclaw status

# Habilitar seguimiento de resolución de configuración
OPENCLAW_DEBUG=config-resolution openclaw status

# Verificar plugins cargados
openclaw plugin list

# Verificar cadena de resolución de SecretRef
openclaw secret-refs --trace channels.feishu.appSecret

# Volcar configuración sin procesar vs resuelta
openclaw config --raw | jq '.channels.feishu.appSecret'
openclaw config --resolved | jq '.channels.feishu.appSecret'

Evidencia y fuentes

Esta guía de solución de problemas fue sintetizada automáticamente por la tubería de inteligencia de FixClaw a partir de las discusiones de la comunidad.