{"record":{"id":"2c623438734d494d","repo":"tinyhumansai/openhuman","slug":"integration-id-is-required","errorCode":null,"errorMessage":"integration id is required","messagePattern":"integration id is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/api/rest.rs","lineNumber":1121,"sourceCode":"    ) -> Result<Value> {\n        let channel = channel.trim().trim_matches('/');\n        anyhow::ensure!(!channel.is_empty(), \"channel is required\");\n        let encoded = urlencoding::encode(channel);\n        let mut path = format!(\"channels/{encoded}/threads\");\n        if let Some(active) = active_filter {\n            path.push_str(if active {\n                \"?active=true\"\n            } else {\n                \"?active=false\"\n            });\n        }\n        self.authed_json(bearer_jwt, Method::GET, &path, None).await\n    }\n\n    /// Revokes (deletes) an active integration.\n    pub async fn revoke_integration(&self, integration_id: &str, bearer_jwt: &str) -> Result<()> {\n        let id = integration_id.trim();\n        anyhow::ensure!(!id.is_empty(), \"integration id is required\");\n        self.authed_json(\n            bearer_jwt,\n            Method::DELETE,\n            &format!(\"auth/integrations/{id}\"),\n            None,\n        )\n        .await?;\n        Ok(())\n    }\n}\n\n/// AES-256-GCM decrypt compatible with backend `encryptMessageFromString` (IV 16 + tag 16 + ciphertext, base64).\npub fn decrypt_handoff_blob(b64_ciphertext: &str, key_str: &str) -> Result<String> {\n    let key = key_bytes_from_string(key_str)?;\n    let combined = base64::engine::general_purpose::STANDARD\n        .decode(b64_ciphertext.trim())\n        .context(\"base64-decode encrypted payload\")?;\n    if combined.len() < 32 {","sourceCodeStart":1103,"sourceCodeEnd":1139,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/src/api/rest.rs#L1103-L1139","documentation":"Thrown by revoke_integration when the integration id is empty after trimming. The revoke is a DELETE on auth/integrations/{id}; unlike the handoff/client-key methods there is no 24-char length requirement here — only non-emptiness — but the id is still the backend integration identifier. Input validation only.","triggerScenarios":"Calling revoke_integration(\"\") — e.g. a revoke action dispatched from a UI row whose id failed to load, or a stale record with a blank id field.","commonSituations":"Settings screen's revoke button enabled on a row rendered from partial data, or an id lost through serialization after a refactor.","solutions":["Pass the id verbatim from list_integrations","Disable revoke actions on rows without an id","Guard at the call site with trim()"],"exampleFix":"// before\nclient.revoke_integration(&integration_id, &jwt).await?; // integration_id = \"\"\n\n// after\nlet id = integration_id.trim();\nanyhow::ensure!(!id.is_empty(), \"integration id is required to revoke\");\nclient.revoke_integration(id, &jwt).await?;","handlingStrategy":"validation","validationCode":"let id = integration_id.trim();\nanyhow::ensure!(!id.is_empty(), \"integration id is required to revoke\");\nclient.revoke_integration(id, &jwt).await?;","typeGuard":"fn has_integration_id(s: &str) -> bool {\n    !s.trim().is_empty()\n}","tryCatchPattern":null,"preventionTips":["Source the id from list_integrations rows and disable revoke actions on rows without one","Confirm destructive actions (revoke) with the integration name, not the raw id, before calling"],"tags":["rust","validation","integration","revoke"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}