zeroclaw-labs/zeroclaw · error

Jira search_tickets failed

Error message

Jira search_tickets failed ({status}): {}

What it means

Thrown when the Jira Cloud search call (POST {base_url}/rest/api/3/search with JQL, page size 100) returns non-2xx; search_tickets dispatches to this v3 path whenever an email is configured. The truncated body (500 chars) is inlined. Frequent causes: 400 invalid JQL (syntax, unknown field or function), 401/403 credential problems, or 404/gone when Atlassian removes or changes the Cloud REST search endpoint.

Solutions

  1. Paste the exact JQL into Jira's advanced search in the web UI to get its precise error
  2. Fix quoting: wrap phrases in double quotes, escape embedded quotes, and check field names against /rest/api/3/field
  3. If the message shows 404/410 with a 'removed' body, upgrade zeroclaw-tools - the endpoint contract changed
  4. On 401/403 verify email + token with the myself action before debugging JQL

Example fix

// before: raw user phrase passed as JQL -> HTTP 400
{"action": "search_tickets", "query": "summary ~ ship it now"}

// after: quote the phrase so the JQL parses
{"action": "search_tickets", "query": "summary ~ \"ship it now\""}
Defensive patterns

Strategy: try-catch

Validate before calling

// cheap JQL sanity check before the call
fn jql_looks_valid(q: &str) -> bool {
    let q = q.trim();
    !q.is_empty() && q.matches('"').count() % 2 == 0
}

Type guard

fn is_jira_search_http_failure(err: &anyhow::Error) -> bool {
    err.to_string().starts_with("Jira search_tickets failed (")
}

Try / catch

match jira.execute(search_args).await {
    Ok(res) => res,
    Err(e) if is_jira_search_http_failure(&e) => {
        let msg = e.to_string();
        if msg.contains("(400") {
            surface_jql_error(&msg) // body carries Atlassian's JQL message
        } else if msg.contains("(401") || msg.contains("(403") {
            refresh_credentials()
        } else {
            return Err(e)
        }
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: search_tickets with malformed JQL - unbalanced quotes, unescaped reserved words, or a custom field referenced by a name that does not exist in the site; an empty query string; or an Atlassian-side endpoint change where /rest/api/3/search answers 404/410 for the pinned zeroclaw-tools version.

Common situations: Agents building JQL from raw user input with unescaped double quotes; sites where custom fields were renamed after JQL templates were written; Atlassian deprecating older search endpoints so an older zeroclaw-tools build starts failing until upgraded.

Related errors


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

Appendix: source

Thrown at crates/zeroclaw-tools/src/jira_tool.rs:224

                .http
                .post(&url)
                .json(&body)
                .timeout(std::time::Duration::from_secs(self.timeout_secs));
            let resp = self.authenticated(req).send().await.map_err(|e| {
                ::zeroclaw_log::record!(
                    ERROR,
                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
                        .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                        .with_attrs(::serde_json::json!({"error": format!("{}", e)})),
                    "jira: Jira search_tickets request failed"
                );
                anyhow::Error::msg(format!("Jira search_tickets request failed: {e}"))
            })?;

            let status = resp.status();
            if !status.is_success() {
                let text = resp.text().await.unwrap_or_default();
                anyhow::bail!(
                    "Jira search_tickets failed ({status}): {}",
                    crate::util_helpers::truncate_with_ellipsis(&text, MAX_ERROR_BODY_CHARS)
                );
            }

            let raw: Value = resp.json().await.map_err(|e| {
                ::zeroclaw_log::record!(
                    ERROR,
                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
                        .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                        .with_attrs(::serde_json::json!({"error": format!("{}", e)})),
                    "jira: Failed to parse Jira search response"
                );
                anyhow::Error::msg(format!("Failed to parse Jira search response: {e}"))
            })?;

            if let Some(page) = raw["issues"].as_array() {
                issues.extend(page.iter().map(shape_basic_search));

View on GitHub (pinned to 88bb9c8533)