{"record":{"id":"90a2087063dd168f","repo":"zeroclaw-labs/zeroclaw","slug":"microsoft365-client-secret-must-not-be-empty-when","errorCode":null,"errorMessage":"microsoft365.client_secret must not be empty when auth_flow is 'client_credentials'","messagePattern":"microsoft365\\.client_secret must not be empty when auth_flow is 'client_credentials'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-config/src/schema.rs","lineNumber":21721,"sourceCode":"            if client.is_none() {\n                anyhow::bail!(\n                    \"microsoft365.client_id must not be empty when microsoft365 is enabled\"\n                );\n            }\n            let flow = self.microsoft365.auth_flow.trim();\n            if flow != \"client_credentials\" && flow != \"device_code\" {\n                anyhow::bail!(\n                    \"microsoft365.auth_flow must be 'client_credentials' or 'device_code'\"\n                );\n            }\n            if flow == \"client_credentials\"\n                && self\n                    .microsoft365\n                    .client_secret\n                    .as_deref()\n                    .is_none_or(|s| s.trim().is_empty())\n            {\n                anyhow::bail!(\n                    \"microsoft365.client_secret must not be empty when auth_flow is 'client_credentials'\"\n                );\n            }\n        }\n\n        // Microsoft 365\n        if self.microsoft365.enabled {\n            let tenant = self\n                .microsoft365\n                .tenant_id\n                .as_deref()\n                .map(str::trim)\n                .filter(|s| !s.is_empty());\n            if tenant.is_none() {\n                anyhow::bail!(\n                    \"microsoft365.tenant_id must not be empty when microsoft365 is enabled\"\n                );\n            }","sourceCodeStart":21703,"sourceCodeEnd":21739,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-config/src/schema.rs#L21703-L21739","documentation":"When the trimmed auth_flow is \"client_credentials\", microsoft365.client_secret must be present and non-whitespace; the device_code flow intentionally skips this check. The bail comes from the first M365 validation block (the quoted-message variant). This mirrors Microsoft's own rule: the client-credentials OAuth grant authenticates with client ID + secret, so a missing secret cannot work at runtime.","triggerScenarios":"Set `auth_flow = \"client_credentials\"` with client_secret omitted, set to \"\", or whitespace-only on an enabled [microsoft365] section.","commonSituations":"Secret rotation that removes the old value before the new one lands; keeping the secret only in an env var that the service unit or container never loads; switching from device_code to client_credentials without adding a secret.","solutions":["Set microsoft365.client_secret to the app registration's secret value (Azure portal > App registrations > Certificates & secrets)","Or switch to `auth_flow = \"device_code\"` if you cannot hold a secret in this deployment","If the secret is env-injected, verify the variable actually reaches the process before startup validation"],"exampleFix":"# before\n[microsoft365]\nenabled = true\ntenant_id = \"...\"\nclient_id = \"...\"\nauth_flow = \"client_credentials\"\n# client_secret missing\n\n# after\n[microsoft365]\nenabled = true\ntenant_id = \"...\"\nclient_id = \"...\"\nauth_flow = \"client_credentials\"\nclient_secret = \"<secret-value>\"","handlingStrategy":"validation","validationCode":"fn m365_secret_precheck(m: &zeroclaw_config::Microsoft365Config) -> Result<(), String> {\n    if !m.enabled { return Ok(()); }\n    if m.auth_flow.trim() == \"client_credentials\"\n        && m.client_secret.as_deref().map(str::trim).is_none_or(str::is_empty)\n    {\n        return Err(\"client_secret required for client_credentials flow\".into());\n    }\n    Ok(())\n}","typeGuard":"fn m365_secret_consistent(m: &zeroclaw_config::Microsoft365Config) -> bool {\n    m.auth_flow.trim() != \"client_credentials\"\n        || m.client_secret.as_deref().map(str::trim).is_some_and(|s| !s.is_empty())\n}","tryCatchPattern":"if let Err(err) = config.validate() {\n    if err.to_string().contains(\"microsoft365.client_secret\") {\n        // inject the secret from the secret store, or switch to device_code flow\n    }\n}","preventionTips":["Make the secret a required template variable when auth_flow = client_credentials","Verify env-injected secrets are exported in the service unit before startup","Prefer device_code for workstations that cannot hold secrets"],"tags":["configuration","validation","microsoft365","auth-flow","secrets","zeroclaw","rust"],"backgroundTag":"required-config-field-missing","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}