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
- Enable the OTP subsystem: set `enabled = true` under `[security.otp]`, then re-run `zeroclaw estop resume` (the first run prints an enrollment URI)
- Or drop the OTP requirement: set `require_otp_to_resume = false` under `[security.estop]`
- 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
- Treat estop and otp flags as one unit in config templates and linters
- Add a config-consistency check to deployment scripts
- Test `zeroclaw estop resume` in staging before relying on it in production
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
- Emergency stop is disabled. Enable [security.estop].enabled
- security.otp.cache_valid_secs must be greater than or equal
- security.otp.gated_actions[{i}] contains invalid characters:
- runtime.wasm.tools_dir must not contain '..' path traversal
- Invalid OTP code; estop resume denied
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/93f5eea1567ba38c.
Report an issue: GitHub.