zeroclaw-labs/zeroclaw · error
IAM policy: duplicate role mapping for normalized key '{}' (
Error message
IAM policy: duplicate role mapping for normalized key '{}' (from nevis_role '{}') — remove or merge the duplicate entry What it means
IamPolicy::from_mappings found two role mappings whose nevis_role values collide after normalization (trim + ASCII lowercase). Duplicates are rejected instead of silently applying last-wins because an accidental overwrite could broaden or revoke access in the deny-by-default policy engine.
Source
Thrown at crates/zeroclaw-runtime/src/security/iam_policy.rs:93
.zeroclaw_permissions
.iter()
.filter(|p| !p.eq_ignore_ascii_case("all"))
.map(|p| p.trim().to_ascii_lowercase())
.collect();
let all_workspaces = mapping
.workspace_access
.iter()
.any(|w| w.eq_ignore_ascii_case("all"));
let allowed_workspaces: Vec<String> = mapping
.workspace_access
.iter()
.filter(|w| !w.eq_ignore_ascii_case("all"))
.map(|w| w.trim().to_ascii_lowercase())
.collect();
if role_map.contains_key(&key) {
bail!(
"IAM policy: duplicate role mapping for normalized key '{}' \
(from nevis_role '{}') — remove or merge the duplicate entry",
key,
mapping.nevis_role
);
}
role_map.insert(
key,
CompiledRole {
all_tools,
allowed_tools,
all_workspaces,
allowed_workspaces,
},
);
}
View on GitHub (pinned to 88bb9c8533)
Solutions
- Find the two entries named in the message (the normalized key and the offending nevis_role) and merge their permissions/workspaces into one.
- Add a config-lint step that fails on case-insensitive duplicate role keys before deploy.
- Adopt one casing convention (e.g. always lowercase) for nevis_role values across all sources.
- If roles truly differ, rename one of them in Nevis so normalized keys are distinct.
Example fix
# before [[iam.role_mappings]] nevis_role = "Admin" zeroclaw_permissions = ["all"] [[iam.role_mappings]] nevis_role = "admin" zeroclaw_permissions = ["fs_read"] # after — one merged entry [[iam.role_mappings]] nevis_role = "Admin" zeroclaw_permissions = ["all"]
Defensive patterns
Strategy: validation
Validate before calling
fn duplicate_role_key(ms: &[RoleMapping]) -> Option<String> {
let mut seen = std::collections::HashSet::new();
for m in ms {
let k = m.nevis_role.trim().to_ascii_lowercase();
if !k.is_empty() && !seen.insert(k) {
return Some(k);
}
}
None
} Try / catch
if let Some(key) = duplicate_role_key(&mappings) {
anyhow::bail!("config invalid: duplicate role '{key}' — merge before deploy");
}
let policy = IamPolicy::from_mappings(&mappings)?; Prevention
- Lint IAM config in CI for case-insensitive duplicate role keys.
- Adopt one casing convention for role names across all config sources.
- When merging config fragments, run a uniqueness check on normalized keys before loading.
When it happens
Trigger: Config lists the same role with different casing ("Admin" and "admin"), or the same role in two files/sections merged at load; YAML anchors or includes duplicating a role mapping block; refactors that copied a block and edited only permissions.
Common situations: Multi-file config merges where casing drifts; hand-edited configs adding a role that already exists upstream; CI environments that concatenate config fragments.
Related errors
- security.nevis: {msg}
- memory write denied by policy: {error}
- unsupported room visibility '{other}': expected private or p
- channel '{}' does not support forge API requests
- Destination {} is not in allowed list
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/23e02bdc470f8d9f.
Report an issue: GitHub.