{"record":{"id":"779bb276bb6d3cdd","repo":"tinyhumansai/openhuman","slug":"channel-is-required","errorCode":null,"errorMessage":"channel is required","messagePattern":"channel is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/api/rest.rs","lineNumber":555,"sourceCode":"            .to_string();\n        anyhow::ensure!(!jwt.is_empty(), \"consume login token response missing jwt\");\n        Ok(jwt)\n    }\n\n    /// Validates that the provided session token is still active and accepted.\n    pub async fn validate_session_token(&self, bearer_jwt: &str) -> Result<()> {\n        let _ = self.fetch_current_user(bearer_jwt).await?;\n        Ok(())\n    }\n\n    /// Creates a short-lived link token for connecting a specific communication channel.\n    pub async fn create_channel_link_token(\n        &self,\n        channel: &str,\n        bearer_jwt: &str,\n    ) -> Result<Value> {\n        let channel = channel.trim().trim_matches('/');\n        anyhow::ensure!(!channel.is_empty(), \"channel is required\");\n        let encoded_channel = urlencoding::encode(channel);\n\n        self.authed_json(\n            bearer_jwt,\n            Method::POST,\n            &format!(\"auth/channels/{encoded_channel}/link-token\"),\n            None,\n        )\n        .await\n    }\n\n    /// Generic authenticated JSON request helper for backend API routes.\n    pub async fn authed_json(\n        &self,\n        bearer_jwt: &str,\n        method: Method,\n        path: &str,\n        body: Option<Value>,","sourceCodeStart":537,"sourceCodeEnd":573,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/src/api/rest.rs#L537-L573","documentation":"Thrown by create_channel_link_token when the channel argument is empty after trimming whitespace and stripping slashes. The channel becomes a URL path segment (auth/channels/{channel}/link-token) naming which integration the short-lived link token is for; an empty segment has no referent. Caller-input validation only — nothing is sent.","triggerScenarios":"Calling create_channel_link_token(\"\", ...) or create_channel_link_token(\"/\", ...) — e.g. the channel id was taken from a channel-connection record whose provider field was blank.","commonSituations":"A channel integration row created before a schema change left the provider field empty, or the pairing UI passed its unselected state straight through.","solutions":["Pass the concrete channel/provider identifier from the integration record","Validate the channel field where the record is created so blank integrations cannot enter the store","Guard at the call site with the same trim+slash normalization the method applies"],"exampleFix":"// before\nlet v = client.create_channel_link_token(&ch, &jwt).await?; // ch = \"\"\n\n// after\nlet ch = ch.trim().trim_matches('/');\nanyhow::ensure!(!ch.is_empty(), \"channel is required for link-token creation\");\nlet v = client.create_channel_link_token(ch, &jwt).await?;","handlingStrategy":"validation","validationCode":"let channel = channel.trim().trim_matches('/');\nanyhow::ensure!(!channel.is_empty(), \"channel is required for link-token creation\");\nlet v = client.create_channel_link_token(channel, &jwt).await?;","typeGuard":"fn normalize_channel(raw: &str) -> Option<String> {\n    let c = raw.trim().trim_matches('/');\n    (!c.is_empty()).then(|| c.to_string())\n}","tryCatchPattern":null,"preventionTips":["Reject blank provider/channel fields when integration records are created","Run channel strings through normalize_channel at the edge of your module"],"tags":["rust","validation","channel","link-token"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}