{"record":{"id":"943f9743bda656ac","repo":"zeroclaw-labs/zeroclaw","slug":"jira-get-ticket-failed-status","errorCode":null,"errorMessage":"Jira get_ticket failed ({status}): {}","messagePattern":"Jira get_ticket failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/jira_tool.rs","lineNumber":127,"sourceCode":"            .http\n            .get(&url)\n            .query(&query)\n            .timeout(std::time::Duration::from_secs(self.timeout_secs));\n        let resp = self.authenticated(req).send().await.map_err(|e| {\n            ::zeroclaw_log::record!(\n                ERROR,\n                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)\n                    .with_outcome(::zeroclaw_log::EventOutcome::Failure)\n                    .with_attrs(::serde_json::json!({\"error\": format!(\"{}\", e)})),\n                \"jira: Jira get_ticket request failed\"\n            );\n            anyhow::Error::msg(format!(\"Jira get_ticket request failed: {e}\"))\n        })?;\n\n        let status = resp.status();\n        if !status.is_success() {\n            let text = resp.text().await.unwrap_or_default();\n            anyhow::bail!(\n                \"Jira get_ticket failed ({status}): {}\",\n                crate::util_helpers::truncate_with_ellipsis(&text, MAX_ERROR_BODY_CHARS)\n            );\n        }\n\n        let raw: Value = resp.json().await.map_err(|e| {\n            ::zeroclaw_log::record!(\n                ERROR,\n                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)\n                    .with_outcome(::zeroclaw_log::EventOutcome::Failure)\n                    .with_attrs(::serde_json::json!({\"error\": format!(\"{}\", e)})),\n                \"jira: Failed to parse Jira get_ticket response\"\n            );\n            anyhow::Error::msg(format!(\"Failed to parse Jira get_ticket response: {e}\"))\n        })?;\n\n        let shaped = match level {\n            LevelOfDetails::Basic => shape_basic(&raw),","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/jira_tool.rs#L109-L145","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Run the myself action with the same credentials first - a 401 there pinpoints bad auth, while 404 here means a missing or hidden issue","Verify the issue key opens in the web UI of exactly that site","Check the API token at id.atlassian.com (Security -> API tokens) and re-create it if revoked","For Jira Cloud set BOTH email and API token; for Server/DC set NEITHER so the Bearer PAT path (v2) is used","Confirm base_url is the site root, e.g. https://yoursite.atlassian.net, with no /rest suffix"],"exampleFix":"// before: Jira Cloud configured without email -> v2 + Bearer -> 401/404\nJiraTool::new(\n    \"https://acme.atlassian.net\".into(),\n    None, // email missing: tool switches to Server/DC mode\n    personal_token,\n    actions,\n    security,\n    30,\n)\n\n// after: Cloud needs the account email -> v3 + Basic(email:token)\nJiraTool::new(\n    \"https://acme.atlassian.net\".into(),\n    Some(\"dev@acme.com\".into()),\n    api_token,\n    actions,\n    security,\n    30,\n)","handlingStrategy":"try-catch","validationCode":"// validate the issue key before spending a round trip\nfn valid_issue_key(k: &str) -> bool {\n    let mut parts = k.splitn(2, '-');\n    matches!((parts.next(), parts.next()),\n        (Some(p), Some(n))\n        if !p.is_empty()\n            && p.chars().all(|c| c.is_ascii_alphanumeric())\n            && !n.is_empty()\n            && n.chars().all(|c| c.is_ascii_digit()))\n}","typeGuard":"fn is_jira_get_ticket_http_failure(err: &anyhow::Error) -> bool {\n    err.to_string().starts_with(\"Jira get_ticket failed (\")\n}","tryCatchPattern":"match jira.execute(get_ticket_args).await {\n    Ok(res) => res,\n    Err(e) if is_jira_get_ticket_http_failure(&e) => {\n        let msg = e.to_string();\n        if msg.starts_with(\"Jira get_ticket failed (404\") {\n            report_issue_not_found(&msg) // missing or not browsable\n        } else if msg.starts_with(\"Jira get_ticket failed (401\") {\n            fix_credentials(&msg) // email/token/base_url mismatch\n        } else {\n            return Err(e)\n        }\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Preflight credentials with the myself action once at startup","Keep one config source of truth for base_url/email/token and derive Cloud-vs-Server from it","Trim whitespace when loading tokens - never paste with a trailing newline","Map project key prefixes to site names in docs so agents query the right instance"],"tags":["rust","zeroclaw","jira","rest-api","http-status","authentication"],"backgroundTag":"jira-rest-api-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}