home / skills / 01000001-01001110 / agent-jira-skills / jira-issues

jira-issues skill

/jira-issues

This skill helps you manage Jira issues end-to-end by creating, reading, updating, and deleting Stories, Tasks, Bugs, and Epics.

npx playbooks add skill 01000001-01001110/agent-jira-skills --skill jira-issues

Review the files below or copy the command above to add this skill to your agents.

Files (5)
SKILL.md
8.4 KB
---
name: jira-issues
description: Create, read, update, and delete Jira issues. Use when managing Stories, Tasks, Bugs, or Epics - includes field updates and metadata.
---

# Jira Issues Skill

## Purpose
Create, read, update, and delete issues in Jira Cloud. Manage issue fields, transitions, and metadata.

## When to Use
- Creating new issues (Story, Task, Bug, Epic)
- Updating issue fields (summary, description, assignee, etc.)
- Reading issue details
- Deleting issues

## Prerequisites
- Authenticated JiraClient (see jira-auth skill)
- Project access permissions
- Issue type IDs for the target project

## Implementation Pattern

### Step 1: Define Issue Types

```typescript
interface JiraIssue {
  id: string;
  key: string;
  self: string;
  fields: {
    summary: string;
    description?: {
      type: 'doc';
      version: 1;
      content: Array<{
        type: string;
        content?: Array<{
          type: string;
          text: string;
        }>;
      }>;
    };
    status: { name: string; id: string };
    assignee?: { accountId: string; displayName: string };
    reporter?: { accountId: string; displayName: string };
    priority?: { name: string; id: string };
    issuetype: { name: string; id: string };
    project: { key: string; id: string };
    created: string;
    updated: string;
    labels?: string[];
    components?: Array<{ id: string; name: string }>;
  };
}

interface CreateIssueInput {
  projectKey: string;
  summary: string;
  issueType: 'Story' | 'Task' | 'Bug' | 'Epic' | string;
  description?: string;
  assigneeAccountId?: string;
  labels?: string[];
  priority?: string;
}
```

### Step 2: Create Issue

```typescript
async function createIssue(
  client: JiraClient,
  input: CreateIssueInput
): Promise<{ id: string; key: string; self: string }> {
  const body: any = {
    fields: {
      project: { key: input.projectKey },
      summary: input.summary,
      issuetype: { name: input.issueType },
    },
  };

  // Add description in Atlassian Document Format (ADF)
  if (input.description) {
    body.fields.description = {
      type: 'doc',
      version: 1,
      content: [
        {
          type: 'paragraph',
          content: [
            {
              type: 'text',
              text: input.description,
            },
          ],
        },
      ],
    };
  }

  if (input.assigneeAccountId) {
    body.fields.assignee = { id: input.assigneeAccountId };
  }

  if (input.labels) {
    body.fields.labels = input.labels;
  }

  if (input.priority) {
    body.fields.priority = { name: input.priority };
  }

  return client.request<{ id: string; key: string; self: string }>('/issue', {
    method: 'POST',
    body: JSON.stringify(body),
  });
}
```

### Step 3: Get Issue

```typescript
async function getIssue(
  client: JiraClient,
  issueKeyOrId: string,
  options: {
    fields?: string[];
    expand?: string[];
  } = {}
): Promise<JiraIssue> {
  const params = new URLSearchParams();
  if (options.fields) params.set('fields', options.fields.join(','));
  if (options.expand) params.set('expand', options.expand.join(','));

  const query = params.toString() ? `?${params.toString()}` : '';
  return client.request<JiraIssue>(`/issue/${issueKeyOrId}${query}`);
}
```

### Step 4: Update Issue

```typescript
interface UpdateIssueInput {
  summary?: string;
  description?: string;
  assigneeAccountId?: string | null;
  labels?: string[];
  priority?: string;
}

async function updateIssue(
  client: JiraClient,
  issueKeyOrId: string,
  input: UpdateIssueInput
): Promise<void> {
  const body: any = { fields: {} };

  if (input.summary) {
    body.fields.summary = input.summary;
  }

  if (input.description !== undefined) {
    body.fields.description = input.description
      ? {
          type: 'doc',
          version: 1,
          content: [
            {
              type: 'paragraph',
              content: [{ type: 'text', text: input.description }],
            },
          ],
        }
      : null;
  }

  if (input.assigneeAccountId !== undefined) {
    body.fields.assignee = input.assigneeAccountId
      ? { id: input.assigneeAccountId }
      : null;
  }

  if (input.labels) {
    body.fields.labels = input.labels;
  }

  if (input.priority) {
    body.fields.priority = { name: input.priority };
  }

  await client.request(`/issue/${issueKeyOrId}`, {
    method: 'PUT',
    body: JSON.stringify(body),
  });
}
```

### Step 5: Delete Issue

```typescript
async function deleteIssue(
  client: JiraClient,
  issueKeyOrId: string,
  deleteSubtasks: boolean = false
): Promise<void> {
  const query = deleteSubtasks ? '?deleteSubtasks=true' : '';
  await client.request(`/issue/${issueKeyOrId}${query}`, {
    method: 'DELETE',
  });
}
```

### Step 6: Bulk Create Issues

```typescript
async function bulkCreateIssues(
  client: JiraClient,
  issues: CreateIssueInput[]
): Promise<Array<{ id: string; key: string; self: string }>> {
  const results: Array<{ id: string; key: string; self: string }> = [];

  // Jira doesn't have a native bulk create, so we batch with Promise.all
  const batches = [];
  const batchSize = 10;

  for (let i = 0; i < issues.length; i += batchSize) {
    batches.push(issues.slice(i, i + batchSize));
  }

  for (const batch of batches) {
    const batchResults = await Promise.all(
      batch.map((issue) => createIssue(client, issue))
    );
    results.push(...batchResults);
  }

  return results;
}
```

## curl Examples

### Create Issue
```bash
curl -X POST "$JIRA_BASE_URL/rest/api/3/issue" \
  -H "Authorization: Basic $(echo -n 'email:token' | base64)" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": {
      "project": { "key": "SCRUM" },
      "summary": "New feature implementation",
      "issuetype": { "name": "Story" },
      "description": {
        "type": "doc",
        "version": 1,
        "content": [
          {
            "type": "paragraph",
            "content": [{ "type": "text", "text": "Description here" }]
          }
        ]
      }
    }
  }'
```

### Get Issue
```bash
curl -X GET "$JIRA_BASE_URL/rest/api/3/issue/SCRUM-123" \
  -H "Authorization: Basic $(echo -n 'email:token' | base64)" \
  -H "Accept: application/json"
```

### Update Issue
```bash
curl -X PUT "$JIRA_BASE_URL/rest/api/3/issue/SCRUM-123" \
  -H "Authorization: Basic $(echo -n 'email:token' | base64)" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": {
      "summary": "Updated summary"
    }
  }'
```

### Delete Issue
```bash
curl -X DELETE "$JIRA_BASE_URL/rest/api/3/issue/SCRUM-123" \
  -H "Authorization: Basic $(echo -n 'email:token' | base64)"
```

## API Endpoints Summary

| Operation | Method | Path |
|-----------|--------|------|
| Create issue | POST | `/issue` |
| Get issue | GET | `/issue/{issueIdOrKey}` |
| Update issue | PUT | `/issue/{issueIdOrKey}` |
| Delete issue | DELETE | `/issue/{issueIdOrKey}` |

## Required Fields by Issue Type

### Story
- `project.key` (required)
- `summary` (required)
- `issuetype.name` = "Story" (required)

### Task
- `project.key` (required)
- `summary` (required)
- `issuetype.name` = "Task" (required)

### Bug
- `project.key` (required)
- `summary` (required)
- `issuetype.name` = "Bug" (required)
- `description` (recommended)

## Description Format (ADF)

Jira uses Atlassian Document Format for rich text:

```json
{
  "type": "doc",
  "version": 1,
  "content": [
    {
      "type": "paragraph",
      "content": [
        { "type": "text", "text": "Normal text" }
      ]
    },
    {
      "type": "paragraph",
      "content": [
        {
          "type": "text",
          "text": "Bold text",
          "marks": [{ "type": "strong" }]
        }
      ]
    }
  ]
}
```

## Common Mistakes
- Using plain text for description instead of ADF format
- Not using account ID for assignee (email doesn't work)
- Forgetting project key in create request
- Using issue type name that doesn't exist in project

## References
- [Issues API](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/)
- [Atlassian Document Format](https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/)

## Version History
- 2025-12-10: Created

Overview

This skill provides a lightweight Jira Cloud issues integration to create, read, update, and delete Stories, Tasks, Bugs, and Epics. It wraps Jira REST API calls and handles common field formats like Atlassian Document Format (ADF) for rich descriptions. Use it when you need programmatic issue management from Node.js or Python scripts via a shared JiraClient.

How this skill works

The skill exposes operations to POST new issues, GET issue details, PUT updates, and DELETE issues using the Jira REST API /issue endpoints. It builds request bodies with required fields (project key, issue type, summary) and converts plain descriptions into ADF structure. It also supports batching for bulk creates by executing concurrent create calls in controlled batches.

When to use it

  • Create new Stories, Tasks, Bugs, or Epics programmatically from scripts or CI jobs.
  • Read issue fields, status, assignee, labels, and metadata for reporting or automation.
  • Update summaries, descriptions, assignees, priorities, and labels without manual UI changes.
  • Delete issues or remove subtasks as part of cleanup workflows.
  • Bulk-create multiple issues with batching to avoid rate limits.

Best practices

  • Always supply the project.key and a valid issuetype.name supported by the target project.
  • Convert descriptions to Atlassian Document Format (ADF) to preserve rich-text and avoid API errors.
  • Use accountId for assignee and reporter fields — email addresses are not accepted.
  • Batch bulk creates (e.g., size 10) to balance throughput and API rate limits.
  • Request only needed fields with the fields query parameter to reduce payload size.

Example use cases

  • Create a Story with ADF description, assign it to a user, and set priority via an automated backlog import.
  • Fetch an issue by key to display summary, status, assignee, and labels in a custom dashboard.
  • Update a Bug’s description and change its assignee as part of a triage automation.
  • Delete obsolete test issues and optionally remove their subtasks during project cleanup.
  • Bulk-create tasks from a CSV input for sprint planning scripts.

FAQ

What format does the description need to be in?

Descriptions should use Atlassian Document Format (ADF). The skill will convert a plain string into a simple ADF paragraph block when provided.

How do I assign an issue to a user?

Provide the assignee accountId (not email) in the assignee field. To unassign, pass null for the assignee field in updates.