Sync Jira
For Claude: This is a sync command. Execute phases 1-3 in order. Model requirement: This skill MUST run on Sonnet 4.6 (claude-s…
# Beads <> Jira Mirror Sync Implementation Plan
> **For Claude:** This is a sync command. Execute phases 1-3 in order.
>
> **Model requirement:** This skill MUST run on **Sonnet 4.6** (`claude-sonnet-4-6`),
> not Opus. The work is mechanical (API calls, data mapping, pagination) and does
> not require deep reasoning. If you are running on Opus, tell the user:
> "Switch to Sonnet 4.6 with `/model sonnet` before running this sync."
**Goal:** Sync all Beads epics and tasks to Jira with full metadata,
hierarchy, and dependencies using a 3-phase pipeline: collect, diff, execute.
**Target:** Your Jira Cloud instance and project (configured in `.sync/jira-config.json`).
**Architecture:** Python scripts handle data processing (export, matching,
delta detection, payload building). Claude calls Jira MCP tools directly for
all API operations. No Python touches the Jira API.
---
## MCP Tools Reference
| MCP Tool | Purpose |
|----------|---------|
| `searchJiraIssuesUsingJql` | Fetch all issues in project via JQL |
| `getJiraIssue` | Fetch single issue with fields |
| `createJiraIssue` | Create new issue |
| `editJiraIssue` | Update issue fields |
| `transitionJiraIssue` | Change issue status |
| `getTransitionsForJiraIssue` | List available status transitions |
| `jiraWrite` | Create issue links (dependencies) |
| `jiraRead` | Get issue link types |
> **Note:** Custom field CREATION requires Jira Admin UI (no MCP tool exists).
> Custom field UPDATES use `editJiraIssue` with the field in `fields` param.
> Issue deletion may not be supported via MCP -- see Step 1.5.
---
## First-Run Setup
Before the first sync, complete the setup in the README:
1. Create a Jira Cloud project (if needed)
2. Create an API token
3. Create 6 custom fields in Jira
4. Run `python3 scripts/discover-jira-config.py` to auto-discover all IDs
5. Install the Atlassian MCP plugin in Claude Code
If `.sync/jira-config.json` doesn't exist yet, tell the user to run the
discovery script first.
---
## Built-in Field Mappings
Unlike Asana, Jira has built-in fields for priority, issue type, and status.
These do NOT need custom fields.
### Priority (built-in)
| Beads Priority | Jira Priority |
|----------------|---------------|
| P0 | Highest |
| P1 | High |
| P2 | Medium |
| P3 | Low |
| P4 | Lowest |
### Issue Types (built-in)
| Beads Type | Jira Issue Type |
|------------|-----------------|
| epic | Epic |
| task | Task |
| bug | Bug |
| feature | Story |
| chore | Task |
| decision | Task |
### Status (via transitions -- NOT settable as a field)
**CRITICAL:** Status CANNOT be set as a field in Jira. You MUST use
`transitionJiraIssue` with the transition ID from `.sync/jira-config.json`.
Setting status as a field is silently ignored.
---
## Custom Field Value Formats
When custom fields are configured (non-null in `.sync/jira-config.json`):
**Short text (Beads ID):**
```json
"customfield_XXXXX": "myproject-abc.1"
Date (Created/Updated Beads):
"customfield_XXXXX": "2026-02-15"
Number (Chain Depth):
"customfield_XXXXX": 3
Dropdown/Select (Execution Status, Execution Wave):
"customfield_XXXXX": {"value": "Critical Blocker"}
Prerequisites
- Beads CLI available (
bdcommand) .sync/jira-config.jsonpopulated (viadiscover-jira-config.py)- Atlassian MCP tools active in Claude Code session
.sync/directory exists (created by export script)
Phase 1: Collect & Diff (~2 min)
Step 1: Export Beads data
chmod +x scripts/export-beads.sh && ./scripts/export-beads.sh
This runs bd export and converts the JSONL output to a JSON array at
.sync/beads-export.json. Dependencies are embedded inline — no merge
step needed.
Step 2: Fetch Jira snapshot (or skip via delta mode)
Before fetching: Read custom_fields from .sync/jira-config.json and use
those exact field IDs in the fields array. Do NOT guess or use placeholder IDs.
Check .sync/jira-sync-state.json for last_synced_at:
DELTA MODE (last_synced_at exists — typical re-run)
Since Jira is a write-only mirror (no manual Jira edits), we can detect changes entirely from the Beads export without fetching Jira at all:
- Filter
.sync/beads-export.jsonto issues whereupdated_at > last_synced_at - Look up each changed issue's
jira_keyin.sync/jira-gid-mapping.json - Any issue NOT in the mapping → needs a create (add to "create" list)
- All issues found in mapping → build update payloads directly (no Jira fetch)
- SKIP
.sync/jira-export.jsonentirely — not needed for delta runs - Phase 1.3 (sync-diff) becomes: just build payloads for the N changed issues
Phase 1 MCP calls in delta mode: 0 (pure local file operations)
FULL MODE (no last_synced_at — first run or forced refresh)
Claude calls MCP directly:
Call searchJiraIssuesUsingJql:
cloudId: "<from jira-config.json>"
jql: "project = <PROJECT_KEY> ORDER BY issuekey ASC"
maxResults: 100
fields: ["summary", "description", "status", "issuetype", "priority", "parent", <custom_field_ids from jira-config.json>]
**CRITICAL:** Extract issue data from the response's `issues` array.
Each item has `key`, `id`, and `fields`.
Save to .sync/jira-export.json as:
{
"fetched_at": "<ISO timestamp>",
"project_key": "<PROJECT_KEY>",
"data": [ ... all issues ... ]
}
Pagination: After fetching page 1, check if issues.totalCount == 100
(the max per request). If so, get the highest issue key returned (e.g.,
PROJ-199) and fetch again:
jql: "project = <PROJECT_KEY> AND issuekey > PROJ-199 ORDER BY issuekey ASC"
Repeat until the response returns fewer than 100 issues. Merge all pages
into the same data array.
Do NOT use nextPageToken — the Jira MCP does not return this field.
After Phase 3 (verify) passes
Write .sync/jira-sync-state.json:
{"last_synced_at": "<start-of-sync ISO timestamp>", "issue_count": N}
Use the timestamp ```
Maintain Sync Jira?
Let people know it's listed here — add the badge (live metrics, light/dark aware) or a plain link to your README or docs.
[Sync Jira on getagentictools](https://getagentictools.com/loops/arkevo-beads-jira-mirror-sync-implementation-plan?ref=badge)