{"record":{"id":"78d04800ae906058","repo":"tinyhumansai/openhuman","slug":"integrationid-must-be-a-24-char-hex-id","errorCode":null,"errorMessage":"integrationId must be a 24-char hex id","messagePattern":"integrationId must be a 24-char hex id","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/api/rest.rs","lineNumber":879,"sourceCode":"        let integrations = value\n            .get(\"integrations\")\n            .cloned()\n            .unwrap_or_else(|| value.clone());\n        serde_json::from_value(integrations).context(\"parse integrations response\")\n    }\n\n    /// Fetches the decrypted OAuth tokens for a specific integration.\n    ///\n    /// This is a one-time handoff process. The encryption key must match the\n    /// one used by the backend to encrypt the tokens.\n    pub async fn fetch_integration_tokens_handoff(\n        &self,\n        integration_id: &str,\n        bearer_jwt: &str,\n        encryption_key: &str,\n    ) -> Result<IntegrationTokensHandoff> {\n        let id = integration_id.trim();\n        anyhow::ensure!(\n            !id.is_empty() && id.len() == 24,\n            \"integrationId must be a 24-char hex id\"\n        );\n        let body = serde_json::json!({ \"key\": encryption_key.trim() });\n        let value = self\n            .authed_json(\n                bearer_jwt,\n                Method::POST,\n                &format!(\"auth/integrations/{id}/tokens\"),\n                Some(body),\n            )\n            .await\n            .context(\"integration tokens handoff\")?;\n        let encrypted = value\n            .get(\"encrypted\")\n            .and_then(Value::as_str)\n            .context(\"integration tokens response missing encrypted payload\")?;\n        let plaintext = decrypt_handoff_blob(encrypted, encryption_key.trim())?;","sourceCodeStart":861,"sourceCodeEnd":897,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/src/api/rest.rs#L861-L897","documentation":"Thrown by fetch_integration_tokens_handoff when the integration id is empty or not exactly 24 chars after trimming. Backend integration ids are MongoDB ObjectIds (24 hex chars) and the id is interpolated into auth/integrations/{id}/tokens. Note the code only checks length — the \"hex\" in the message is not verified, so a 24-char non-hex string passes here and fails server-side.","triggerScenarios":"Passing a UUID (32/36 chars), an internal numeric id, or a blank string as integration_id — e.g. mixing up your own record key with the backend's integration ObjectId.","commonSituations":"Ids from two systems conflated after a refactor, trailing newline/whitespace in ids read from CSV or clipboard, or a placeholder like \"test\" used in a scratch script.","solutions":["Use the id verbatim from list_integrations — that is the backend ObjectId","If you hold your own ids, map them to the backend integration id before calling","Validate 24-char length (and hex-ness, to be strict) at the boundary"],"exampleFix":"// before\nlet h = client.fetch_integration_tokens_handoff(&my_uuid, &jwt, &key).await?;\n\n// after\nlet id = list_integrations(&jwt).await?\n    .into_iter().find(|i| i.provider == wanted)\n    .map(|i| i.id)\n    .ok_or_else(|| anyhow!(\"no integration for {wanted}\"))?;\nlet h = client.fetch_integration_tokens_handoff(&id, &jwt, &key).await?;","handlingStrategy":"validation","validationCode":"let id = integration_id.trim();\nanyhow::ensure!(id.len() == 24, \"integration id must be a 24-char backend ObjectId, got {} chars\", id.len());\nlet h = client.fetch_integration_tokens_handoff(id, &jwt, &key).await?;","typeGuard":"fn is_backend_integration_id(s: &str) -> bool {\n    let t = s.trim();\n    t.len() == 24 && t.chars().all(|c| c.is_ascii_hexdigit())\n}","tryCatchPattern":null,"preventionTips":["Always source integration ids from list_integrations, never synthesize them locally","Keep your own ids in a separate typed field so they cannot be passed where ObjectIds are expected"],"tags":["rust","validation","integration","objectid"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}