A third-party memory plugin is configured with an exec SecretRef for its API token. The Gateway resolves the reference, the provider authenticates, recall and retain both work. Then you run the status command and see Authentication failed: Invalid API key - yet the command exits 0 and reports the provider as ready (openclaw#172098).
The credential is fine. The diagnostic path is not resolving it.
Why it happens
The runtime and the diagnostics take different routes to the same provider config. The runtime runs the full secret-resolution pass before handing the config to the provider. The status command and the doctor probe take a shortcut - a foreign-provider branch that returns early, or a raw-config handoff - and skip the resolution entirely. The provider receives the unresolved reference string as if it were the literal API key.
Nothing fails loudly when this happens. The plugin's lazy init swallows the auth error - it logs the message and carries on - so the doctor probe reports no findings and the status command still exits 0.
The fix
Every code path that loads a provider's config must run the same secret-resolution pass as the runtime. If the secret cannot be resolved, report the provider as unavailable rather than probing with the raw reference:
// wrong: hands the raw SecretRef to the provider
const config = await loadProviderConfig(slot)
// right: resolve declared secret targets first
const config = await resolveSecretTargets(
await loadProviderConfig(slot),
manifest.secretTargets
)
if (!config.apiToken) return { status: 'unavailable' }
The pattern generalises beyond secrets. If the runtime validates, resolves, or transforms a value before use, the diagnostic path that exercises the same component must do the same - otherwise you are testing a different code path and the failure you see belongs to the harness, not the thing being tested.