Jira issues from the command line
Entry point: scripts/jira.py (shortened to jira.py in the examples below). Standard
library only, so it runs anywhere Python 3.8 does.
1. Access
Credentials are read in this order: environment → ~/.config/atlassian-kit/atlassian.env
→ config.json in the skill root.
JIRA_BASE_URL=https://yoursite.atlassian.net
JIRA_EMAIL=you@example.com
JIRA_API_TOKEN=...
The env file matters because a desktop app is not started from a shell and never sees what
~/.zshrc exports. Never put the token in config.json — that file belongs in git.
Verify with python3 jira.py check: it prints your name, accountId and the issue types of
the project.
Tokens: https://id.atlassian.com/manage-profile/security/api-tokens — use the e-mail you
actually sign in with, a different address on the same domain returns 401.
2. Creating an issue
- Write the description into a Markdown file, not a command-line argument — that
keeps lists, code blocks and non-ASCII text intact (
--desc-file body.md).
- Run
--dry-run and show the human exactly what would be sent: project, type,
summary, epic, sprint, assignee, custom fields.
- Wait for an explicit yes. A created issue is visible to the whole team and usually
cannot be deleted — this is an outward-facing action.
- After creating, print the key and the URL, and check
jira.py get KEY if the
description had any complex formatting.
3. Commands
# what types and fields this project has - the first thing to run on an unfamiliar project
python jira.py meta --project PROJ
python jira.py meta --project PROJ --type Task --values
# create
python jira.py create -s "Summary" --desc-file body.md \
--epic PROJ-42 --sprint 1753 -a Smith \
-f "Team=Platform" -f "Severity=Major" \
--component ClickHouse --labels ml,etl --dry-run
# a batch from JSON (see assets/issues.example.json)
python jira.py create --spec tasks.json --dry-run
# read and search
python jira.py get PROJ-1234
python jira.py search --jql "project = PROJ AND sprint in openSprints() AND assignee = currentUser()"
# update, assign, link, comment, transition
python jira.py update PROJ-1234 -s "New summary" --desc-file body.md -f "Size=M"
python jira.py assign PROJ-1234 --user Smith
python jira.py link --type Blocks --outward PROJ-2 --inward PROJ-1
python jira.py comment PROJ-1234 --text "shipped"
python jira.py transition PROJ-1234 # lists the available transitions
python jira.py transition PROJ-1234 --to "In Progress"
# sprints and people
python jira.py sprints --state active
python jira.py users Smith
Details worth knowing:
-f "Field name=value" sets any field by its human name, repeatable. The name is
matched case-insensitively as a substring, and so is the value (-f "Sev=Major" works).
When a value is not allowed, the script prints the full list of allowed ones.
--desc-file understands Markdown: headings, bullet and numbered lists, fenced code
blocks with a language, ---, bold, italic, code, links, and pipe
tables. All of it is converted to ADF, because API v3 refuses a plain string.
--epic works both in classic projects (the Epic Link field) and team-managed ones
(parent).
--dry-run exists on create and update.
4. Switching projects
Project resolution: --project KEY → JIRA_PROJECT → default_project in config.json
→ error.
Permanently, put it in config.json, together with a board for sprints and the field
values that should be filled in automatically:
{
"base_url": "https://yoursite.atlassian.net",
"default_project": "PROJ",
"projects": {
"PROJ": {
"board_id": 12,
"default_type": "Task",
"defaults": { "Team": "Platform" }
}
}
}
Onboarding onto an unfamiliar project: jira.py meta -p KEY for the types →
jira.py meta -p KEY -t Task --values for the fields and their allowed values → move the
mandatory ones into defaults. The board id comes from jira.py sprints -p KEY with no
--board.
5. Traps
- Check the direction a link came out as. Which of
--outward / --inward ends up as
"blocks" depends on the link type's configuration on that site. link prints the result
as Jira now reports it — read it rather than assuming.
- 404 instead of 401. A project you have no rights to answers "not found", not
"forbidden". If a key that certainly exists 404s, it is a permission problem.
- The summary is a single line; everything else goes in the description.
- A sprint is set by numeric id, never by name (
sprints --grep finds the id).
1---2name: jira-tasks3description: Create and manage Jira Cloud issues through the REST API - file a task, bug, epic or sub-task, attach it to an epic and a sprint, fill in custom fields by their human names, set the assignee, link issues, change status, search with JQL. Works with any Atlassian Cloud site and any project; the project is a flag or a config value. Use whenever asked to create, file, update, assign, link, comment on or search Jira issues or tickets. Not for Confluence pages - that is the confluence-kb skill.4---56# Jira issues from the command line78Entry point: `scripts/jira.py` (shortened to `jira.py` in the examples below). Standard9library only, so it runs anywhere Python 3.8 does.1011## 1. Access1213Credentials are read in this order: environment → `~/.config/atlassian-kit/atlassian.env`14→ `config.json` in the skill root.1516```17JIRA_BASE_URL=https://yoursite.atlassian.net18JIRA_EMAIL=you@example.com19JIRA_API_TOKEN=...20```2122The env file matters because a desktop app is not started from a shell and never sees what23`~/.zshrc` exports. **Never put the token in `config.json`** — that file belongs in git.24Verify with `python3 jira.py check`: it prints your name, accountId and the issue types of25the project.2627Tokens: https://id.atlassian.com/manage-profile/security/api-tokens — use the e-mail you28actually sign in with, a different address on the same domain returns 401.2930## 2. Creating an issue31321. **Write the description into a Markdown file**, not a command-line argument — that33 keeps lists, code blocks and non-ASCII text intact (`--desc-file body.md`).342. **Run `--dry-run`** and show the human exactly what would be sent: project, type,35 summary, epic, sprint, assignee, custom fields.363. **Wait for an explicit yes.** A created issue is visible to the whole team and usually37 cannot be deleted — this is an outward-facing action.384. After creating, print the key and the URL, and check `jira.py get KEY` if the39 description had any complex formatting.4041## 3. Commands4243```bash44# what types and fields this project has - the first thing to run on an unfamiliar project45python jira.py meta --project PROJ46python jira.py meta --project PROJ --type Task --values4748# create49python jira.py create -s "Summary" --desc-file body.md \50 --epic PROJ-42 --sprint 1753 -a Smith \51 -f "Team=Platform" -f "Severity=Major" \52 --component ClickHouse --labels ml,etl --dry-run5354# a batch from JSON (see assets/issues.example.json)55python jira.py create --spec tasks.json --dry-run5657# read and search58python jira.py get PROJ-123459python jira.py search --jql "project = PROJ AND sprint in openSprints() AND assignee = currentUser()"6061# update, assign, link, comment, transition62python jira.py update PROJ-1234 -s "New summary" --desc-file body.md -f "Size=M"63python jira.py assign PROJ-1234 --user Smith64python jira.py link --type Blocks --outward PROJ-2 --inward PROJ-165python jira.py comment PROJ-1234 --text "shipped"66python jira.py transition PROJ-1234 # lists the available transitions67python jira.py transition PROJ-1234 --to "In Progress"6869# sprints and people70python jira.py sprints --state active71python jira.py users Smith72```7374Details worth knowing:7576- `-f "Field name=value"` sets any field by its **human name**, repeatable. The name is77 matched case-insensitively as a substring, and so is the value (`-f "Sev=Major"` works).78 When a value is not allowed, the script prints the full list of allowed ones.79- `--desc-file` understands Markdown: headings, bullet and numbered lists, fenced code80 blocks with a language, `---`, **bold**, *italic*, `code`, [links](url), and pipe81 tables. All of it is converted to ADF, because API v3 refuses a plain string.82- `--epic` works both in classic projects (the Epic Link field) and team-managed ones83 (`parent`).84- `--dry-run` exists on `create` and `update`.8586## 4. Switching projects8788Project resolution: `--project KEY` → `JIRA_PROJECT` → `default_project` in `config.json`89→ error.9091Permanently, put it in `config.json`, together with a board for sprints and the field92values that should be filled in automatically:9394```json95{96 "base_url": "https://yoursite.atlassian.net",97 "default_project": "PROJ",98 "projects": {99 "PROJ": {100 "board_id": 12,101 "default_type": "Task",102 "defaults": { "Team": "Platform" }103 }104 }105}106```107108Onboarding onto an unfamiliar project: `jira.py meta -p KEY` for the types →109`jira.py meta -p KEY -t Task --values` for the fields and their allowed values → move the110mandatory ones into `defaults`. The board id comes from `jira.py sprints -p KEY` with no111`--board`.112113## 5. Traps114115- **Check the direction a link came out as.** Which of `--outward` / `--inward` ends up as116 "blocks" depends on the link type's configuration on that site. `link` prints the result117 as Jira now reports it — read it rather than assuming.118- **404 instead of 401.** A project you have no rights to answers "not found", not119 "forbidden". If a key that certainly exists 404s, it is a permission problem.120- The summary is a single line; everything else goes in the description.121- A sprint is set by **numeric id**, never by name (`sprints --grep` finds the id).