# Jira Tasks

> 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.

- Skill: `viderstar/jira-tasks` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add viderstar/jira-tasks`
- Raw SKILL.md: https://api.skillmd.com/api/skills/viderstar/jira-tasks/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: ViderStar (https://skillmd.com/u/viderstar)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/viderstar/jira-tasks

---


# 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

1. **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`).
2. **Run `--dry-run`** and show the human exactly what would be sent: project, type,
   summary, epic, sprint, assignee, custom fields.
3. **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.
4. After creating, print the key and the URL, and check `jira.py get KEY` if the
   description had any complex formatting.

## 3. Commands

```bash
# 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](url), 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:

```json
{
  "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).

