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

jira-transitions skill

/jira-transitions

This skill helps you move Jira issues through transitions, fetch available states, and set resolutions across tasks to streamline workflow.

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

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

Files (3)
SKILL.md
8.5 KB
---
name: jira-transitions
description: Move Jira issues through workflow states. Use when transitioning issues (To Do, In Progress, Done) or setting resolutions.
---

# Jira Transitions Skill

## Purpose
Move issues through workflow states. Get available transitions and execute status changes.

## When to Use
- Moving issues to different statuses (To Do → In Progress → Done)
- Getting available transitions for an issue
- Bulk transitioning issues
- Setting resolution when closing issues

## Prerequisites
- Authenticated JiraClient (see jira-auth skill)
- Issue transition permissions
- Knowledge of workflow structure

## Important Notes

**Transition IDs are NOT standardized** - they vary by:
- Jira instance
- Project
- Workflow configuration

**Always query available transitions first** before attempting to transition.

## Implementation Pattern

### Step 1: Define Types

```typescript
interface Transition {
  id: string;
  name: string;
  to: {
    id: string;
    name: string;
    statusCategory: {
      id: number;
      key: string;
      name: string;
    };
  };
  fields?: Record<string, {
    required: boolean;
    name: string;
    allowedValues?: Array<{ id: string; name: string }>;
  }>;
}

interface TransitionsResponse {
  transitions: Transition[];
}
```

### Step 2: Get Available Transitions

```typescript
async function getTransitions(
  client: JiraClient,
  issueKeyOrId: string
): Promise<Transition[]> {
  const response = await client.request<TransitionsResponse>(
    `/issue/${issueKeyOrId}/transitions?expand=transitions.fields`
  );
  return response.transitions;
}
```

### Step 3: Find Transition by Name

```typescript
async function findTransitionByName(
  client: JiraClient,
  issueKeyOrId: string,
  targetStatusName: string
): Promise<Transition | null> {
  const transitions = await getTransitions(client, issueKeyOrId);
  return transitions.find(
    t => t.name.toLowerCase() === targetStatusName.toLowerCase() ||
         t.to.name.toLowerCase() === targetStatusName.toLowerCase()
  ) || null;
}
```

### Step 4: Execute Transition

```typescript
interface TransitionOptions {
  resolution?: { name: string } | { id: string };
  comment?: string;
  fields?: Record<string, any>;
}

async function transitionIssue(
  client: JiraClient,
  issueKeyOrId: string,
  transitionId: string,
  options: TransitionOptions = {}
): Promise<void> {
  const body: any = {
    transition: { id: transitionId },
  };

  if (options.resolution || options.fields) {
    body.fields = { ...options.fields };
    if (options.resolution) {
      body.fields.resolution = options.resolution;
    }
  }

  if (options.comment) {
    body.update = {
      comment: [
        {
          add: {
            body: {
              type: 'doc',
              version: 1,
              content: [
                {
                  type: 'paragraph',
                  content: [{ type: 'text', text: options.comment }],
                },
              ],
            },
          },
        },
      ],
    };
  }

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

### Step 5: High-Level Transition Helper

```typescript
async function moveIssueTo(
  client: JiraClient,
  issueKeyOrId: string,
  targetStatus: string,
  options: TransitionOptions = {}
): Promise<boolean> {
  const transition = await findTransitionByName(client, issueKeyOrId, targetStatus);

  if (!transition) {
    console.error(`No transition found to status: ${targetStatus}`);
    return false;
  }

  // Check if resolution is required
  if (transition.fields?.resolution?.required && !options.resolution) {
    // Default to "Done" resolution
    options.resolution = { name: 'Done' };
  }

  await transitionIssue(client, issueKeyOrId, transition.id, options);
  return true;
}
```

### Step 6: Bulk Transition

```typescript
async function bulkTransition(
  client: JiraClient,
  issueKeys: string[],
  targetStatus: string,
  options: TransitionOptions = {}
): Promise<{ success: string[]; failed: string[] }> {
  const results = { success: [] as string[], failed: [] as string[] };

  for (const issueKey of issueKeys) {
    try {
      const success = await moveIssueTo(client, issueKey, targetStatus, options);
      if (success) {
        results.success.push(issueKey);
      } else {
        results.failed.push(issueKey);
      }
    } catch (error) {
      results.failed.push(issueKey);
    }
  }

  return results;
}
```

## Common Transitions

Most Jira projects have these standard transitions:

| From Status | Transition Name | To Status |
|-------------|-----------------|-----------|
| To Do | Start Progress | In Progress |
| In Progress | Done | Done |
| In Progress | Stop Progress | To Do |
| Done | Reopen | To Do |

**Note**: These names vary by workflow configuration.

## Resolution Values

When transitioning to "Done", you often need a resolution:

| Resolution | Description |
|------------|-------------|
| Done | Work completed |
| Won't Do | Not planning to do |
| Duplicate | Already exists |
| Cannot Reproduce | Cannot reproduce issue |

## curl Examples

### Get Available Transitions
```bash
curl -X GET "$JIRA_BASE_URL/rest/api/3/issue/SCRUM-123/transitions?expand=transitions.fields" \
  -H "Authorization: Basic $(echo -n 'email:token' | base64)" \
  -H "Accept: application/json"
```

### Execute Transition (Simple)
```bash
curl -X POST "$JIRA_BASE_URL/rest/api/3/issue/SCRUM-123/transitions" \
  -H "Authorization: Basic $(echo -n 'email:token' | base64)" \
  -H "Content-Type: application/json" \
  -d '{
    "transition": { "id": "21" }
  }'
```

### Transition with Resolution (for Done)
```bash
curl -X POST "$JIRA_BASE_URL/rest/api/3/issue/SCRUM-123/transitions" \
  -H "Authorization: Basic $(echo -n 'email:token' | base64)" \
  -H "Content-Type: application/json" \
  -d '{
    "transition": { "id": "31" },
    "fields": {
      "resolution": { "name": "Done" }
    }
  }'
```

### Transition with Comment
```bash
curl -X POST "$JIRA_BASE_URL/rest/api/3/issue/SCRUM-123/transitions" \
  -H "Authorization: Basic $(echo -n 'email:token' | base64)" \
  -H "Content-Type: application/json" \
  -d '{
    "transition": { "id": "21" },
    "update": {
      "comment": [
        {
          "add": {
            "body": {
              "type": "doc",
              "version": 1,
              "content": [
                {
                  "type": "paragraph",
                  "content": [{ "type": "text", "text": "Moving to In Progress" }]
                }
              ]
            }
          }
        }
      ]
    }
  }'
```

## API Response (204 No Content)

A successful transition returns **204 No Content** with an empty body.

## Error Handling

### Common Errors

| Error | Cause | Solution |
|-------|-------|----------|
| 400 Bad Request | Invalid transition ID | Query transitions first |
| 400 Bad Request | Missing required resolution | Add resolution field |
| 403 Forbidden | No permission to transition | Check workflow permissions |
| 404 Not Found | Issue doesn't exist | Verify issue key |

### Error Response Example
```json
{
  "errorMessages": [
    "You must specify a resolution when transitioning issues to the 'Done' status."
  ],
  "errors": {
    "resolution": "Resolution is required."
  }
}
```

## Workflow Discovery Pattern

```typescript
async function discoverWorkflow(
  client: JiraClient,
  issueKeyOrId: string
): Promise<Map<string, Transition[]>> {
  // Get transitions from current state
  const transitions = await getTransitions(client, issueKeyOrId);

  console.log(`Available transitions from current state:`);
  for (const t of transitions) {
    console.log(`  ${t.id}: ${t.name} → ${t.to.name}`);
    if (t.fields?.resolution?.required) {
      console.log(`    (requires resolution)`);
    }
  }

  return new Map([
    ['current', transitions]
  ]);
}
```

## Common Mistakes
- Using transition ID without querying first
- Forgetting resolution when moving to Done
- Assuming transition IDs are same across projects
- Not handling 204 response (empty body is success)

## References
- [Transitions API](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/#api-rest-api-3-issue-issueidorkey-transitions-get)

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

Overview

This skill moves Jira issues through workflow states and sets resolutions when required. It exposes helpers to list available transitions, find a transition by name, execute a transition, and run bulk transitions. Use it to reliably move issues (e.g., To Do → In Progress → Done) while handling required fields and comments.

How this skill works

The skill queries the Jira Cloud Transitions API to retrieve available transitions for a specific issue, including required fields. It locates a transition by name or target status, prepares the request body (including resolution, fields, and comments), and posts the transition to Jira. It returns success/failure results and supports bulk processing by iterating per-issue and collecting outcomes.

When to use it

  • Move a single issue to a different status (Start Progress, Done, Reopen).
  • Close an issue and set the resolution (Done, Won't Do, Duplicate).
  • Discover available transitions before attempting a change to avoid invalid IDs.
  • Bulk-transition multiple issues to a target state (e.g., close a sprint).
  • Add a comment while performing a transition for audit/context.

Best practices

  • Always call the available-transitions endpoint first; transition IDs vary by project and workflow.
  • Check transition.fields for required resolution or custom fields and provide them in fields when required.
  • Handle 204 No Content as success; the API returns an empty body on a successful transition.
  • Fail fast and record per-issue errors when doing bulk operations to avoid partial ambiguity.
  • Use case-insensitive matching on transition.name and transition.to.name to find the correct transition reliably.

Example use cases

  • Move SCRUM-123 from To Do to In Progress and add a comment explaining the change.
  • Close a set of issues at sprint end, supplying resolution: { name: 'Done' }.
  • Query available transitions for an issue to build a UI that shows allowed next statuses.
  • Bulk reopen a backlog of issues by applying the Reopen transition to multiple keys.
  • Automate CI/CD hooks to move issues to In Progress when work starts and to Done when deployments succeed.

FAQ

Why did my transition request return 400?

Typically the transition ID is invalid for that issue or a required field (often resolution) is missing; query available transitions and inspect transition.fields for required entries.

How do I know which resolution value to send?

Use resolution names used by your Jira instance (e.g., Done, Won't Do). Query your project or use an existing issue to read allowed resolution values if uncertain.

What indicates a successful transition?

A 204 No Content response indicates success. Do not expect a JSON body on success; treat 204 as confirmation.