conductor-oss/conductor · warning · UnsupportedOperationException

env-backed secrets are read-only

Error message

env-backed secrets are read-only

What it means

Thrown by EnvVariableSecretsDAO.putSecret when an attempt is made to write/create a secret via the env-backed secrets DAO. The env-backed DAO reads secrets exclusively from environment variables (with a configurable prefix like CONDUCTOR_SECRET_) and does not support write operations. This is an UnsupportedOperationException — a permanent limitation of the storage backend, not a transient failure.

Source

Thrown at core/src/main/java/com/netflix/conductor/core/secrets/EnvVariableSecretsDAO.java:71

    @Override
    public String getSecret(String name) {
        return EnvVarLookup.lookup(prefix, name);
    }

    @Override
    public boolean secretExists(String name) {
        return getSecret(name) != null;
    }

    @Override
    public List<String> listSecretNames() {
        return new ArrayList<>(EnvVarLookup.allWithPrefix(prefix).keySet());
    }

    @Override
    public void putSecret(String name, String value) {
        throw new UnsupportedOperationException("env-backed secrets are read-only");
    }

    @Override
    public void deleteSecret(String name) {
        throw new UnsupportedOperationException("env-backed secrets are read-only");
    }

    @Override
    public List<CredentialMeta> listWithMeta() {
        List<CredentialMeta> result = new ArrayList<>();
        EnvVarLookup.allWithPrefix(prefix)
                .forEach((name, value) -> result.add(toMeta(name, value)));
        for (String llmKey : KNOWN_LLM_API_KEYS) {
            String value = EnvVarLookup.lookup("", llmKey);
            if (value != null) {
                result.add(toMeta(llmKey, value));
            }
        }

View on GitHub (pinned to cf7c3e4a8a)

Solutions

  1. Set environment variables with the configured prefix (default CONDUCTOR_SECRET_) to provide secrets instead of writing through the API.
  2. Switch to a writable secrets backend by setting conductor.secrets.type to a supported writable implementation (e.g. database-backed DAO).
  3. If you need both env-sourced and API-managed secrets, configure a secrets backend that supports writes.
  4. Guard UI/API code paths that call putSecret with a capability check before invoking the write.

Example fix

# before — trying to write a secret via API with env backend (default)
curl -X POST http://localhost:8080/api/secrets/MY_KEY -d 'value'

# after — set the env var directly
export CONDUCTOR_SECRET_MY_KEY=value
# or switch backend in application.properties:
# conductor.secrets.type=database
Defensive patterns

Strategy: type-guard

Validate before calling

// Check if the secrets backend supports writes before calling putSecret
if (secretsDAO instanceof EnvVariableSecretsDAO || secretsDAO instanceof NoopSecretsDAO) {
    throw new IllegalStateException(
        "Cannot write secrets with " + secretsDAO.getClass().getSimpleName()
            + " — configure a writable secrets backend");
}
secretsDAO.putSecret(name, value);

Type guard

// Check write capability by testing with a no-op safe probe
public boolean canWriteSecrets(SecretsDAO dao) {
    try {
        // Use a marker that won't harm a real backend
        // For env/noop, this throws UnsupportedOperationException
        return !(dao instanceof EnvVariableSecretsDAO)
            && !(dao instanceof NoopSecretsDAO);
    } catch (Exception e) {
        return false;
    }
}

Try / catch

try {
    secretsDAO.putSecret(name, value);
} catch (UnsupportedOperationException e) {
    // Backend is read-only — guide the user to configure a writable backend
    throw new IllegalStateException(
        "Secrets backend is read-only. Set conductor.secrets.type to a writable backend.", e);
}

Prevention

When it happens

Trigger: Calling the secrets API endpoint (POST /api/secrets/{name}) or any code path that invokes SecretsDAO.putSecret while the 'conductor.secrets.type' property is set to 'env' (which is the default). The env DAO is selected by @ConditionalOnProperty and is active by default.

Common situations: Application tries to write a secret through the API without configuring a writable secrets backend (e.g. database-backed or vault-backed). Default configuration uses env-backed secrets, which is read-only. Developer assumes the secrets API is fully functional for writes but hasn't changed conductor.secrets.type.

Related errors


AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14). Data as JSON: /api/errors/b1a7e441c1669040. Report an issue: GitHub.