Jira Interaction

Skill
v1.0.1

Generic Jira update discipline for issue comments, transitions, and triage.

jira
tracker
project-management
Placeholders to fill:{{JIRA_PROJECT_KEY}}

Template Content

Jira Interaction (Read + Outbound Push)

The swarm has Jira OAuth support but no inbound sync. Use Atlassian REST API v3 through script credential bindings.

Authentication: script credential binding

Use the swarm-scripts skill and script-run (args first, ctx second) for every authenticated request. The API server resolves an OAuth authorization, refreshes it when needed, and substitutes [REDACTED:JIRA_OAUTH_ACCESS_TOKEN] in the request header only for the binding's allowed hosts. Never read credential tables, request a raw token from Lead, or copy a token into source, arguments, environment variables, logs, or task output.

Try the conventional binding key JIRA_OAUTH_ACCESS_TOKEN first, using Authorization: Bearer [REDACTED:JIRA_OAUTH_ACCESS_TOKEN] for api.atlassian.com. Do not ask Lead before the first try. credential-bindings is lead-only; workers cannot list bindings.

On HTTP 401 with the conventional key, report the missing binding or authorization to Lead as a blocker. The fetch wrapper in src/scripts-runtime/credential-broker/fetch-patch.ts drops any header with an unresolved [REDACTED: placeholder, so a missing binding sends an unauthenticated request that yields 401, never a credential leak.

Lead uses credential-bindings action list to check the binding. If missing, Lead registers/authorizes the provider and creates a binding visible to the agent with configKey: "JIRA_OAUTH_ACCESS_TOKEN", authKind: "oauth", oauthAuthorizationId, allowedHosts: ["api.atlassian.com"], and headerTemplate: "Authorization: Bearer [REDACTED:JIRA_OAUTH_ACCESS_TOKEN]".

An expiring authorization is refreshed server-side; a refresh failure, revoked/missing authorization, or persistent 401 needs Lead/user re-authorization through credential-bindings action oauth-authorize-url. Do not query expiry/token columns or loop on 401s.

Supported implementation: src/be/script-credential-broker.ts loads scoped relational bindings and calls resolveOAuthBindingToken from src/be/oauth-credential-bindings.ts. The scripts runtime substitutes placeholders at egress for allowed hosts.

Calling pattern

Allowed host: api.atlassian.com. Use the 3LO proxy https://api.atlassian.com/ex/jira/<CLOUD_ID>/rest/api/3, rather than the site hostname. Keep the site, cloud ID, and default project as deployment-specific values.

Run this source with script-run, supplying non-secret args: cloudId, path, optional method, query, and body.

export default async function (args, ctx) {
  const url = new URL(`https://api.atlassian.com/ex/jira/${encodeURIComponent(args.cloudId)}/rest/api/3/${args.path}`);
  for (const [key, value] of Object.entries(args.query ?? {})) {
    url.searchParams.set(key, String(value));
  }
  const response = await fetch(url, {
    method: args.method ?? "GET",
    headers: {
      Authorization: "Bearer [REDACTED:JIRA_OAUTH_ACCESS_TOKEN]",
      Accept: "application/json",
      ...(args.body ? { "Content-Type": "application/json" } : {}),
    },
    ...(args.body ? { body: JSON.stringify(args.body) } : {}),
  });
  if (!response.ok) throw new Error(`Jira HTTP ${response.status}: ${await response.text()}`);
  return response.status === 204 ? { success: true } : await response.json();
}

Return only the issue/project fields needed for the task, never headers or credentials. To discover the cloud ID, use the same script header in a GET to https://api.atlassian.com/oauth/token/accessible-resources; select the authorized site's resource ID and confirm its scopes permit the intended operation.

Common operations

Pass these paths and payloads to the script above. GET is the default.

OperationMethod and pathQuery or body
List projectsGET project/searchRead values for keys, names, IDs
Get project and issue typesGET project/<PROJECT_KEY>Read issueTypes before creating issues
Search issuesGET search/jqlquery: { jql: "project = <PROJECT> AND statusCategory != Done", fields: "summary,status,assignee,priority" }
Create issuePOST issuebody: { fields: { project: { key: "<PROJECT>" }, summary: "Short title", issuetype: { name: "Task" }, description: <ADF_DOC> } }
Discover transitionsGET issue/<KEY>/transitionsRead transitions for the target status
Transition issuePOST issue/<KEY>/transitionsbody: { transition: { id: "<TRANSITION_ID>" } }
CommentPOST issue/<KEY>/commentbody: { body: <ADF_DOC> }
Find accountGET user/searchquery: { query: "<name-or-email>" }
AssignPUT issue/<KEY>/assigneebody: { accountId: "<ACCOUNT_ID>" }; null unassigns
Edit fieldsPUT issue/<KEY>body: { fields: { summary: "New summary", labels: ["swarm"] } }

Issue creation returns { id, key, self } (201). The human URL is https://<your-site>.atlassian.net/browse/<KEY>. Transitions, assignment, and edits return 204 without a body.

ADF cheat-sheet

ADF = JSON tree. Always wrap content in { "type": "doc", "version": 1, "content": [...] }.

Common nodes:

  • Paragraph: { "type": "paragraph", "content": [ { "type": "text", "text": "hi" } ] }
  • Bold: { "type": "text", "text": "x", "marks": [{ "type": "strong" }] }
  • Code inline: { "type": "text", "text": "x", "marks": [{ "type": "code" }] }
  • Code block: { "type": "codeBlock", "attrs": { "language": "bash" }, "content": [ { "type": "text", "text": "echo hi" } ] }
  • Bullet list: { "type": "bulletList", "content": [ { "type": "listItem", "content": [ { "type": "paragraph", "content": [...] } ] } ] }
  • Link: { "type": "text", "text": "click", "marks": [{ "type": "link", "attrs": { "href": "https://..." } }] }

If you need rich content, build it in a script — don't try to write deep ADF inline in shell.

Operational rules

  • Discover transitions per issue; IDs vary by project and workflow.
  • Use /search/jql for cloud searches.
  • Descriptions, comments, and rich text fields require ADF documents.
  • Assignment uses account IDs rather than usernames.
  • For bulk work use the swarm-scripts skill, handle pagination, and back off on 429 using Retry-After.
  • Check every response before reporting success. On 400 inspect errorMessages/errors; on 403 confirm scopes and issue permissions with Lead; on 404 verify the cloud ID, project, and issue key. On refresh failure or persistent 401 request re-authorization.

Worked workflow: close an issue

  1. Run GET issue/<KEY>/transitions through the script and select the intended transition from the returned workflow.
  2. Run POST issue/<KEY>/transitions with the discovered transition ID.
  3. Confirm the 204 success before reporting the issue transitioned. For multiple issues, discover transitions for each issue and process pagination rather than assuming all workflows match.

The MCP tracker tools manage sync mappings; they do not replace this authenticated Jira request path.