{"record":{"id":"6633eec9dd7a42e1","repo":"zeroclaw-labs/zeroclaw","slug":"jira-list-projects-failed-status","errorCode":null,"errorMessage":"Jira list_projects failed ({status}): {}","messagePattern":"Jira list_projects failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/jira_tool.rs","lineNumber":424,"sourceCode":"        let req = self\n            .http\n            .get(&url)\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 list_projects request failed\"\n            );\n            anyhow::Error::msg(format!(\"Jira list_projects 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 list_projects failed ({status}): {}\",\n                crate::util_helpers::truncate_with_ellipsis(&text, MAX_ERROR_BODY_CHARS)\n            );\n        }\n\n        let projects: Vec<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 list_projects response\"\n            );\n            anyhow::Error::msg(format!(\"Failed to parse Jira list_projects response: {e}\"))\n        })?;\n\n        let keys: Vec<String> = projects\n            .iter()","sourceCodeStart":406,"sourceCodeEnd":442,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/jira_tool.rs#L406-L442","documentation":"GET {base_url}/rest/api/{2|3}/project returned non-2xx during list_projects; the truncated body is inlined. This is usually pure access failure: 401 wrong email/token pair (Cloud) or wrong PAT (Server/DC), 403 the token user may browse no projects, or 404 from a base_url that is not a Jira site root (edge host, wrong product domain, proxy).","triggerScenarios":"list_projects with a revoked Atlassian API token; a newly created PAT not yet propagated; a user with no Browse Projects permission anywhere; base_url pointing at a Confluence domain or portal so /rest/api/*/project 404s.","commonSituations":"Tokens rotated overnight by security policy; site renames invalidating a cached base_url; CI secrets carrying trailing newlines that corrupt Basic auth; Atlassian answering 401 with a 'basic auth with API token' hint when an account password was supplied instead of a token.","solutions":["Call the myself action first: a 401 there means credentials, a 200 means this is a permission or URL problem","Re-create the API token and update the secret store","Confirm base_url resolves to the Jira site root and nothing else","Grant the token user Browse Projects in at least one project"],"exampleFix":"// before: secret loaded with a trailing newline -> Jira 401\nlet token = std::fs::read_to_string(\"token.txt\")?;\n\n// after: trim - whitespace-padded credentials always fail\nlet token = std::fs::read_to_string(\"token.txt\")?.trim().to_string();","handlingStrategy":"validation","validationCode":"// one-time preflight before relying on list_projects\nasync fn jira_credentials_ok(jira: &JiraTool) -> bool {\n    jira.execute(serde_json::json!({\"action\": \"myself\"}))\n        .await\n        .is_ok()\n}","typeGuard":"fn is_jira_list_projects_http_failure(err: &anyhow::Error) -> bool {\n    err.to_string().starts_with(\"Jira list_projects failed (\")\n}","tryCatchPattern":"match jira.execute(list_projects_args).await {\n    Ok(res) => res,\n    Err(e) if is_jira_list_projects_http_failure(&e) => {\n        let msg = e.to_string();\n        if msg.starts_with(\"Jira list_projects failed (401\") {\n            halt_and_reauth(&msg) // every other action would 401 too\n        } else {\n            return Err(e)\n        }\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Run a myself preflight at session start and treat 401 as a hard stop for all Jira actions","Trim secrets at load time","Store base_url per site in config, not in prompts","Alert on token age and rotate before expiry"],"tags":["rust","zeroclaw","jira","projects","authentication","http-status"],"backgroundTag":"jira-rest-api-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}