{"record":{"id":"8ae3aaad4751d4fd","repo":"zeroclaw-labs/zeroclaw","slug":"createsession-failed-status-body","errorCode":null,"errorMessage":"createSession failed ({status}): {body}","messagePattern":"createSession failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"critical","filePath":"crates/zeroclaw-channels/src/bluesky.rs","lineNumber":140,"sourceCode":"    /// Create a new session with handle + app password.\n    async fn create_session(&self) -> Result<()> {\n        let client = self.http_client();\n        let resp = client\n            .post(format!(\"{BSKY_API_BASE}/com.atproto.server.createSession\"))\n            .json(&serde_json::json!({\n                \"identifier\": self.handle,\n                \"password\": self.app_password,\n            }))\n            .send()\n            .await?;\n\n        let status = resp.status();\n        if !status.is_success() {\n            let body = resp\n                .text()\n                .await\n                .unwrap_or_else(|e| format!(\"<failed to read response: {e}>\"));\n            bail!(\"createSession failed ({status}): {body}\");\n        }\n\n        let session: CreateSessionResponse = resp.json().await?;\n        let mut auth = self.auth.lock();\n        auth.access_jwt = session.access_jwt;\n        auth.refresh_jwt = session.refresh_jwt;\n        auth.did = session.did;\n        // AT Protocol JWTs typically last ~2 hours; refresh well before that.\n        auth.expires_at = Instant::now() + Duration::from_secs(90 * 60);\n        Ok(())\n    }\n\n    /// Refresh an existing session.\n    async fn refresh_session(&self) -> Result<()> {\n        let refresh_jwt = {\n            let auth = self.auth.lock();\n            auth.refresh_jwt.clone()\n        };","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/bluesky.rs#L122-L158","documentation":"Bluesky's com.atproto.server.createSession returned a non-2xx response while exchanging the configured handle + app password for session JWTs; the HTTP status and response body are embedded verbatim. listen() calls create_session first, so this error typically aborts channel startup — a 401 body saying the identifier or password is invalid means bad credentials.","triggerScenarios":"BlueskyChannel::listen (or any first-auth path) with a wrong handle, a revoked or rotated app password, the account's main login password instead of an App Password, or the API rejecting the createSession request (rate limit, malformed identifier).","commonSituations":"Using the login password instead of an App Password created in Bluesky settings; app password regenerated after a leak so the old one is dead; handle typo or renamed account; polling too aggressively and hitting rate limits at startup.","solutions":["Create or verify an App Password in Bluesy settings (Settings -> App passwords) and set it as the channel's app_password.","Confirm the handle is exactly right, including any custom domain.","Read the embedded status: 401 means credentials, 400 means malformed identifier, 429 means rate limited (back off and retry).","Reproduce outside ZeroClaw with a manual createSession request against bsky.social/xrpc using the same credentials to confirm whether they are valid."],"exampleFix":"# before: main login password is rejected by AT Protocol\n[channels.bluesky.main]\nhandle = \"me.example.com\"\napp_password = \"my-login-password\"\n\n# after: App Password generated in Bluesky settings\n[channels.bluesky.main]\nhandle = \"me.example.com\"\napp_password = \"xxxx-xxxx-xxxx-xxxx\"","handlingStrategy":"try-catch","validationCode":"if handle.trim().is_empty() || app_password.trim().is_empty() {\n    anyhow::bail!(\"bluesky channel requires a non-empty handle and app password\");\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = bluesky.listen(tx).await {\n    if e.to_string().contains(\"createSession failed (401\") {\n        tracing::error!(\"bluesky credentials rejected — check the app password and handle\");\n    }\n    return Err(e);\n}","preventionTips":["Always use App Passwords, never the account login password","Store app passwords in the secret store and rotate without code changes","Do one createSession at startup and fail fast on 401 instead of retry-looping"],"tags":["rust","bluesky","atproto","authentication","http-api","credentials"],"backgroundTag":"authentication-credentials-rejected","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}