{"record":{"id":"3545e82f1e4c6d36","repo":"zeroclaw-labs/zeroclaw","slug":"google-workspace-allowed-services-contains-duplica","errorCode":null,"errorMessage":"google_workspace.allowed_services contains duplicate entry: {normalized}","messagePattern":"google_workspace\\.allowed_services contains duplicate entry: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-config/src/schema.rs","lineNumber":21813,"sourceCode":"        for (i, service) in self.google_workspace.allowed_services.iter().enumerate() {\n            let normalized = service.trim();\n            if normalized.is_empty() {\n                validation_bail!(\n                    RequiredFieldEmpty,\n                    format!(\"google_workspace.allowed_services[{i}]\"),\n                    \"google_workspace.allowed_services[{i}] must not be empty\"\n                );\n            }\n            if !normalized\n                .chars()\n                .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_' || c == '-')\n            {\n                anyhow::bail!(\n                    \"google_workspace.allowed_services[{i}] contains invalid characters: {normalized}\"\n                );\n            }\n            if !seen_gws_services.insert(normalized.to_string()) {\n                anyhow::bail!(\n                    \"google_workspace.allowed_services contains duplicate entry: {normalized}\"\n                );\n            }\n        }\n\n        // Build the effective allowed-services set for cross-validation.\n        // When the operator leaves allowed_services empty the tool falls back to\n        // DEFAULT_GWS_SERVICES; use the same constant here so validation is\n        // consistent in both cases.\n        let effective_services: std::collections::HashSet<&str> =\n            if self.google_workspace.allowed_services.is_empty() {\n                DEFAULT_GWS_SERVICES.iter().copied().collect()\n            } else {\n                self.google_workspace\n                    .allowed_services\n                    .iter()\n                    .map(|s| s.trim())\n                    .collect()","sourceCodeStart":21795,"sourceCodeEnd":21831,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-config/src/schema.rs#L21795-L21831","documentation":"A per-section HashSet (seen_gws_services) tracks normalized allowed_services entries; inserting a value that is already in the set bails with the duplicate named. Because normalization (trim, and whatever casing fold precedes the strict-lowercase charset check) happens before insertion, entries differing only in padding whitespace collapse to the same duplicate. The deduped set feeds the effective allowed-services cross-validation that follows, so duplicates are rejected rather than silently merged.","triggerScenarios":"Set allowed_services = [\"gmail\", \"gmail\"], or [\"gmail\", \" gmail\"], or merge config layers that each append the same service so the concatenated list contains it twice.","commonSituations":"Overlay/profile configs that merge arrays by concatenation; hand-edits adding a service that was already listed further down; copy-paste between config revisions.","solutions":["Remove the duplicate entry named in the message","If config layers merge lists by concatenation, deduplicate before writing the merged file","Watch whitespace-only differences — ' gmail' duplicates 'gmail' after normalization"],"exampleFix":"# before\n[google_workspace]\nallowed_services = [\"gmail\", \"drive\", \"gmail\"]\n\n# after\n[google_workspace]\nallowed_services = [\"gmail\", \"drive\"]","handlingStrategy":"validation","validationCode":"fn gws_duplicate_precheck(services: &[String]) -> Result<(), String> {\n    let mut seen = std::collections::HashSet::new();\n    for s in services {\n        let norm = s.trim().to_lowercase();\n        if !seen.insert(norm.clone()) {\n            return Err(format!(\"duplicate allowed_services entry: {norm}\"));\n        }\n    }\n    Ok(())\n}","typeGuard":"fn gws_services_unique(services: &[String]) -> bool {\n    let norm: std::collections::HashSet<String> = services.iter().map(|s| s.trim().to_lowercase()).collect();\n    norm.len() == services.len()\n}","tryCatchPattern":"if let Err(err) = config.validate() {\n    if err.to_string().contains(\"google_workspace.allowed_services contains duplicate\") {\n        // drop the later occurrence of the named service and reload\n    }\n}","preventionTips":["Deduplicate merged arrays before writing layered configs","Trim entries on input so whitespace variants don't create hidden duplicates","Add a config-lint step that fails on duplicate list entries across all sections"],"tags":["configuration","validation","google-workspace","duplicates","zeroclaw","rust"],"backgroundTag":"duplicate-config-entry","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}