{"record":{"id":"dd6e57ea93d75474","repo":"zeroclaw-labs/zeroclaw","slug":"matrix-login-requires-either-access-token-or-user","errorCode":null,"errorMessage":"matrix login requires either access_token or user_id+password","messagePattern":"matrix login requires either access_token or user_id\\+password","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/matrix.rs","lineNumber":1528,"sourceCode":"            state_dir.display(),\n        );\n    }\n\n    async fn login_fresh(client: &Client, config: &MatrixConfig) -> Result<()> {\n        // Prefer password when set: it creates a server-side device matching\n        // `config.device_id`, so subsequent crypto operations don't fight with\n        // a token bound to a different device.\n        if let Some(pw) = config.password.as_deref().filter(|s| !s.is_empty()) {\n            return password_login(client, config, pw).await;\n        }\n        if config\n            .access_token\n            .as_deref()\n            .is_some_and(|t| !t.is_empty())\n        {\n            return access_token_login(client, config).await;\n        }\n        bail!(\"matrix login requires either access_token or user_id+password\")\n    }\n\n    async fn password_login(client: &Client, config: &MatrixConfig, password: &str) -> Result<()> {\n        let user_id = config\n            .user_id\n            .clone()\n            .filter(|s| !s.is_empty())\n            .ok_or_else(|| {\n                ::zeroclaw_log::record!(\n                    WARN,\n                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)\n                        .with_outcome(::zeroclaw_log::EventOutcome::Failure),\n                    \"matrix.user_id is required for password login\"\n                );\n                anyhow::Error::msg(\"matrix.user_id is required for password login\")\n            })?;\n        let mut login = client\n            .matrix_auth()","sourceCodeStart":1510,"sourceCodeEnd":1546,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/matrix.rs#L1510-L1546","documentation":"Matrix login dispatches purely on config: a non-empty (trimmed) access_token selects token login, otherwise user-id + password are used. If neither combination is present, login bails before any network call. It is a configuration-completeness error: one of access_token, or both user-id and password, must be non-empty.","triggerScenarios":"Starting or constructing the Matrix channel when channels.matrix has no access_token and lacks the user-id + password pair, or has only one of the two password fields.","commonSituations":"Half-finished migration from token auth to password auth (password set, user-id forgotten); env-var interpolation producing empty strings; first-time setup that skipped credentials; config keys renamed after an upgrade.","solutions":["Set a valid channels.matrix.access_token, or set both channels.matrix.user-id and channels.matrix.password.","Values are trimmed: a whitespace-only token counts as missing - delete the blank keys entirely.","Verify env expansion (e.g. ${MATRIX_TOKEN}) actually resolves in the deployment environment.","Run a config lint at deploy time so missing credentials fail the pipeline, not the runtime."],"exampleFix":"# before\n[channels.matrix]\nhomeserver = \"https://matrix.example.org\"\n\n# after\n[channels.matrix]\nhomeserver = \"https://matrix.example.org\"\nuser-id = \"@bot:example.org\"\npassword = \"correct-horse-battery\"","handlingStrategy":"validation","validationCode":"fn matrix_auth_complete(cfg: &MatrixConfig) -> bool {\n    let token = cfg.access_token.as_deref().is_some_and(|t| !t.trim().is_empty());\n    let password = cfg.user_id.as_deref().is_some_and(|u| !u.trim().is_empty())\n        && cfg.password.as_deref().is_some_and(|p| !p.is_empty());\n    token || password\n}\n\nassert!(matrix_auth_complete(&config), \"channels.matrix needs access_token or user-id+password\");","typeGuard":"enum MatrixAuth<'a> { Token(&'a str), Password(&'a str, &'a str), Missing }\n\nfn classify_auth(cfg: &MatrixConfig) -> MatrixAuth<'_> {\n    if let Some(t) = cfg.access_token.as_deref() {\n        if !t.trim().is_empty() {\n            return MatrixAuth::Token(t);\n        }\n    }\n    match (cfg.user_id.as_deref(), cfg.password.as_deref()) {\n        (Some(u), Some(p)) if !u.trim().is_empty() && !p.is_empty() => MatrixAuth::Password(u, p),\n        _ => MatrixAuth::Missing,\n    }\n}","tryCatchPattern":null,"preventionTips":["Fail fast at config load with a schema check that requires one complete auth method.","Assert secrets-manager values are non-empty during deployment, not at runtime.","Keep first-time setup checklists that enumerate homeserver plus one auth pair."],"tags":["matrix","auth","config","login","credentials"],"backgroundTag":"missing-auth-credentials","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}