zeroclaw-labs/zeroclaw · error

Jira get_ticket failed ({status}): {}

Error message

Jira get_ticket failed ({status}): {}

What it means

Raised when GET {base_url}/rest/api/{2 or 3}/issue/{issue_key} answers non-2xx; the response body (truncated to 500 chars via truncate_with_ellipsis) is appended to the message. JiraTool picks API v3 + HTTP Basic (email:api_token) for Jira Cloud and v2 + Bearer PAT for Server/Data Center, so both wrong credentials and wrong deployment-type configuration surface here. Common statuses: 401 bad credentials, 404 issue missing or not visible to the token user, 403 no Browse permission.

Source

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

            .http
            .get(&url)
            .query(&query)
            .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 get_ticket request failed"
            );
            anyhow::Error::msg(format!("Jira get_ticket request failed: {e}"))
        })?;

        let status = resp.status();
        if !status.is_success() {
            let text = resp.text().await.unwrap_or_default();
            anyhow::bail!(
                "Jira get_ticket 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 get_ticket response"
            );
            anyhow::Error::msg(format!("Failed to parse Jira get_ticket response: {e}"))
        })?;

        let shaped = match level {
            LevelOfDetails::Basic => shape_basic(&raw),

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Run the myself action with the same credentials first - a 401 there pinpoints bad auth, while 404 here means a missing or hidden issue
  2. Verify the issue key opens in the web UI of exactly that site
  3. Check the API token at id.atlassian.com (Security -> API tokens) and re-create it if revoked
  4. For Jira Cloud set BOTH email and API token; for Server/DC set NEITHER so the Bearer PAT path (v2) is used
  5. Confirm base_url is the site root, e.g. https://yoursite.atlassian.net, with no /rest suffix

Example fix

// before: Jira Cloud configured without email -> v2 + Bearer -> 401/404
JiraTool::new(
    "https://acme.atlassian.net".into(),
    None, // email missing: tool switches to Server/DC mode
    personal_token,
    actions,
    security,
    30,
)

// after: Cloud needs the account email -> v3 + Basic(email:token)
JiraTool::new(
    "https://acme.atlassian.net".into(),
    Some("dev@acme.com".into()),
    api_token,
    actions,
    security,
    30,
)
Defensive patterns

Strategy: try-catch

Validate before calling

// validate the issue key before spending a round trip
fn valid_issue_key(k: &str) -> bool {
    let mut parts = k.splitn(2, '-');
    matches!((parts.next(), parts.next()),
        (Some(p), Some(n))
        if !p.is_empty()
            && p.chars().all(|c| c.is_ascii_alphanumeric())
            && !n.is_empty()
            && n.chars().all(|c| c.is_ascii_digit()))
}

Type guard

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

Try / catch

match jira.execute(get_ticket_args).await {
    Ok(res) => res,
    Err(e) if is_jira_get_ticket_http_failure(&e) => {
        let msg = e.to_string();
        if msg.starts_with("Jira get_ticket failed (404") {
            report_issue_not_found(&msg) // missing or not browsable
        } else if msg.starts_with("Jira get_ticket failed (401") {
            fix_credentials(&msg) // email/token/base_url mismatch
        } else {
            return Err(e)
        }
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: get_ticket on a key that does not exist in the configured site, belongs to a project the token user cannot browse (Jira answers 404 for hidden issues), or any call made while email/api_token/base_url are mismatched - for example omitting the email on Cloud so v2 + Bearer is sent where Basic is required.

Common situations: base_url typos (wrong site slug in https://yoursite.atlassian.net); revoked or expired Atlassian API tokens; PAT-on-Cloud or email-on-Server mix-ups because api_version() is chosen purely by email presence; issue keys typo'd or the issue moved/deleted.

Related errors


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