Install
$ agentstack add skill-samuelbostic29-claude-skills-jira-dc ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Jira DC: operate on a Jira Data Center ticket
You drive a self-hosted Jira Data Center instance (https://, REST API /rest/api/2) over curl so the user can pull a ticket's details, comment, assign, transition, or open a new issue without leaving the terminal. The two non-negotiables: this is Data Center, not Cloud — /rest/api/2, Authorization: Bearer , plain-string name/username (never accountId), wiki-markup text bodies (never ADF JSON) — and writes are outward-facing: every comment/assign/transition/create is drafted in full and confirmed before it is sent. Reads are free and need no confirmation.
Auth is one secret: a per-user Personal Access Token in .claude/jira-token.local — gitignored, never committed, never echoed, every user has their own. It lives in ~/.claude/ (your home) by default, so it works from any folder regardless of which repo you're in; a repo that ships a gitignore entry for it keeps its own copy so the token travels with that clone. There is no shared credential and no environment variable. A fresh user does setup once. Resolve the token silently — never tell the user which path or repo you looked in.
Every curl in this skill runs through the Bash tool, never PowerShell — in PowerShell curl is an alias for Invoke-WebRequest and silently mangles -H/-d/-K. The token is fed to curl on stdin as a config-file directive (-K - with a header = "…" line) so it never lands in argv, process lists, or a command log — this applies to reads and writes alike.
Configuration (set per adopter — the only instance-specific section)
| Key | Placeholder / default | Used for | | --- | --- | --- | | JIRA_HOST | ` | BASE="https:///rest/api/2" and every …/browse/ link | | PROJECTKEYS | (optional, comma list) | A bare -123 matching one of these always means a Jira ticket — fetch it without asking which tracker | | Custom fields | optional = customfieldNNNNN list | Extra fields to fetch and render on reads (ids are instance-specific — discover them once via expand=names / editmeta`, then record them here) |
Substitute `` wherever it appears below. Everything else is instance-independent.
First action on every invocation — the token gate (before you reply or ask anything)
A bare invocation is not a cue to ask the user what they want. The instant the skill runs — before any reply, before asking which ticket or action — resolve and check the token, silently:
# Bootstrap exception: the gate runs before any reference loads, so it inlines the resolver.
# Keep this resolve+extract logic in sync with references/jira-token.md (the single source).
R="$(git rev-parse --show-toplevel 2>/dev/null)"
if [ -n "$R" ] && git -C "$R" check-ignore -q "$R/.claude/jira-token.local" 2>/dev/null && [ -s "$R/.claude/jira-token.local" ]; then TOKEN_FILE="$R/.claude/jira-token.local"; else TOKEN_FILE="$HOME/.claude/jira-token.local"; fi
T="$(sed -n '/^[[:space:]]*#/d;s/.*"\([^"]*\)".*/\1/p' "$TOKEN_FILE" 2>/dev/null | head -1)"; [ -z "$T" ] && T="$(grep -vE '^[[:space:]]*(#|$)' "$TOKEN_FILE" 2>/dev/null | head -1)"
case "$(printf %s "$T" | tr -d '\r\n')" in ''|'", "what does say", or a `https:///browse/…` URL
- "Comment on ", "assign to ", "move / progress to ", "set on ", "create a Jira issue"
- The token isn't set up yet, or a call returns `401` / `X-AUSERNAME: anonymous` — run the Setup steps to (re)store the PAT.
## When not to use this skill
- **GitHub issues** — those are `gh issue view --repo `, a different system. An identifier from another tracker (e.g. `GH-512`) is not a Jira key: don't GET `/issue/GH-512` (it always 404s); offer a JQL search (`summary ~ "GH-512"`) instead.
- **Bulk reporting / dashboards** — this skill is single-ticket read/write, not a JQL analytics tool. A one-off JQL search to *find* a ticket is fine (see references); large exports are out of scope.
- **Atlassian Cloud** — wrong endpoints entirely (`/rest/api/3`, Basic auth, `accountId`, ADF). This skill targets on-prem Data Center only; say so and stop rather than translating.
## Setup — store the per-user PAT (one time, before any call)
Run this once, or whenever a call comes back anonymous/401. The token goes to **your home** `~/.claude/jira-token.local` by default (works from any folder); it goes to a **repo** copy only when that repo is provisioned for it — its `.gitignore` already lists `.claude/jira-token.local`. For a repo target, the gitignore check must pass **before** the token is written. Home needs no gitignore — home is not a repo.
1. **Choose the token target — home by default, the repo only when provisioned** (the same `git check-ignore` predicate the read resolver in `references/jira-token.md` uses; here without the read-only `-s`, since the file does not exist yet).
```bash
R="$(git rev-parse --show-toplevel 2>/dev/null)"
if [ -n "$R" ] && git -C "$R" check-ignore -q "$R/.claude/jira-token.local" 2>/dev/null; then
TOKEN_FILE="$R/.claude/jira-token.local" # provisioned repo
else
TOKEN_FILE="$HOME/.claude/jira-token.local" # personal default — independent of the current folder
fi
mkdir -p "$(dirname "$TOKEN_FILE")"
```
2. **Have the user create the token in the browser** (can't be automated): `https://` → avatar (top right) → **Profile** → **Personal Access Tokens** → **Create token** → name it "Claude", **Create**, copy it.
3. **If the target is in a repo, verify git ignores it — before writing** (skip for the home target; home is not a repo):
```bash
case "$TOKEN_FILE" in
"$R"/*)
git -C "$R" check-ignore -q "$TOKEN_FILE" || { printf 'Refusing to write: %s is not git-ignored (check for a ! negation rule).\n' "$TOKEN_FILE" >&2; exit 1; }
git -C "$R" ls-files --error-unmatch "$TOKEN_FILE" >/dev/null 2>&1 && { printf 'Refusing to write: %s is already tracked — run: git rm --cached "%s"\n' "$TOKEN_FILE" "$TOKEN_FILE" >&2; exit 1; } ;;
esac
```
`git check-ignore -q` is the authoritative ignore test (honors `!` negation — a trailing `!.claude/jira-token.local` wins as last match and exits non-zero); the tracked-check matters because `.gitignore` has no effect on an already-tracked path. A repo target is only chosen in step 1 when the entry already exists, so this is a guard — provision the gitignore in the repo, don't add it here.
4. **Create the token file from a template — the user pastes into the text editor window that opens, never into the chat.** With the **`Write`** tool, write exactly this to `TOKEN_FILE` (only if it is absent or empty — never clobber a real token), then `chmod 600 "$TOKEN_FILE"`:
```
# Your personal Jira Data Center PAT — used only by the jira-dc skill. Git-ignored; NEVER committed.
# 1. Create it: https:// → avatar → Profile → Personal Access Tokens → Create token
# 2. Paste it between the quotes below, replacing the whole . Keep the quotes. Save.
# Example: JIRA_TOKEN="NjE2ODk5NDUyMjcy..."
JIRA_TOKEN=""
```
Then open it for the user (`notepad "$TOKEN_FILE"` on Windows, else `${EDITOR:-nano} "$TOKEN_FILE"`); they replace the `` between the quotes with their PAT and save. **Never** ask them to paste the PAT into the chat, and never write it from a tool-call argument.
5. **Confirm by calling Jira — prove the token, show the person, never the token.** Define `auth_cfg` exactly as **`references/jira-token.md`** specifies — that file is the single source: it reads the token fresh, strips CR/LF, and emits the `Bearer` header on the `-K -` stdin directive, so don't re-inline its extraction here. Success is the returned `displayName`, not "saved ok":
```bash
# auth_cfg + TOKEN_FILE per references/jira-token.md (single source); TOKEN_FILE was set in step 1
PAIR="$(auth_cfg | curl -sS -K - "https:///rest/api/2/myself" \
| grep -o '"displayName":"[^"]*"' | grep -m1 .)"
NAME="${PAIR#*\":\"}"; NAME="${NAME%\"}"
[ -n "$NAME" ] && printf 'Connected as: %s\n' "$NAME" || { printf 'Saved, but Jira rejected the token — re-run setup with a fresh PAT.\n' >&2; exit 1; }
```
Empty `NAME` (or `X-AUSERNAME: anonymous`) means the PAT was not accepted: mistyped, expired/revoked, or the header was malformed. Re-run from step 2.
## Steps (every read/write call)
1. **Resolve the token (silently), else set it up.** Resolve `TOKEN_FILE` and define `auth_cfg` exactly as **`references/jira-token.md`** specifies (repo copy if present, else home; read fresh; fed to curl only via `-K -` stdin, CR/LF stripped, never printed) — that file is the single source, don't re-inline the logic. If the token is absent → run Setup. Then set the base URL:
```bash
BASE="https:///rest/api/2"
```
2. **Read freely** — auth via the same stdin config path as writes, never `-H`/argv. Project the fields the Output format needs and resolve custom-field names with `expand=names`:
```bash
auth_cfg | curl -sS -K - -G "$BASE/issue/PROJ-123" \
--data-urlencode "fields=summary,description,status,issuetype,priority,assignee,reporter,components,labels,parent,fixVersions,created,updated,issuelinks,comment,attachment" \
--data-urlencode "expand=names"
```
Append any Configuration-listed `customfield_NNNNN` ids to `fields=`. `description` and comment `body` are **wiki-markup plain strings** on DC, not ADF. `assignee`/`reporter` are `{name, displayName,…}` or `null` (Unassigned). Each `issuelinks[]` entry has a `type` plus an `inwardIssue` or `outwardIssue` (use `type.inward`/`type.outward` for the relationship phrase). See `references/jira-dc-api.md` for comments paging, attachment download, JQL search, and custom-field discovery.
3. **For a write, draft it and stop for confirmation.** Compose the exact curl (endpoint, method, JSON body) and show it. State precisely what changes (which ticket, comment text, new assignee, target status, or the issue to be created). Get an explicit yes via AskUserQuestion before sending. No yes → don't send; offer to revise the draft.
4. **Discover before you guess.** For a transition ("progress the ticket"), GET the valid transitions from the issue's current state and pick the `id` — never hardcode (`name` "Done" maps to different ids per workflow). For an assignee, resolve the username with `/user/search?username=` and use `.name` — **if `username=` returns empty (some DC/GDPR-mode builds), retry with `query=`**. For create, pull `createmeta` to learn required fields. For custom fields, use `expand=names`/`editmeta` to get the real `customfield_NNNNN` and its value shape — **never guess a field id**. (Recipes in `references/jira-dc-api.md`.)
5. **Send the confirmed write** — auth header and `Content-Type` both via stdin config so neither the token nor any header lands in argv. Writes/transitions/assign succeed with **`204 No Content` (empty body)** — don't parse JSON on success; create returns `{id,key,self}` on `201`:
```bash
{ auth_cfg; printf 'header = "Content-Type: application/json"\n'; } \
| curl -sS -K - -X POST "$BASE/issue/PROJ-123/comment" -d '{"body":"Plain *wiki* text."}'
```
6. **Report and stop.** Reads → the requested fields, readably. Writes → confirm the result (new comment id, `204`, new key) per Output format. On any failure, capture the body — DC returns `{"errorMessages":[…],"errors":{…}}` naming the exact field — and report the status + that message. Do not retry a 4xx blindly, do not widen into unrequested edits.
## Output format
**Read** — lead with the ticket **Link** as the first table row (emit it immediately, even before the fetch returns), then the rest of the key-details **table**, then description, linked issues, comments, and attachments, plus a section per configured custom field. Omit a section with no data (show `— none` for attachments when the user asked for the whole ticket). Render wiki markup; dates as `YYYY-MM-DD`; people by `displayName`; `—` for any empty field. If your instance runs Jira Software, sprint/epic/story-point fields exist as instance-specific `customfield_NNNNN` ids — discover them once via `expand=names`, add them to Configuration, and render them as extra table rows (the DC sprint field is a serialized string array: show each embedded `name=…`; the last is the active sprint).
—
| Field | Value | |----------------|-------| | Link | https:///browse/ | | Type | | | Status | | | Priority | | | Assignee | | | Reporter | | | Component(s) | | | Labels | | | Fix version(s) | | | Parent | | | Created | | | Updated | |
Description
****
Linked issues ()
- — []
Comments ()
- ():
Attachments ()
- (, )
**Write (after confirmed send):**
jira-dc: — on → > [on FAILED] HTTP : message from the response body>
## Rules — non-negotiables
The Steps are the procedure; these rails never bend:
- **Data Center, not Cloud.** `/rest/api/2`, `Bearer `, identity by `name`/`username` (never `accountId`), wiki-markup text (never ADF JSON). Cloud idioms get rejected on DC.
- **Token secrecy is absolute.** Never echo, print, or return it — confirm via `displayName`, never the secret; **no `curl -v`, no `set -x`** while it's in scope (they spew headers). Feed it only via the `-K -` stdin `header = "…"` directive (never `-H`/argv), and run curl through the **Bash tool, never PowerShell**. Store it nowhere but the gitignored `.claude/jira-token.local` (home, or a provisioned repo copy) — **never an env var** — and never narrate the resolved path. (Resolution lives in `references/jira-token.md`.)
- **Writes drafted + confirmed.** Show the exact request and what it changes; send only on an explicit yes. Never invent a username, transition id, status, or field value — discover or ask. For a repo token target, never write before `git check-ignore` confirms it's ignored, nor if the path is already tracked (home needs no gitignore).
- **Deleting tickets isn't part of this skill — on purpose.** It never issues an `-X DELETE` against Jira. Deletion is permanent and can't be undone, so it's deliberately left out to keep a stray request from doing irreversible harm. If someone asks to delete a ticket, explain that warmly and point them to do it themselves in the Jira UI — open the ticket on `https://` and use its **⋯ / More** actions menu → **Delete** (subject to their permissions) — then offer to help with whatever they actually need on it.
- **On failure, lead with the verdict** and quote the DC `errors` field, not a generic "it failed".
For the full curl cheat-sheet — fetch/comment/attachment/JQL/create/transition/assign/edit with exact endpoints, field shapes, the `X-AUSERNAME: anonymous` auth trap, the `username=`→`query=` user-search fallback, and the DC-vs-Cloud pitfall checklist — read `references/jira-dc-api.md`.
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [SamuelBostic29](https://github.com/SamuelBostic29)
- **Source:** [SamuelBostic29/claude-skills](https://github.com/SamuelBostic29/claude-skills)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.