{"record":{"id":"d3902adac078702f","repo":"zeroclaw-labs/zeroclaw","slug":"invalid-forge-http-method-expected-get-post","errorCode":null,"errorMessage":"invalid forge HTTP method `{}` (expected GET/POST/PATCH/PUT/DELETE)","messagePattern":"invalid forge HTTP method `(.+?)` \\(expected GET/POST/PATCH/PUT/DELETE\\)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/git/channel.rs","lineNumber":671,"sourceCode":"        // `ghc_<id>` message ids name a comment; anything else reacts on\n        // the issue/PR body itself.\n        let target = match message_id.strip_prefix(\"ghc_\") {\n            Some(comment_id) => ReactionTarget::Comment {\n                repo: issue.repo.clone(),\n                comment_id: comment_id.to_string(),\n            },\n            None => ReactionTarget::Issue(issue),\n        };\n        self.provider.add_reaction(&target, emoji).await?;\n        Ok(())\n    }\n\n    async fn forge_request(\n        &self,\n        request: zeroclaw_api::channel::ForgeApiRequest,\n    ) -> anyhow::Result<zeroclaw_api::channel::ForgeApiResponse> {\n        let Some(method) = ForgeMethod::parse(&request.method) else {\n            anyhow::bail!(\n                \"invalid forge HTTP method `{}` (expected GET/POST/PATCH/PUT/DELETE)\",\n                request.method\n            );\n        };\n        let resp = self\n            .provider\n            .forge_request(ForgeRequest {\n                method,\n                path: request.path,\n                body: request.body,\n            })\n            .await?;\n        Ok(zeroclaw_api::channel::ForgeApiResponse {\n            status: resp.status,\n            body: resp.body,\n        })\n    }\n}","sourceCodeStart":653,"sourceCodeEnd":689,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/git/channel.rs#L653-L689","documentation":"forge_request parses the incoming ForgeApiRequest.method with ForgeMethod::parse, which accepts exactly GET, POST, PATCH, PUT, DELETE. Any other value — lowercase variants, HEAD, OPTIONS, or methods with stray whitespace — bails before any HTTP traffic reaches the forge.","triggerScenarios":"Calling the channel's forge_request API with method = \"get\" (lowercase), \"head\", \"options\", or a user-supplied method forwarded unvalidated from glue code.","commonSituations":"Adapter code passing through HTTP methods from external input; case mismatches after a refactor; assuming HEAD/OPTIONS work because they are standard HTTP methods.","solutions":["Uppercase and whitelist the method before calling forge_request","Restrict external input to the five supported verbs at the API boundary","Map unsupported methods explicitly if you must accept them (e.g. HEAD → GET without body, OPTIONS → reject)"],"exampleFix":"// before\nlet req = ForgeApiRequest { method: \"get\".into(), .. }; // bails: lowercase\n\n// after\nlet method = raw.trim().to_ascii_uppercase();\nassert!(matches!(method.as_str(), \"GET\" | \"POST\" | \"PATCH\" | \"PUT\" | \"DELETE\"));\nlet req = ForgeApiRequest { method, .. };","handlingStrategy":"type-guard","validationCode":"// Rust — normalize and restrict at the API boundary\nlet method = raw.trim().to_ascii_uppercase();\nif supported_forge_method(&method).is_none() {\n    anyhow::bail!(\"method '{raw}' not supported; use GET/POST/PATCH/PUT/DELETE\");\n}","typeGuard":"// Rust — narrow to the supported verbs before calling forge_request\nfn supported_forge_method(raw: &str) -> Option<String> {\n    let m = raw.trim().to_ascii_uppercase();\n    matches!(m.as_str(), \"GET\" | \"POST\" | \"PATCH\" | \"PUT\" | \"DELETE\").then_some(m)\n}","tryCatchPattern":null,"preventionTips":["Normalize HTTP methods to uppercase at the trust boundary","Whitelist the five supported verbs for any user-supplied method input"],"tags":["git","forge-api","http-method","validation"],"backgroundTag":"invalid-http-method","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}