This guide explains how to set up automatic Jira synchronization with the GSD (Get Stuff Done) workflow in Claude Code. When configured, GSD will automatically create/update Jira issues as you work through milestones and phases, and use Jira ticket IDs in commit messages.
| GSD Event | Jira Action | Commit Format |
|---|---|---|
/gsd:new-milestone |
Creates Epic (or skips if already linked) | — |
/gsd:plan-phase N |
Creates Task under Epic for the phase | — |
/gsd:execute-phase N |
Moves Task to "In Progress" | MGMT-XXXXX: description |
| PR created | Move Task to "Code Review" (manual) | — |
/gsd:complete-milestone |
Moves Epic to "Done" | — |
Commit messages change from the default GSD format:
feat(08-02): create user registration endpoint
to Jira-prefixed format:
MGMT-12346: create user registration endpoint
You can also link existing Jira epics and tickets instead of creating new ones:
/jira-sync link-epic MGMT-12345
/jira-sync link-phase 3 MGMT-12346
/jira-sync status
/jira-sync unlink
- Claude Code installed
- GSD installed (
npx get-shit-done-cc@latest) - jira-cli installed and configured for Red Hat Jira
- Jira Claude Code plugin installed
go install github.com/ankitpokhrel/jira-cli/cmd/jira@latestjira init --installation local --server https://issues.redhat.com --auth-type bearerAdd your Personal Access Token to ~/.netrc:
machine issues.redhat.com
login <your-email>
password <your-personal-access-token>
Generate a token at: issues.redhat.com → Profile → Personal Access Tokens
Verify:
jira me
# Should print your emailclaude plugins install jira@ecosystem-claude-pluginsThe integration requires two types of changes:
- New files — a workflow file and a command file (easy to copy)
- Patches to existing GSD workflow files — modifications to 4 GSD files (need to be reapplied after GSD updates)
Create the command file at ~/.claude/commands/gsd/jira-sync.md:
---
name: gsd:jira-sync
description: Link GSD milestones and phases to Jira epics and tickets, or view current mapping
argument-hint: "<link-epic MGMT-XXXXX | link-phase N MGMT-XXXXX | status | unlink>"
allowed-tools:
- Read
- Write
- Edit
- Bash
- AskUserQuestion
---
<objective>
Manage the Jira mapping for the current GSD milestone. Link existing Jira epics and tickets to milestones and phases, view current mapping, or remove mappings.
Subcommands:
- `link-epic MGMT-XXXXX` — Link existing Jira epic to current milestone
- `link-phase <phase-number> MGMT-XXXXX` — Link existing Jira ticket to a phase
- `status` — Show current Jira mapping with live status from Jira
- `unlink` — Remove all Jira mappings
When no subcommand is given, show status.
</objective>
<execution_context>
@/home/eran/.claude/get-shit-done/workflows/jira-sync.md
</execution_context>
<context>
Subcommand and arguments: $ARGUMENTS
Jira CLI is pre-configured for Red Hat Jira (issues.redhat.com), MGMT project.
Mapping is stored in `.planning/config.json` under the `jira` key.
</context>
<process>
Execute the jira-sync workflow from @/home/eran/.claude/get-shit-done/workflows/jira-sync.md.
Use the jira-task-management skill knowledge for Jira CLI commands.
</process>Note: Update the
@/home/eran/paths to match your home directory.
Copy the workflow file to ~/.claude/get-shit-done/workflows/jira-sync.md.
The source file is available in this repository at docs/gsd-jira-sync-workflow.md.
cp docs/gsd-jira-sync-workflow.md ~/.claude/get-shit-done/workflows/jira-sync.mdApply these changes to the GSD workflow files in ~/.claude/get-shit-done/workflows/. Each patch shows the exact location and content to insert.
Important: These patches must be reapplied after running
/gsd:update. GSD automatically backs up modified files and you can use/gsd:reapply-patchesto restore them.
Location: Insert new step 10.5 between step 10 (roadmap commit) and step 11 (Done banner).
Find this line: ## 11. Done
Insert BEFORE it:
## 10.5. Jira Sync — Create or Link Epic
After roadmap is committed, sync with Jira. If an epic is already linked (via `/jira-sync link-epic`), skip creation. Otherwise, create a new one.
```bash
if command -v jira &>/dev/null; then
# Check if epic already linked
EXISTING_EPIC=$(node -e "try { const c=JSON.parse(require('fs').readFileSync('.planning/config.json','utf8')); console.log(c.jira?.epic || ''); } catch(e) { console.log(''); }")
if [ -n "$EXISTING_EPIC" ]; then
echo "Jira Epic already linked: ${EXISTING_EPIC} — skipping creation"
else
MILESTONE_TITLE="v[X.Y] [Milestone Name]"
MILESTONE_GOAL="[One sentence goal from step 4]"
EPIC_OUTPUT=$(jira epic create -s "${MILESTONE_TITLE}" -n "${MILESTONE_TITLE}" -b "GSD Milestone: ${MILESTONE_GOAL}" -l OSAC --no-input 2>&1)
EPIC_KEY=$(echo "$EPIC_OUTPUT" | grep -oE 'MGMT-[0-9]+' | head -1)
if [ -n "$EPIC_KEY" ]; then
node -e "
const fs = require('fs');
const cfg = JSON.parse(fs.readFileSync('.planning/config.json', 'utf8'));
cfg.jira = { epic: '${EPIC_KEY}', phases: {} };
fs.writeFileSync('.planning/config.json', JSON.stringify(cfg, null, 2) + '\n');
"
echo "Jira Epic created: ${EPIC_KEY}"
else
echo "Warning: Could not create Jira epic (jira CLI may not be configured)"
fi
fi
fi
```Location: Insert new step 12.5 between step 12 (revision loop) and step 13 (present final status).
Find this line: ## 13. Present Final Status
Insert BEFORE it:
## 12.5. Jira Sync — Create Task for Phase
After plans are verified, create a Jira Task under the Epic for this phase.
```bash
if command -v jira &>/dev/null; then
EPIC_KEY=$(node -e "try { const c=JSON.parse(require('fs').readFileSync('.planning/config.json','utf8')); console.log(c.jira?.epic || ''); } catch(e) { console.log(''); }")
EXISTING=$(node -e "try { const c=JSON.parse(require('fs').readFileSync('.planning/config.json','utf8')); console.log(c.jira?.phases?.['${PHASE}'] || ''); } catch(e) { console.log(''); }")
if [ -n "$EPIC_KEY" ] && [ -z "$EXISTING" ]; then
TASK_OUTPUT=$(jira issue create -tTask -s "Phase ${PHASE}: ${phase_name}" \
-b "GSD Phase ${PHASE}: ${phase_name}" \
-P "$EPIC_KEY" -l OSAC --no-input 2>&1)
TASK_KEY=$(echo "$TASK_OUTPUT" | grep -oE 'MGMT-[0-9]+' | head -1)
if [ -n "$TASK_KEY" ]; then
node -e "
const fs = require('fs');
const cfg = JSON.parse(fs.readFileSync('.planning/config.json', 'utf8'));
if (!cfg.jira) cfg.jira = { epic: '', phases: {} };
if (!cfg.jira.phases) cfg.jira.phases = {};
cfg.jira.phases['${PHASE}'] = '${TASK_KEY}';
fs.writeFileSync('.planning/config.json', JSON.stringify(cfg, null, 2) + '\n');
"
echo "Jira Task created: ${TASK_KEY} (linked to ${EPIC_KEY})"
fi
fi
fi
```Location: Insert new step jira_sync_in_progress BEFORE the existing validate_phase step.
Find this line: <step name="validate_phase">
Insert BEFORE it:
<step name="jira_sync_in_progress">
Move the Jira Task for this phase to "In Progress":
```bash
if command -v jira &>/dev/null; then
JIRA_KEY=$(node -e "try { const c=JSON.parse(require('fs').readFileSync('.planning/config.json','utf8')); console.log(c.jira?.phases?.['${PHASE_NUMBER}'] || ''); } catch(e) { console.log(''); }")
if [ -n "$JIRA_KEY" ]; then
jira issue move "$JIRA_KEY" "In Progress" 2>/dev/null || true
echo "Jira ${JIRA_KEY} → In Progress"
fi
fi
```
</step>This patch has 4 sub-changes in the same file:
4a. Task Commit Protocol (replace the commit type table and format line)
Find the section starting with **3. Commit type:** through **4. Format:**. Replace the entire block with:
**3. Resolve commit prefix — check for Jira key first:**
```bash
JIRA_KEY=$(node -e "
try {
const fs = require('fs');
const c = JSON.parse(fs.readFileSync('.planning/config.json', 'utf8'));
const phase = '${PHASE}'.split('-')[0].replace(/^0+/, '');
console.log(c.jira?.phases?.[phase] || '');
} catch(e) { console.log(''); }
" 2>/dev/null)
```
**If JIRA_KEY is set**, use Jira-prefixed format:
| Example |
|---------|
| MGMT-12346: create user registration endpoint |
| MGMT-12346: correct email validation regex |
| MGMT-12346: add failing test for password hashing |
| MGMT-12346: add bcrypt dependency |
**Format:** `{JIRA_KEY}: {description}` with bullet points for key changes.
**If JIRA_KEY is empty**, fall back to conventional commit format:
| Type | When | Example |
|------|------|---------|
| `feat` | New functionality | feat(08-02): create user registration endpoint |
| `fix` | Bug fix | fix(08-02): correct email validation regex |
| `test` | Test-only (TDD RED) | test(08-02): add failing test for password hashing |
| `refactor` | No behavior change (TDD REFACTOR) | refactor(08-02): extract validation to helper |
| `perf` | Performance | perf(08-02): add database index |
| `docs` | Documentation | docs(08-02): add API docs |
| `style` | Formatting | style(08-02): format auth module |
| `chore` | Config/deps | chore(08-02): add bcrypt dependency |
**Format:** `{type}({phase}-{plan}): {description}` with bullet points for key changes.4b. TDD commit messages
Find the 3 lines for RED/GREEN/REFACTOR commits. Replace with:
2. **RED:** ... commit using resolved prefix: `{JIRA_KEY}: add failing test for [feature]` or fallback `test({phase}-{plan}): add failing test for [feature]`
3. **GREEN:** ... commit using resolved prefix: `{JIRA_KEY}: implement [feature]` or fallback `feat({phase}-{plan}): implement [feature]`
4. **REFACTOR:** ... commit using resolved prefix: `{JIRA_KEY}: clean up [feature]` or fallback `refactor({phase}-{plan}): clean up [feature]`
4c. Metadata commit step
Find <step name="git_commit_metadata">. Replace the entire step with:
<step name="git_commit_metadata">
Task code already committed per-task. Commit plan metadata using resolved prefix:
```bash
# Use Jira key if mapped, otherwise default format
JIRA_KEY=$(node -e "
try {
const fs = require('fs');
const c = JSON.parse(fs.readFileSync('.planning/config.json', 'utf8'));
const phase = '${PHASE}'.split('-')[0].replace(/^0+/, '');
console.log(c.jira?.phases?.[phase] || '');
} catch(e) { console.log(''); }
" 2>/dev/null)
if [ -n "$JIRA_KEY" ]; then
COMMIT_MSG="${JIRA_KEY}: complete [plan-name] plan"
else
COMMIT_MSG="docs({phase}-{plan}): complete [plan-name] plan"
fi
node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit "${COMMIT_MSG}" --files .planning/phases/XX-name/{phase}-{plan}-SUMMARY.md .planning/STATE.md .planning/ROADMAP.md .planning/REQUIREMENTS.md
```4d. Codebase map git log grep
Find the FIRST_TASK=$(git log ...) line in the update_codebase_map step. Replace with:
# Search for commits by Jira key or phase-plan pattern
JIRA_KEY=$(node -e "try { const fs=require('fs'); const c=JSON.parse(fs.readFileSync('.planning/config.json','utf8')); const p='${PHASE}'.split('-')[0].replace(/^0+/,''); console.log(c.jira?.phases?.[p]||''); } catch(e) { console.log(''); }" 2>/dev/null)
if [ -n "$JIRA_KEY" ]; then
FIRST_TASK=$(git log --oneline --grep="${JIRA_KEY}:" --reverse | head -1 | cut -d' ' -f1)
else
FIRST_TASK=$(git log --oneline --grep="feat({phase}-{plan}):" --grep="fix({phase}-{plan}):" --grep="test({phase}-{plan}):" --reverse | head -1 | cut -d' ' -f1)
fiLocation: Insert new step jira_sync_done BEFORE the existing git_tag step.
Find this line: <step name="git_tag">
Insert BEFORE it:
<step name="jira_sync_done">
Move Jira Epic to "Done":
```bash
if command -v jira &>/dev/null; then
EPIC_KEY=$(node -e "try { const c=JSON.parse(require('fs').readFileSync('.planning/config.json','utf8')); console.log(c.jira?.epic || ''); } catch(e) { console.log(''); }")
if [ -n "$EPIC_KEY" ]; then
jira issue move "$EPIC_KEY" "Done" 2>/dev/null || true
echo "Jira Epic ${EPIC_KEY} → Done"
fi
fi
```
</step>The Jira mapping is stored in .planning/config.json (per-project, alongside other GSD config):
{
"mode": "yolo",
"parallelization": true,
"jira": {
"epic": "MGMT-12345",
"phases": {
"1": "MGMT-12346",
"2": "MGMT-12347",
"3": "MGMT-12348"
}
}
}/gsd:new-milestone # Creates Epic automatically
/gsd:plan-phase 1 # Creates Task MGMT-XXXXX under Epic
/gsd:execute-phase 1 # Moves Task to "In Progress", commits use MGMT-XXXXX: ...
/gsd:complete-milestone # Moves Epic to "Done"/jira-sync link-epic MGMT-12345 # Link your existing epic
/jira-sync link-phase 1 MGMT-12346 # Link existing ticket to phase 1
/jira-sync link-phase 2 MGMT-12347 # Link existing ticket to phase 2
/jira-sync status # Verify mapping
/gsd:plan-phase 3 # Phase 3 has no link → auto-creates Task
/gsd:execute-phase 1 # Uses MGMT-12346 in commits/jira-sync status
# Shows table of all phases with Jira keys, summaries, and statuses- All Jira operations check
command -v jirafirst — no errors if CLI isn't installed - All Jira transitions use
|| true— GSD workflows never fail due to Jira errors - Duplicate detection — won't create a second ticket if phase is already mapped
- If no Jira mapping exists, commits fall back to default GSD format
/gsd:update # GSD backs up modified files automatically
/gsd:reapply-patches # Restores your Jira patchesIf /gsd:reapply-patches doesn't work (e.g., GSD restructured the files), re-apply the patches manually using this guide.
| File | Type | Location |
|---|---|---|
jira-sync.md |
New command | ~/.claude/commands/gsd/jira-sync.md |
jira-sync.md |
New workflow | ~/.claude/get-shit-done/workflows/jira-sync.md |
new-milestone.md |
Patched | ~/.claude/get-shit-done/workflows/ |
plan-phase.md |
Patched | ~/.claude/get-shit-done/workflows/ |
execute-phase.md |
Patched | ~/.claude/get-shit-done/workflows/ |
execute-plan.md |
Patched | ~/.claude/get-shit-done/workflows/ |
complete-milestone.md |
Patched | ~/.claude/get-shit-done/workflows/ |
The integration defaults to the MGMT project and OSAC label. To change:
- Project prefix: Search for
MGMT-in the workflow files and replace with your project key - Label: Search for
-l OSACand replace with your label - Jira server: This is configured in jira-cli, not in these files
The grep -oE 'MGMT-[0-9]+' pattern extracts ticket keys from Jira CLI output. Update this regex if your project uses a different key format.