Control Koan directly from Jira issue comments using @mention commands.
Introduced in: commit
fd3ccf8. Enhanced with Jira URL support in skills, comment acknowledgment, and per-project target branches.
Koan can poll your Jira Cloud instance for @mentions in issue comments. When a user posts:
@koan-bot plan
...in a Jira issue comment, Koan detects the mention, validates the command and the user's permissions, and queues a mission — all without webhooks or external services.
Jira-originated missions are marked with 🎫 in the mission queue (vs 📬 for GitHub-originated missions), making it easy to trace where a mission came from.
Jira + GitHub: Both integrations can run simultaneously. See Running Both Integrations below.
- Go to https://id.atlassian.com/manage-profile/security/api-tokens
- Click Create API token, give it a name (e.g. "Koan bot")
- Copy the token
In instance/config.yaml:
jira:
enabled: true
base_url: "https://myorg.atlassian.net"
email: "bot@example.com"
nickname: "koan-bot"
authorized_users: ["*"]Set the API token via environment variable (recommended) or config:
# In .env
KOAN_JIRA_API_TOKEN=your-api-token-hereTell Koan which Jira project keys correspond to which Koan projects:
jira:
projects:
# Simple format — project name only:
FOO: myproject # FOO-123 → project "myproject"
# Extended format — with optional target branch for PRs:
BAR:
project: anotherproject # BAR-456 → project "anotherproject"
branch: "11.126" # PRs target branch "11.126" instead of repo defaultBoth formats can be mixed. The branch field is optional — when omitted, PRs target the repository's default branch as usual.
@koan-bot plan
Koan will:
- Detect the @mention during its next polling cycle
- Validate the command and user permissions
- Create a pending mission:
- [project:myproject] /plan https://myorg.atlassian.net/browse/FOO-123 🎫 - Post a
👍 Mission queued: /planacknowledgment reply on the Jira comment - Send a Telegram notification confirming the mission was queued
- Execute it in the next agent loop iteration — fetching the full Jira issue context (title, description, and all comments)
All settings live under the jira: key in instance/config.yaml.
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Master switch for Jira integration |
base_url |
string | — | Jira instance URL (e.g. https://myorg.atlassian.net). Required when enabled |
email |
string | — | Atlassian account email for Basic auth. Required when enabled |
api_token |
string | — | Jira API token. Can also be set via KOAN_JIRA_API_TOKEN env var (takes precedence). Required when enabled |
nickname |
string | — | Bot's @mention name in Jira comments (without @). Required when enabled |
commands_enabled |
bool | false |
Reserved for future per-command filtering |
authorized_users |
list | [] |
["*"] = all users, or list of Jira account emails |
max_age_hours |
int | 24 |
Ignore comments older than this (stale protection) |
check_interval_seconds |
int | 60 |
Base polling interval in seconds (min: 10) |
max_check_interval_seconds |
int | 180 |
Maximum backoff interval when idle (min: 30) |
projects |
dict | {} |
Jira project key mapping. Simple: FOO: myproject. Extended: FOO: {project: myproject, branch: "11.126"} |
| Variable | Purpose |
|---|---|
KOAN_JIRA_API_TOKEN |
Jira API token (overrides jira.api_token in config) |
When jira.enabled: true, Koan validates the configuration at startup and warns if any required field is missing (base_url, email, api_token, nickname). The integration is silently skipped if enabled: false.
Jira reuses the same github_enabled: true skill flag for command discovery — both GitHub and Jira dispatch the exact same set of commands. No separate Jira flag is needed.
Custom skills under
instance/skills/<scope>/(e.g. the cPanel integration shipping/cp_fixand/cp_plan) are exposed here the same way: setgithub_enabled: trueandgroup: integrationsin their SKILL.md. Such skills with ahandler.pyare dispatched in-process by the Jira bridge — not queued as slash missions — and the handler automatically receives the originating Jira issue key inctx.argswhen the commenter omitted one. Seekoan/skills/README.mdfor the full pattern.
| Command | Aliases | What it does | Context-aware |
|---|---|---|---|
ask |
— | Ask Koan a question about a Jira issue | Yes |
audit |
— | Audit a project codebase and create GitHub issues | Yes |
brainstorm |
— | Decompose a topic into linked GitHub issues | Yes |
deepplan |
deeplan |
Spec-first design with Socratic exploration | Yes |
fix |
— | Fix an issue end-to-end | Yes |
gh_request |
— | Natural-language GitHub request dispatch | Yes |
implement |
impl |
Implement an issue | Yes |
plan |
— | Deep-think and create a structured plan | Yes |
profile |
perf, benchmark |
Queue a performance profiling mission | Yes |
rebase |
rb |
Rebase a PR onto latest upstream | Yes |
recreate |
rc |
Recreate a diverged PR from scratch | Yes |
refactor |
rf |
Queue a refactoring mission | Yes |
review |
rv |
Queue a code review mission | Yes |
reviewrebase |
rr |
Review then rebase combo | Yes |
security_audit |
security, secu |
Security-focused audit | Yes |
squash |
sq |
Squash all PR commits into one | Yes |
Commands with context awareness accept additional text after the command word:
@koan-bot implement phase 1 only
This creates a mission: /implement https://myorg.atlassian.net/browse/FOO-123 phase 1 only
You can override the default project mapping using the repo: token:
@koan-bot plan repo:other-project focus on API layer
This routes the mission to other-project instead of the project mapped to the Jira issue's project key.
You can override the target branch for PRs using the branch: token:
@koan-bot fix branch:main
This takes highest priority — overriding both the per-project branch configured in jira.projects and the repository's default branch. Useful for one-off requests targeting a different release branch.
When a target branch is set (via config or override), the feature branch is created from it and the PR targets it with --base.
run.py ← Pre-iteration check (before plan_iteration)
loop_manager.py ← Also polls during sleep cycle (throttled, after GitHub check)
↓
jira_notifications.py ← Fetches & filters Jira comments, parses @mentions
↓
jira_command_handler.py ← Validates commands, checks permissions, creates missions
↓
jira_config.py ← Reads jira: config (project map + branch map)
↓
skills.py ← Skill flags: github_enabled (reused for Jira)
Jira notifications are checked in two places:
- Pre-iteration: At the start of each agent loop iteration (so
plan_iteration()sees Jira missions immediately) - During sleep: Between iterations (same as GitHub, with exponential backoff)
1. process_jira_notifications()
2. Build JQL query (POST /rest/api/3/search/jql): issues updated in mapped projects since last check
3. Paginate results using cursor-based nextPageToken
4. Fetch recent comments on matching issues
5. For each comment containing @nickname:
a. Skip if already processed (in-memory set + .jira-processed.json)
b. Skip if stale (> max_age_hours)
c. Parse @mention → extract (command, context)
d. Handle repo: override if present
e. Handle branch: override if present (or use per-project config default)
f. Validate command → skill must have github_enabled: true
g. Check user permission → allowlist of Jira account emails
h. Insert mission into missions.md (with branch:X token if set)
i. Mark comment as processed (in-memory + persistent tracker)
j. Post 👍 acknowledgment reply on the Jira comment
k. Notify via Telegram (🎫 emoji prefix)
Jira Cloud stores comment bodies as ADF — a JSON tree format. Koan recursively extracts plain text from ADF nodes while skipping code blocks (codeBlock, code, inlineCard) to prevent false @mention matches inside code examples.
Both ADF (Jira Cloud) and plain text (Jira Server/older) formats are supported.
Two-tier approach matching the GitHub integration pattern:
- In-memory BoundedSet: Tracks processed comment IDs within a session (capped at 10,000 entries). Fast, but lost on restart.
- Persistent tracker:
.jira-processed.jsonin the instance directory. Loaded on startup, trimmed to 5,000 entries to prevent unbounded growth. Written via atomic file operations.
| Condition | Check interval |
|---|---|
| Mentions found | check_interval_seconds (default: 60s) |
| 1 empty check | 2x base interval |
| 2 consecutive empty | 4x base interval |
| 3+ consecutive empty | max_check_interval_seconds cap (default: 180s) |
Backoff resets immediately when any mention is found.
When a mission originates from a Jira URL (e.g. /fix https://myorg.atlassian.net/browse/FOO-123), the skill runners (/fix, /plan, /implement) automatically detect the Jira URL and fetch full issue context from the Jira REST API:
- Title: Issue summary
- Description: Full issue body (converted from ADF to plain text)
- All comments: Every comment with author attribution (ADF to plain text)
This context is fed to Claude the same way GitHub issue context would be — the agent sees the complete Jira issue when working on the fix or plan.
Skills that accept GitHub issue/PR URLs also accept Jira browse URLs:
/fix https://myorg.atlassian.net/browse/FOO-123/plan https://myorg.atlassian.net/browse/FOO-123/implement https://myorg.atlassian.net/browse/FOO-123
When the source is Jira, GitHub-specific steps (closed-state check, PR submission) are adjusted — PR submission still works if the Koan project has a github_url configured in projects.yaml.
Jira API calls use HTTP Basic authentication with your Atlassian account email and an API token. The token is never logged. It can be provided via:
KOAN_JIRA_API_TOKENenvironment variable (recommended)jira.api_tokenin config.yaml
Every command goes through:
- Allowlist check: The commenter's email must be in
authorized_users(or wildcard*is set) - Stale comment protection: Comments older than
max_age_hoursare silently discarded
Note: Unlike GitHub, Jira does not expose a "write access" check via its REST API. Permission control relies on the
authorized_usersallowlist. Use explicit email lists instead of["*"]for tighter security.
@mentions inside Jira code blocks ({code}...{code}, {{...}}, {noformat}...{noformat}) are ignored, preventing accidental command triggers from code examples.
Jira project keys used in JQL queries are validated against a strict alphanumeric pattern (^[A-Z0-9]+$). Non-conforming keys are silently filtered out.
Jira and GitHub integrations are designed to coexist. They serve complementary roles:
| GitHub | Jira | |
|---|---|---|
| Primary use | Code-level actions (PR rebase, code review, implementation) | Issue tracking and project planning |
| Trigger location | PR/issue comments on GitHub | Issue comments on Jira |
| Mission marker | 📬 | 🎫 |
| Auth method | gh CLI + GH_TOKEN |
HTTP Basic + API token |
| Permission model | Allowlist + GitHub write access check | Allowlist (email-based) |
| Polling | GitHub notifications API | JQL search + comment fetch |
# GitHub integration
github:
nickname: "koan-bot"
commands_enabled: true
authorized_users: ["*"]
# Jira integration
jira:
enabled: true
base_url: "https://myorg.atlassian.net"
email: "bot@example.com"
nickname: "koan-bot"
authorized_users: ["*"]
projects:
PROJ: myproject # Simple format
INFRA: # Extended format with target branch
project: infrastructure
branch: "11.126"# In .env
GH_TOKEN=ghp_xxxx
KOAN_JIRA_API_TOKEN=xxxxBoth integrations poll independently during the agent's sleep cycle — GitHub notifications are checked first, then Jira. Each has its own backoff schedule. Missions from both sources enter the same missions.md queue and are processed identically by the agent loop.
- GitHub @mentions: Best for code-centric actions — rebasing a PR, reviewing a diff, implementing a specific issue with linked code context.
- Jira @mentions: Best for project-level planning — turning a Jira epic into implementation tasks, planning a feature described in a ticket, auditing code related to a Jira story.
Both can trigger the same set of commands. The difference is the context URL attached to the mission — a GitHub URL gives the agent direct access to diffs and PR metadata, while a Jira URL provides issue descriptions and comment threads.
- Check feature is enabled:
jira.enabled: truein config.yaml - Verify required fields:
base_url,email,api_token, andnicknamemust all be set. Check logs for startup validation warnings. - Check project mapping: The Jira issue's project key must be in
jira.projects. A comment onFOO-123requiresprojects: { FOO: some_project }. - Check polling: Look for
[jira]log entries inmake logs. If you see "no recently-updated issues found", the JQL query isn't matching. - Verify API access: Test manually:
curl -X POST -u "email@example.com:YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ "https://myorg.atlassian.net/rest/api/3/search/jql" \ -d '{"jql": "project = FOO", "maxResults": 1}'
Note: Jira Cloud deprecated
GET /rest/api/3/search(returns HTTP 410). Koan usesPOST /rest/api/3/search/jqlwith cursor-based pagination.
The 🎫 mission was written to missions.md. Check:
instance/missions.md— the mission should be in the Pending section- Agent loop logs — the mission will be picked up in the next iteration
- Project name resolution — the
repo:override or project mapping must point to a valid Koan project inprojects.yaml
Jira project keys must be uppercase alphanumeric (e.g., FOO, MYPROJ). Keys with special characters are silently filtered out. Check your jira.projects mapping uses valid keys.
Expected behavior. The in-memory processed set is lost on restart, but the persistent tracker (.jira-processed.json) prevents most duplicates. If a crash occurred between mission creation and tracker update, a duplicate may appear — it's harmless and the agent handles already-completed missions gracefully.
- GitHub Notification Commands — GitHub @mention integration (complementary)
- Messaging: Telegram — Primary command interface
- Messaging: Slack — Alternative messaging provider
- Skills Reference — Full skill documentation
- User Manual — Complete usage guide