zeroclaw-labs/zeroclaw · error
cloud_ops.iac_tools must not be empty when cloud_ops is enab
Error message
cloud_ops.iac_tools must not be empty when cloud_ops is enabled
What it means
ZeroClaw rejects a config in which the [cloud_ops] section is enabled but declares no infrastructure-as-code tools. The check runs at the end of CloudOpsConfig::validate() (after default_cloud, supported_clouds, and cost-threshold guards) because an enabled cloud_ops deployment is expected to manage at least one IaC tool such as terraform or pulumi. Fresh configs inherit a non-empty default list (default_cloud_ops_iac_tools()), so hitting this almost always means an explicit `iac_tools = []` or a config layer that cleared the list.
Source
Thrown at crates/zeroclaw-config/src/schema.rs:18600
"cloud_ops.supported_clouds[{i}] must not be empty"
);
}
}
if !self.supported_clouds.contains(&self.default_cloud) {
anyhow::bail!(
"cloud_ops.default_cloud '{}' is not in cloud_ops.supported_clouds {:?}",
self.default_cloud,
self.supported_clouds
);
}
if self.cost_threshold_monthly_usd < 0.0 {
anyhow::bail!(
"cloud_ops.cost_threshold_monthly_usd must be non-negative, got {}",
self.cost_threshold_monthly_usd
);
}
if self.iac_tools.is_empty() {
anyhow::bail!("cloud_ops.iac_tools must not be empty when cloud_ops is enabled");
}
}
Ok(())
}
}
fn default_cloud_ops_cloud() -> String {
"aws".into()
}
fn default_cloud_ops_supported_clouds() -> Vec<String> {
vec!["aws".into(), "azure".into(), "gcp".into()]
}
fn default_cloud_ops_iac_tools() -> Vec<String> {
vec!["terraform".into()]
}
View on GitHub (pinned to 88bb9c8533)
Solutions
- Set at least one tool identifier: `iac_tools = ["terraform"]` under [cloud_ops]
- If you do not use IaC tooling, set `[cloud_ops] enabled = false` so the whole guard block is skipped
- Audit config layers (base file, profiles, overlays, ZEROCLAW_* env mirrors) for a layer that overrides iac_tools with an empty list after the base sets it
- While fixing, verify default_cloud, supported_clouds, and cost_threshold_monthly_usd too — those guards run first and often fail together once cloud_ops is enabled
Example fix
# before [cloud_ops] enabled = true iac_tools = [] # after [cloud_ops] enabled = true iac_tools = ["terraform", "pulumi"]
Defensive patterns
Strategy: validation
Validate before calling
fn cloud_ops_precheck(cfg: &zeroclaw_config::Config) -> Result<(), String> {
let ops = &cfg.cloud_ops;
if ops.enabled && ops.iac_tools.is_empty() {
return Err("cloud_ops.iac_tools must not be empty when cloud_ops is enabled".into());
}
Ok(())
} Type guard
fn has_iac_tools_when_enabled(cfg: &zeroclaw_config::Config) -> bool {
!cfg.cloud_ops.enabled || !cfg.cloud_ops.iac_tools.is_empty()
} Try / catch
match config.validate() {
Err(err) if err.to_string().contains("cloud_ops.iac_tools") => {
// populate iac_tools or disable [cloud_ops], then reload the config
}
other => other?,
} Prevention
- Keep a known-good [cloud_ops] block in the base config and toggle only `enabled`
- Run Config::validate() on every config change in CI before deploy
- In overlays, omit iac_tools to inherit defaults instead of writing []
When it happens
Trigger: Set `[cloud_ops] enabled = true` together with `iac_tools = []`, or apply an overlay/profile/env-mirror layer that replaces the default list with an empty array. Config::validate() aborts with this message before the application starts.
Common situations: Trimming a copied example config down to the bone and emptying arrays instead of deleting keys; enabling cloud_ops purely for cost monitoring without wanting IaC tooling; overlay configs that concatenate/override lists and end up writing an empty list over the populated default.
Related errors
- gateway.path_prefix contains invalid character '{bad}'; only
- risk_profiles.{profile_alias}.shell_env_passthrough[{i}] is
- security.otp.cache_valid_secs must be greater than or equal
- security.otp.gated_actions[{i}] contains invalid characters:
- providers.models.{profile_name}.uri must use http/https
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/4971e8a37f460aa2.
Report an issue: GitHub.