Skip to content
Connect to Jira

How to connect Sortie to Jira

This guide configures Sortie to poll issues from a Jira project, dispatch agents, and transition issues through your Jira workflow. By the end, you will have a working WORKFLOW.md that authenticates against your Jira instance, fetches the right issues, and reports back status changes.

Jira Server and Data Center: This guide walks through the Cloud setup (REST API v3, Basic auth with email:token). If you are connecting to a self-hosted Jira Server or Data Center instance, see the Jira adapter reference for v2 configuration, Bearer/PAT authentication, and the api_version: "2" field.

Prerequisites

  • Sortie installed and on your PATH (installation guide)
  • Quick start completed with the file adapter (quick start)
  • A Jira Cloud instance with an API token, or a Jira Server / Data Center instance (see note above)
  • An API token from Atlassian account settings: Security: API tokens (Cloud) or a Personal Access Token from your instance profile (Server / Data Center)
  • Your Jira project key (the prefix on issue identifiers, PROJ in PROJ-42)
  • The workflow status names used in your project (e.g., “To Do”, “In Progress”, “Done”)

Create the API token

Generate a token in your Atlassian account settings. Sortie authenticates with Basic Auth for Cloud, which requires your email address and the token joined by a colon:

you@company.com:your-api-token-here

Both parts must be non-empty. Sortie validates this format at startup and rejects values without a colon or with an empty side.

Store the credentials in environment variables:

export SORTIE_JIRA_ENDPOINT="https://yourcompany.atlassian.net"
export SORTIE_JIRA_API_KEY="you@company.com:your-api-token-here"

The endpoint is the base URL of your Jira instance, no /rest/api/... suffix. Sortie rejects endpoints that include an API path.

Write the minimum configuration

Replace the tracker section in your WORKFLOW.md front matter:

WORKFLOW.md
---
tracker:
  kind: jira
  endpoint: $SORTIE_JIRA_ENDPOINT
  api_key: $SORTIE_JIRA_API_KEY
  project: PROJ
  active_states: [To Do, In Progress]
  terminal_states: [Done]

agent:
  kind: claude-code
---

Fix {{ .issue.identifier }}: {{ .issue.title }}

Three fields are required:

  • endpoint: Jira Cloud base URL. Sortie strips trailing slashes.
  • api_key: email:token format. Sent as a Base64-encoded Basic Auth header on every request.
  • project: Jira project key. Must not be empty.

The $VAR syntax expands environment variables at config load time. endpoint and project expand only when the entire value is a variable reference ($VAR or ${VAR}). api_key expands variables anywhere in the string, so $SORTIE_JIRA_API_KEY works both ways.

If you omit active_states, Sortie defaults to ["Backlog", "Selected for Development", "In Progress"]. Override this to match your project’s actual workflow status names. State names are compared case-insensitively; "to do" matches Jira’s "To Do".

Scope issues with a query filter

By default, Sortie fetches all issues in active_states for the project. The query_filter field appends a raw JQL fragment to narrow the result:

tracker:
  kind: jira
  endpoint: $SORTIE_JIRA_ENDPOINT
  api_key: $SORTIE_JIRA_API_KEY
  project: PROJ
  query_filter: "labels = 'agent-ready'"
  active_states: [To Do, In Progress]
  terminal_states: [Done]

Sortie wraps your fragment in AND (...) and appends it to the base query. The resulting JQL for this example:

project = "PROJ" AND status IN ("To Do", "In Progress") AND (labels = 'agent-ready') ORDER BY priority ASC, created ASC

Other useful filters:

# Only backend issues
query_filter: "component = Backend"

# Only issues assigned to me
query_filter: "assignee = currentUser()"

# Combination
query_filter: "component = Backend AND assignee = currentUser()"

The filter applies to candidate fetches and state-change polls. It does not apply to reconciliation lookups (ID-based fetches of issues already dispatched) because those issues already passed filtering at dispatch time.

Configure handoff state

When an agent completes its work, Sortie can transition the issue to a specific state (a review column, a QA queue, or any reachable status in your Jira workflow):

tracker:
  kind: jira
  endpoint: $SORTIE_JIRA_ENDPOINT
  api_key: $SORTIE_JIRA_API_KEY
  project: PROJ
  active_states: [To Do, In Progress]
  handoff_state: Human Review
  terminal_states: [Done]

Sortie uses the Jira transitions API: it fetches available transitions for the issue, finds one whose target status matches handoff_state (case-insensitive), and executes it. If no matching transition exists (because the Jira workflow does not allow it from the current status), Sortie logs an error:

level=ERROR msg="transition failed" error="tracker: tracker_payload: no transition to state \"Human Review\" available for issue PROJ-42"

Two constraints:

  • handoff_state must not collide with any value in terminal_states. Sortie rejects this at startup.
  • The transition must be available from the issue’s current Jira status. Check your Jira workflow diagram if transitions fail.

Configure dispatch-time transitions

Sortie can also transition an issue when the agent picks it up, moving it to an “In Progress” column so your team sees work has started:

tracker:
  kind: jira
  endpoint: $SORTIE_JIRA_ENDPOINT
  api_key: $SORTIE_JIRA_API_KEY
  project: PROJ
  active_states: [To Do, In Progress]
  in_progress_state: In Progress
  handoff_state: Human Review
  terminal_states: [Done]

in_progress_state must be a value in active_states. If the issue is already in that state at dispatch time, the transition is skipped (debug log only). If the transition fails for other reasons (for example, the Jira workflow does not allow it), Sortie logs a warning and continues. The agent session proceeds regardless.

Three constraints:

  • in_progress_state must appear in active_states. Otherwise reconciliation would cancel the worker after the state change.
  • in_progress_state must not collide with terminal_states or handoff_state.
  • The API token needs write permissions (same as handoff_state).

Enable tracker comments

Sortie can post comments on Jira issues at session lifecycle points (dispatch, completion, and failure). This creates a visible audit trail in the ticket without leaving Jira:

tracker:
  # ... existing fields ...
  comments:
    on_dispatch: true
    on_completion: true
    on_failure: true

Each flag is independent. Enable only the events you care about. All default to false.

Comment failures are non-fatal. Sortie logs a warning and continues. The API token needs the same write permissions as handoff_state (write:jira-work or write:issue:jira).

See the workflow config reference for comment content details.

Verify the connection

Validate syntax

Check your configuration without making API calls:

sortie validate ./WORKFLOW.md

This parses front matter, compiles the prompt template, and runs preflight checks. It catches missing fields, bad email:token format, and env vars that resolve to empty strings.

Test connectivity

Run a single poll cycle without dispatching agents:

sortie --dry-run ./WORKFLOW.md

Watch the logs. A successful poll produces:

level=INFO msg="tick completed" candidates=3 dispatched=0 running=0 retrying=0

candidates=3 means Sortie found 3 issues in your active states (and matching your query_filter, if set). dispatched=0 is expected in dry-run mode; no agents are launched.

If candidates=0 and you expected results, check that your active_states values match Jira’s status names exactly (comparison is case-insensitive, but the names must otherwise match) and that your query_filter JQL is valid.

Troubleshoot authentication and API errors

Wrong credentials or expired token

level=ERROR msg="poll failed" error="tracker: tracker_auth_error: GET /rest/api/3/search/jql: 401"

Verify your token is valid by testing it directly:

curl -s -u "you@company.com:your-api-token" \
  "https://yourcompany.atlassian.net/rest/api/3/myself" | head -5

If this returns your user profile, the token works. If it returns 401, regenerate the token.

CAPTCHA lockout

level=ERROR msg="poll failed" error="tracker: tracker_auth_error: GET /rest/api/3/search/jql: 401 (CAPTCHA challenge triggered; log in via browser to resolve)"

Jira locked the account after repeated failed attempts. Log in to Jira through a browser, complete the CAPTCHA, then restart Sortie.

Project not found

level=ERROR msg="poll failed" error="tracker: tracker_not_found: GET /rest/api/3/search/jql: not found"

The project key does not match any project in your Jira instance. Verify the key in Jira’s project settings; it is the short prefix, not the project name.

Rate limiting

level=WARN msg="poll failed" error="tracker: tracker_api: GET /rest/api/3/search/jql: rate limited (retry after 30 seconds)"

Jira enforces rate limits on API calls. Sortie does not throttle client-side; it logs the response and waits for the next poll interval. If you hit this repeatedly, increase polling.interval_ms or narrow your query_filter to reduce result set size. Sortie paginates with a page size of 50, so large projects generate multiple API calls per poll.

Unreachable handoff transition

level=ERROR msg="transition failed" error="tracker: tracker_payload: no transition to state \"Human Review\" available for issue PROJ-42"

The target state isn’t reachable from the issue’s current status in your Jira workflow. Open the Jira workflow editor and confirm that a transition exists from the expected source status to your handoff_state.

Full production example

---
tracker:
  kind: jira
  endpoint: $SORTIE_JIRA_ENDPOINT
  api_key: $SORTIE_JIRA_API_KEY
  project: PLATFORM
  query_filter: "labels = 'agent-ready'"
  active_states:
    - To Do
    - In Progress
  in_progress_state: In Progress
  handoff_state: Human Review
  terminal_states:
    - Done
    - Won't Do

polling:
  interval_ms: 60000

workspace:
  root: ~/workspace/sortie

agent:
  kind: claude-code
  max_turns: 3
---

You are a senior engineer. Your work is tracked by Sortie.

## Task

**{{ .issue.identifier }}**: {{ .issue.title }}
{{ if .issue.description }}

### Description

{{ .issue.description }}
{{ end }}
{{ if .issue.labels }}
**Labels:** {{ .issue.labels | join ", " }}
{{ end }}
{{ if .issue.url }}
**Ticket:** {{ .issue.url }}
{{ end }}

This configuration polls every 60 seconds, picks up issues labeled agent-ready in “To Do” or “In Progress,” runs up to 3 agent turns per issue, and moves completed issues to “Human Review.” For the full set of configuration options, see the WORKFLOW.md reference. For prompt template syntax, see How to write a prompt template.

Was this page helpful?