zeroclaw-labs/zeroclaw · error
Jira comment_ticket failed
Error message
Jira comment_ticket failed ({status}): {} What it means
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.
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
Example fix
// before: empty comment body sneaks through to an HTTP 400
{"action": "comment_ticket", "ticket": "ACME-7", "comment": ""}
// after: guard the argument before invoking the tool
let comment = args["comment"].as_str().unwrap_or("").trim();
if comment.is_empty() {
return Err(anyhow::anyhow!("comment must be non-empty"));
} Defensive patterns
Strategy: validation
Validate before calling
// caller-side pre-check before comment_ticket
fn comment_args_valid(args: &serde_json::Value) -> bool {
args.get("ticket").and_then(|v| v.as_str()).is_some_and(valid_issue_key)
&& args
.get("comment")
.and_then(|v| v.as_str())
.is_some_and(|c| !c.trim().is_empty())
} Type guard
fn is_jira_comment_http_failure(err: &anyhow::Error) -> bool {
err.to_string().starts_with("Jira comment_ticket failed (")
} Try / catch
match jira.execute(comment_args).await {
Ok(res) => res,
Err(e) if is_jira_comment_http_failure(&e) => {
let msg = e.to_string();
if msg.starts_with("Jira comment_ticket failed (403") {
queue_for_human_review(&msg) // permission problem: do not retry
} else {
return Err(e)
}
}
Err(e) => return Err(e),
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- Jira list_projects users failed
- Jira get_ticket failed
- Jira list_projects failed
- Jira myself failed ( )
- Jira search_tickets failed
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/8db77040601232de.
Report an issue: GitHub.
Appendix: source
Thrown at crates/zeroclaw-tools/src/jira_tool.rs:375
.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 comment_ticket request failed"
);
anyhow::Error::msg(format!("Jira comment_ticket request failed: {e}"))
})?;
let status = resp.status();
if !status.is_success() {
let text = resp.text().await.unwrap_or_default();
anyhow::bail!(
"Jira comment_ticket failed ({status}): {}",
crate::util_helpers::truncate_with_ellipsis(&text, MAX_ERROR_BODY_CHARS)
);
}
let response: 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 comment response"
);
anyhow::Error::msg(format!("Failed to parse Jira comment response: {e}"))
})?;
let shaped = shape_comment_response(&response);
Ok(ToolResult {View on GitHub (pinned to 88bb9c8533)