zeroclaw-labs/zeroclaw · error · anyhow::Error

security.estop.require_otp_to_resume=true but security.otp.e

Error message

security.estop.require_otp_to_resume=true but security.otp.enabled=false

What it means

`zeroclaw estop resume` refuses to run when `[security.estop] require_otp_to_resume = true` but `[security.otp] enabled = false`. Without OTP there is no validator to enforce the promised second factor, so ZeroClaw fails fast on the contradictory config instead of resuming unprotected.

Source

Thrown at src/main.rs:6527

        .context("Config path must have a parent directory")?;
    let mut manager = security::EstopManager::load(&config.security.estop, config_dir)?;

    match estop_command {
        Some(EstopSubcommands::Status) => {
            print_estop_status(&manager.status());
            Ok(())
        }
        Some(EstopSubcommands::Resume {
            network,
            domains,
            tools,
            otp,
        }) => {
            let selector = build_resume_selector(network, domains, tools)?;
            let mut otp_code = otp;
            let otp_validator = if config.security.estop.require_otp_to_resume {
                if !config.security.otp.enabled {
                    bail!(
                        "security.estop.require_otp_to_resume=true but security.otp.enabled=false"
                    );
                }
                if otp_code.is_none() {
                    let entered = secret_prompt("Enter OTP code", false)?;
                    if !entered.is_empty() {
                        eprintln!("{}", ta("cli-otp-received", &[], "  ✓ OTP received"));
                    }
                    otp_code = Some(entered);
                }

                let store = security::SecretStore::new(config_dir, config.secrets.encrypt);
                let (validator, enrollment_uri) =
                    security::OtpValidator::from_config(&config.security.otp, config_dir, &store)?;
                if let Some(uri) = enrollment_uri {
                    println!(
                        "{}",
                        t(

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Enable the OTP subsystem: set `enabled = true` under `[security.otp]`, then re-run `zeroclaw estop resume` (the first run prints an enrollment URI)
  2. Or drop the OTP requirement: set `require_otp_to_resume = false` under `[security.estop]`
  3. Keep both flags in one committed config template so they cannot drift apart again

Example fix

# before
[security.estop]
enabled = true
require_otp_to_resume = true
# [security.otp] missing or enabled = false
# after
[security.estop]
enabled = true
require_otp_to_resume = true
[security.otp]
enabled = true
Defensive patterns

Strategy: validation

Validate before calling

r="$(zeroclaw config get security.estop.require_otp_to_resume 2>/dev/null)"
o="$(zeroclaw config get security.otp.enabled 2>/dev/null)"
[ "$r" = "true" ] && [ "$o" != "true" ] && {
  echo "require_otp_to_resume=true but otp.enabled is off"; exit 1;
}

Prevention

When it happens

Trigger: Running `zeroclaw estop resume` (with any selector) while estop.require_otp_to_resume is true and otp.enabled is false — the check fires before the OTP prompt or the secret store are touched.

Common situations: Applying a security-hardening checklist partially (estop flags set, OTP block skipped); templates that ship require_otp_to_resume=true; a config merge that dropped the [security.otp] section.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/93f5eea1567ba38c. Report an issue: GitHub.