{"record":{"id":"8db77040601232de","repo":"zeroclaw-labs/zeroclaw","slug":"jira-comment-ticket-failed-status","errorCode":null,"errorMessage":"Jira comment_ticket failed ({status}): {}","messagePattern":"Jira comment_ticket failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/jira_tool.rs","lineNumber":375,"sourceCode":"            .http\n            .post(&url)\n            .json(&body)\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 comment_ticket request failed\"\n            );\n            anyhow::Error::msg(format!(\"Jira comment_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 comment_ticket failed ({status}): {}\",\n                crate::util_helpers::truncate_with_ellipsis(&text, MAX_ERROR_BODY_CHARS)\n            );\n        }\n\n        let response: 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 comment response\"\n            );\n            anyhow::Error::msg(format!(\"Failed to parse Jira comment response: {e}\"))\n        })?;\n\n        let shaped = shape_comment_response(&response);\n        Ok(ToolResult {","sourceCodeStart":357,"sourceCodeEnd":393,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/jira_tool.rs#L357-L393","documentation":"POST {base_url}/rest/api/{2|3}/issue/{key}/comment answered non-2xx; the truncated body is inlined. This is reached only after the local allowed_actions gate permitted the comment action, so the failure is server-side: 403 the token user lacks Comment permission or the workflow blocks it, 400 invalid or empty body (v3 expects valid ADF content), 404 unknown issue, 401 credentials.","triggerScenarios":"comment_ticket on an issue whose workflow validator forbids commenting or whose resolved screen rejects the body (400); a token user without the Add Comments project permission (403); commenting on an issue key that belongs to another site (404).","commonSituations":"Bot users added to projects as viewers only; comments attempted on closed issues in restricted workflows; agents composing very long or emoji-heavy comments that the v3 ADF conversion rejects; issues moved between projects breaking cached keys.","solutions":["Read the status in the message: 403 -> grant the bot Add Comments permission on the project; 400 -> simplify and shorten the comment text; 404 -> verify the issue key","Open the issue in the web UI and comment as the same bot user to see the exact restriction","Confirm comment_ticket is in allowed_actions and the token user can browse the project","If the workflow blocks comments in the current status, transition the issue first or relax the validator"],"exampleFix":"// before: empty comment body sneaks through to an HTTP 400\n{\"action\": \"comment_ticket\", \"ticket\": \"ACME-7\", \"comment\": \"\"}\n\n// after: guard the argument before invoking the tool\nlet comment = args[\"comment\"].as_str().unwrap_or(\"\").trim();\nif comment.is_empty() {\n    return Err(anyhow::anyhow!(\"comment must be non-empty\"));\n}","handlingStrategy":"validation","validationCode":"// caller-side pre-check before comment_ticket\nfn comment_args_valid(args: &serde_json::Value) -> bool {\n    args.get(\"ticket\").and_then(|v| v.as_str()).is_some_and(valid_issue_key)\n        && args\n            .get(\"comment\")\n            .and_then(|v| v.as_str())\n            .is_some_and(|c| !c.trim().is_empty())\n}","typeGuard":"fn is_jira_comment_http_failure(err: &anyhow::Error) -> bool {\n    err.to_string().starts_with(\"Jira comment_ticket failed (\")\n}","tryCatchPattern":"match jira.execute(comment_args).await {\n    Ok(res) => res,\n    Err(e) if is_jira_comment_http_failure(&e) => {\n        let msg = e.to_string();\n        if msg.starts_with(\"Jira comment_ticket failed (403\") {\n            queue_for_human_review(&msg) // permission problem: do not retry\n        } else {\n            return Err(e)\n        }\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Validate a non-empty comment and a well-formed issue key before the call","Grant bot users the minimum permission set (Browse Projects + Add Comments) explicitly","Keep comment text plain and under Jira body limits","Track which workflow statuses disallow comments and transition first"],"tags":["rust","zeroclaw","jira","comment","permissions","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"}