The Jira integration turns the orchestrator into a Jira-driven agent. When a ticket lands in the right status (or mentions the agent), Avocado reviews the request, applies the changes to your site, posts a preview link, and waits for approval before publishing.Documentation Index
Fetch the complete documentation index at: https://docs.avocadostudio.dev/llms.txt
Use this file to discover all available pages before exploring further.
The
/jira/* routes are internal-only: they don’t appear in the public API reference and are hard-blocked when DEMO_MODE=1 is set.How it works
The integration runs a three-stage state machine driven by Jira ticket statuses and comment events. Review — The agent reads the full ticket context (summary, description, attachments, complete comment thread) and posts one of two comments:- A plan: “Here’s what I’ll do — let me know when to proceed.”
- Questions: “I need more info on X before I can start.”
JIRA_EXECUTE_STATUS (In Progress) so the board reflects live progress. The agent then runs with full tool access, including the complete comment history so any clarifications posted as replies reach the agent. It reads the current site state, applies ops, posts a comment listing every change with clickable preview links, then parks the ticket at JIRA_PREVIEW_STATUS.
Preview — The reporter reviews the draft in the editor. An approval comment triggers publish and moves the ticket to Done. Any other comment re-runs execute with the new instruction.
Approval keywords
The dispatcher uses keyword matching (not LLM inference) to detect approval. These words trigger a stage transition when they appear as whole tokens in the first 500 characters of a comment:go only matches as a whole token)
Self-loop prevention
The agent detects its own comments using both the author account ID and the comment body. A comment is only treated as agent-authored when the JiraaccountId matches JIRA_AGENT_ACCOUNT_ID AND the comment body starts with a known agent headline (e.g. “Draft updated. Ready for your review.” or “Review complete — ready to proceed.”). The dual check prevents false positives on solo / dev Jira tenants where the operator and agent share the same account — reporter replies from that account are still treated as reporter input, not self-loops.
Prerequisites
- A Jira Cloud or Jira Server / Data Center instance
- An API token (Cloud) or Personal Access Token (Server/DC)
ANTHROPIC_API_KEYorOPENAI_API_KEYin the orchestrator environment — the Jira processor uses the same AI backend as the chat pipeline; without at least one key it will refuse every ticket with an error- Orchestrator reachable from Jira’s webhook delivery (for webhook mode) or able to reach Jira (for polling mode)
Cloud vs Server/DC auth
| JIRA Cloud | JIRA Server / Data Center | |
|---|---|---|
JIRA_USER_EMAIL | Required — your Atlassian account email | Leave blank |
JIRA_API_TOKEN | API token from id.atlassian.com | Personal Access Token |
| HTTP auth scheme | Basic (email:token → base64) | Bearer token |
JIRA_USER_EMAIL is set.
Configuration
Set these variables in your.env (or deployment environment). Only JIRA_BASE_URL and JIRA_API_TOKEN are required; everything else has a sensible default.
Required
| Variable | Description |
|---|---|
JIRA_BASE_URL | Your Jira instance URL, e.g. https://mycompany.atlassian.net |
JIRA_API_TOKEN | API token (Cloud) or PAT (Server/DC) |
Auth
| Variable | Default | Description |
|---|---|---|
JIRA_USER_EMAIL | — | Email for Cloud Basic auth. Leave unset for Server/DC Bearer auth. |
JIRA_WEBHOOK_SECRET | — | Shared secret validated on every incoming webhook. Omit to accept all requests (not recommended in production). |
JIRA_AGENT_ACCOUNT_ID | — | The agent’s Jira account ID. Used to detect @mentions and prevent self-loops. Get it from your Jira profile URL or GET /rest/api/3/myself. |
Workflow statuses
These must exactly match your Jira workflow status names (case-insensitive comparison is used).| Variable | Default | Stage |
|---|---|---|
JIRA_REVIEW_STATUS | To Do | Tickets waiting for agent review / reporter approval |
JIRA_EXECUTE_STATUS | In Progress | Agent is applying edits |
JIRA_PREVIEW_STATUS | In Review | Draft ready; reporter reviews and approves |
JIRA_DONE_STATUS | Done | Terminal — ticket closed after publish |
JIRA_FAILED_STATUS | — | Optional — status to transition to on unrecoverable error |
Behaviour
| Variable | Default | Description |
|---|---|---|
JIRA_SITE_ID | avocado-stories | Which site to edit. Leave unset to let the agent detect the target site from the ticket text. |
JIRA_SESSION | jira | Session name prefix used for in-memory state. |
JIRA_AUTO_PUBLISH | 0 | Set to 1 to skip the preview stage: the agent publishes immediately after execute and closes the ticket. |
JIRA_MAX_REVIEW_PASSES | 3 | Maximum clarification rounds per ticket. After this cap, the agent forces a proceed-or-stop decision. |
Polling mode
| Variable | Default | Description |
|---|---|---|
JIRA_POLL_ENABLED | 0 | Set to 1 to enable polling (alternative to webhooks). |
JIRA_POLL_JQL | status in ("To Do", "In Review") | JQL filter for the poller. Defaults to matching your configured review and preview statuses. |
JIRA_POLL_INTERVAL_MS | 60000 | Polling interval in milliseconds. |
Setting up webhooks
Webhooks are the primary event delivery mechanism. If your orchestrator is not publicly reachable (local dev, firewalled environments), use polling mode instead.Make the orchestrator reachable
The orchestrator must be accessible from Jira’s servers. For local development, use a tunnelling tool (e.g.
ngrok http 4200). For production, deploy to a public URL and set ORCHESTRATOR_PUBLIC_ORIGIN accordingly.Create the webhook in Jira
In your Jira project:
- Go to Project Settings → Automation (Cloud) or System → WebHooks (Server/DC).
- Create a new webhook pointing to:
- Subscribe to these event types:
- Issue created (
jira:issue_created) - Issue updated (
jira:issue_updated) — covers status changes - Comment created (
comment_created) - Comment updated (
comment_updated)
- Issue created (
Configure the secret
Set a shared secret in Jira’s webhook configuration. The orchestrator reads it from the The orchestrator validates secrets with a timing-safe comparison. Without a configured secret, all requests are accepted — fine for local dev, not for production.
x-jira-webhook-secret request header (or a secret query parameter).Add the same value to your .env:Verify delivery
Move a test ticket to your
JIRA_REVIEW_STATUS. You should see an HTTP 202 response in Jira’s webhook delivery log and a new comment from the agent on the ticket within seconds.If the webhook times out or returns 503, the integration isn’t configured — check that JIRA_BASE_URL and JIRA_API_TOKEN are set and the orchestrator has restarted.Polling mode
Use polling when webhooks aren’t available (firewall, local dev without ngrok).issueKey:status:commentCount so it only re-processes a ticket when its state actually changes. The dedup cache is cleared every 24 hours.
Webhook routing logic
The table below summarises how the dispatcher maps incoming events to processing modes.| Event | Current status | Comment content | Action |
|---|---|---|---|
| Comment created/updated | JIRA_REVIEW_STATUS | Approval keyword | execute |
| Comment created/updated | JIRA_REVIEW_STATUS | Anything else | review (re-review with new info) |
| Comment created/updated | JIRA_PREVIEW_STATUS | Approval keyword | publish |
| Comment created/updated | JIRA_PREVIEW_STATUS | Anything else | execute (apply follow-up edits) |
| Comment (any status) | Any | @site-editor, @website-agent, @avocado-agent, @ai-editor mention | review |
| Status changed (by human) | → JIRA_REVIEW_STATUS | — | review |
| Status changed (by human) | → JIRA_EXECUTE_STATUS | — | execute |
| Status changed (by human) | → anything else | — | skip |
| Issue created | JIRA_REVIEW_STATUS or assigned to agent | — | review |
| Any event | Any | Authored by agent account | skip (self-loop guard) |
Attachment handling
Attachments on the ticket are downloaded and injected into the agent’s context:| Type | Handling |
|---|---|
| Images (png, jpg, jpeg, webp, svg, gif) | Saved to disk, URL passed to the agent as a reference image |
| Text documents (txt, md) | Content extracted and included inline |
| PDFs | Basic text extraction (regex-based); embedded in context |
| Other | Ignored |
Unsplash image URLs
When a reporter pastes an Unsplash photo-page URL (https://unsplash.com/photos/<id>), the execute agent uses the built-in unsplash_get_by_id tool to resolve it to a direct images.unsplash.com asset URL instead of searching for a substitute. The same tool accepts a bare photo ID. If the URL can’t be resolved, the agent asks rather than silently falling back to a keyword search.
What reporters see
Every agent action posts a comment to the ticket. Here’s what each one looks like and what to reply.Review comment (waiting for approval)
Execution complete (waiting for publish approval)
After publish
Session isolation
Each Jira ticket gets its own isolated editing session named{JIRA_SESSION}-{issue-key-lowercase} (e.g. jira-site-42). Changes made while handling one ticket cannot affect another ticket’s draft.
Endpoints
All three endpoints require thex-jira-webhook-secret header (or ?secret= query parameter) if JIRA_WEBHOOK_SECRET is configured.
POST /jira/webhook
Receives Jira webhook events. Returns 202 Accepted immediately; processing runs asynchronously.
Response (queued):
POST /jira/process
Manually trigger processing for a ticket — useful for retries and testing.
Request body:
mode is one of review, execute, or publish. Defaults to review.
Unlike /jira/webhook, this call blocks until processing completes and returns the full result.
GET /jira/status
Returns the current state of the processing queue, recent results, and poller status.
Telemetry
Every ticket run appends a line to~/.data/telemetry/jira-telemetry.jsonl (NDJSON). Each entry contains:
| Field | Description |
|---|---|
timestamp | ISO timestamp of the run |
issueKey | Jira issue key (e.g. SITE-42) |
mode | review, execute, or publish |
siteId / session | Target site and session name |
status | success or error |
durationMs | Wall-clock time for the run |
model / provider | LLM used (execute mode only) |
instruction | Full instruction text sent to the agent |
reviewDecision | { decision, plan, questions } (review mode only) |
toolCalls | Per-tool trace: name, redacted input, duration, result excerpt, error flag |
toolCallCount | Total tool calls made |
summary / changes | Agent’s output summary and list of changes |
touchedSlugs | Page slugs modified |
published | Whether the draft was published in this run |
transitions | Jira status transitions performed |
error | Error message if status = "error" |
JIRA: agent tool call line to stdout for each tool invocation during live runs.
Troubleshooting
503 from/jira/webhook
JIRA_BASE_URL or JIRA_API_TOKEN is missing. Check your .env and restart the orchestrator.
Ticket errors with “No AI API key configured”
Neither ANTHROPIC_API_KEY nor OPENAI_API_KEY is set in the orchestrator environment. Add one and restart the orchestrator. The Anthropic key is preferred when both are present.
401 from /jira/webhook
JIRA_WEBHOOK_SECRET is set but the request didn’t include the right value in x-jira-webhook-secret. Confirm Jira is sending the secret in the webhook header.
Agent posts questions but never edits
The reporter’s approval reply didn’t match any keyword. Copy-paste one of the approval keywords above as a standalone comment.
Agent keeps re-reviewing after approval
JIRA_AGENT_ACCOUNT_ID is not set or is wrong. The dispatcher can’t detect which comments are from the agent, so it treats them as reporter comments and triggers re-review. Find your account ID via GET /rest/api/3/myself and set the env var.
Ticket stays in Execute status
The agent failed to apply edits or transition the ticket. Check GET /jira/status for the recent array — the error field shows the root cause.
Poller processes the same ticket repeatedly
The fingerprint (issueKey:status:commentCount) didn’t change between polls. This happens if the agent’s comment failed to post (so comment count didn’t increment). Check orchestrator logs for Jira API errors.
Edits go to the wrong site
JIRA_SITE_ID defaults to avocado-stories. Set it to your site’s ID, or leave it unset and include the site name clearly in the ticket description so the agent can detect it.
Agent ignores or replaces an Unsplash URL I pasted
Paste the full photo-page URL (https://unsplash.com/photos/<id>) or just the bare photo ID. The agent resolves it via unsplash_get_by_id and uses it directly. If UNSPLASH_ACCESS_KEY is missing from the orchestrator environment, resolution will fail and the agent will ask for a direct image URL instead.