zeroclaw-labs/zeroclaw · error · anyhow::Error

invalid forge HTTP method `{}` (expected GET/POST/PATCH/PUT/

Error message

invalid forge HTTP method `{}` (expected GET/POST/PATCH/PUT/DELETE)

What it means

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.

Source

Thrown at crates/zeroclaw-channels/src/git/channel.rs:671

        // `ghc_<id>` message ids name a comment; anything else reacts on
        // the issue/PR body itself.
        let target = match message_id.strip_prefix("ghc_") {
            Some(comment_id) => ReactionTarget::Comment {
                repo: issue.repo.clone(),
                comment_id: comment_id.to_string(),
            },
            None => ReactionTarget::Issue(issue),
        };
        self.provider.add_reaction(&target, emoji).await?;
        Ok(())
    }

    async fn forge_request(
        &self,
        request: zeroclaw_api::channel::ForgeApiRequest,
    ) -> anyhow::Result<zeroclaw_api::channel::ForgeApiResponse> {
        let Some(method) = ForgeMethod::parse(&request.method) else {
            anyhow::bail!(
                "invalid forge HTTP method `{}` (expected GET/POST/PATCH/PUT/DELETE)",
                request.method
            );
        };
        let resp = self
            .provider
            .forge_request(ForgeRequest {
                method,
                path: request.path,
                body: request.body,
            })
            .await?;
        Ok(zeroclaw_api::channel::ForgeApiResponse {
            status: resp.status,
            body: resp.body,
        })
    }
}

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Uppercase and whitelist the method before calling forge_request
  2. Restrict external input to the five supported verbs at the API boundary
  3. Map unsupported methods explicitly if you must accept them (e.g. HEAD → GET without body, OPTIONS → reject)

Example fix

// before
let req = ForgeApiRequest { method: "get".into(), .. }; // bails: lowercase

// after
let method = raw.trim().to_ascii_uppercase();
assert!(matches!(method.as_str(), "GET" | "POST" | "PATCH" | "PUT" | "DELETE"));
let req = ForgeApiRequest { method, .. };
Defensive patterns

Strategy: type-guard

Validate before calling

// Rust — normalize and restrict at the API boundary
let method = raw.trim().to_ascii_uppercase();
if supported_forge_method(&method).is_none() {
    anyhow::bail!("method '{raw}' not supported; use GET/POST/PATCH/PUT/DELETE");
}

Type guard

// Rust — narrow to the supported verbs before calling forge_request
fn supported_forge_method(raw: &str) -> Option<String> {
    let m = raw.trim().to_ascii_uppercase();
    matches!(m.as_str(), "GET" | "POST" | "PATCH" | "PUT" | "DELETE").then_some(m)
}

Prevention

When it happens

Trigger: Calling the channel's forge_request API with method = "get" (lowercase), "head", "options", or a user-supplied method forwarded unvalidated from glue code.

Common situations: Adapter code passing through HTTP methods from external input; case mismatches after a refactor; assuming HEAD/OPTIONS work because they are standard HTTP methods.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/d3902adac078702f. Report an issue: GitHub.