Block pull requests automatically when associated Jira tickets have blocking workflow statuses like "Blocked", "On Hold", or "Requires fixing".
Perfect for teams practicing trunk-based development or using feature branches where multiple commits reference different Jira tickets.
- π Scans PR title and all commits - Extracts ticket references from PR title and every commit message
- π― Smart deduplication - Only checks each unique ticket once
- π« Configurable blocking - Define which Jira statuses should block merging
- π Bypass mechanism - Emergency hotfixes can skip validation with keywords
- π Beautiful summaries - See ticket status overview directly in GitHub Actions
- β‘ Fast and reliable - Uses Jira REST API v3 with efficient queries
- π Secure - Uses GitHub secrets for credentials
- π¨ Flexible - Works with any Jira ticket prefix (PROJ, JIRA, DEV, etc.)
- π·οΈ Position agnostic - Finds tickets anywhere in PR title or commit messages
- π€ Case insensitive - Matches tickets regardless of case
Create .github/workflows/jira-check.yml:
name: Jira Ticket Status Check
on:
pull_request:
types: [opened, edited, synchronize]
jobs:
check-jira:
runs-on: ubuntu-latest
steps:
- uses: designcise/jira-ticket-status-action@v2
with:
jira-base-url: ${{ secrets.JIRA_BASE_URL }}
jira-email: ${{ secrets.JIRA_EMAIL }}
jira-api-token: ${{ secrets.JIRA_API_TOKEN }}
ticket-prefix: 'PROJ' # Your Jira project prefixGo to Settings β Secrets and variables β Actions and add:
| Secret | Example Value | How to Get |
|---|---|---|
JIRA_BASE_URL |
https://company.atlassian.net |
Your Jira instance URL |
JIRA_EMAIL |
bot@company.com |
Email of Jira user |
JIRA_API_TOKEN |
ATATT3xFfGF0... |
Generate here |
Go to Settings β Branches β Branch protection rules and:
- β Enable "Require status checks to pass before merging"
- β
Select
check-jira(or your job name)
Done! π
- uses: designcise/jira-ticket-status-action@v2
with:
jira-base-url: ${{ secrets.JIRA_BASE_URL }}
jira-email: ${{ secrets.JIRA_EMAIL }}
jira-api-token: ${{ secrets.JIRA_API_TOKEN }}
ticket-prefix: 'TEAM'Default blocked statuses: Requires fixing, Blocked, On Hold, Waiting for Dependency
Default bypass keyword: noticket
Define exactly which statuses should block PRs:
- uses: designcise/jira-ticket-status-action@v2
with:
jira-base-url: ${{ secrets.JIRA_BASE_URL }}
jira-email: ${{ secrets.JIRA_EMAIL }}
jira-api-token: ${{ secrets.JIRA_API_TOKEN }}
ticket-prefix: 'PROJ'
blocked-statuses: 'Blocked, Paused, Rejected, Needs Clarification'Allow different keywords for bypassing the check:
- uses: designcise/jira-ticket-status-action@v2
with:
jira-base-url: ${{ secrets.JIRA_BASE_URL }}
jira-email: ${{ secrets.JIRA_EMAIL }}
jira-api-token: ${{ secrets.JIRA_API_TOKEN }}
ticket-prefix: 'DEV'
bypass-keywords: 'noticket, hotfix, emergency, skip-jira'PR titles that bypass:
[noticket] Update dependencies[HOTFIX] Critical security patchemergency: Fix memory leakskip-jira Refactor tests[no-ticket] Quick fix(handles variations)
Check tickets from different projects:
jobs:
check-project-a:
runs-on: ubuntu-latest
steps:
- uses: designcise/jira-ticket-status-action@v2
with:
jira-base-url: ${{ secrets.JIRA_BASE_URL }}
jira-email: ${{ secrets.JIRA_EMAIL }}
jira-api-token: ${{ secrets.JIRA_API_TOKEN }}
ticket-prefix: 'PROJA'
check-project-b:
runs-on: ubuntu-latest
steps:
- uses: designcise/jira-ticket-status-action@v2
with:
jira-base-url: ${{ secrets.JIRA_BASE_URL }}
jira-email: ${{ secrets.JIRA_EMAIL }}
jira-api-token: ${{ secrets.JIRA_API_TOKEN }}
ticket-prefix: 'PROJB'Make decisions based on check results:
- name: Check Jira tickets
id: jira
uses: designcise/jira-ticket-status-action@v2
with:
jira-base-url: ${{ secrets.JIRA_BASE_URL }}
jira-email: ${{ secrets.JIRA_EMAIL }}
jira-api-token: ${{ secrets.JIRA_API_TOKEN }}
ticket-prefix: 'PROJ'
- name: Notify team if blocked
if: steps.jira.outputs.has-blocked == 'true'
run: |
echo "Blocked tickets: ${{ steps.jira.outputs.blocked-tickets }}"
# Send Slack notification, create comment, etc.Run the check only when merging to production branches:
name: Jira Ticket Status Check
on:
pull_request:
types: [opened, edited, synchronize]
branches:
- main
- production
- staging
jobs:
check-jira:
runs-on: ubuntu-latest
steps:
- uses: designcise/jira-ticket-status-action@v2
with:
jira-base-url: ${{ secrets.JIRA_BASE_URL }}
jira-email: ${{ secrets.JIRA_EMAIL }}
jira-api-token: ${{ secrets.JIRA_API_TOKEN }}
ticket-prefix: 'PROJ'| Input | Description | Required | Default |
|---|---|---|---|
jira-base-url |
Jira instance URL (e.g., https://company.atlassian.net) |
Yes | - |
jira-email |
Email address for Jira API authentication | Yes | - |
jira-api-token |
API token for authentication (How to create) | Yes | - |
ticket-prefix |
Jira ticket prefix (e.g., PROJ, JIRA, DEV) |
Yes | - |
blocked-statuses |
Comma-separated list of Jira statuses that should block PRs | No | Requires fixing, Blocked, On Hold, Waiting for Dependency |
bypass-keywords |
Comma-separated list of keywords in PR title to bypass check | No | noticket |
| Output | Type | Description |
|---|---|---|
blocked-tickets |
JSON Array | List of tickets with blocking statuses (e.g., [{"key":"PROJ-123","status":"Blocked"}]) |
all-tickets |
JSON Array | All unique tickets found in PR title and commits |
has-blocked |
Boolean | true if any ticket has a blocking status, false otherwise |
1. Check for bypass keywords in PR title
β
If found β Skip all checks β
If not found β Continue
2. Extract tickets from PR title
β
Scan PR title for {PREFIX}-{number}
Examples:
- "PROJ-100: Add feature" β Extract "PROJ-100"
- "[PROJ-100] Fix bug" β Extract "PROJ-100"
- "Fix for proj-100" β Extract "PROJ-100" (case insensitive)
3. Extract tickets from commits
β
Scan all commit messages for {PREFIX}-{number}
Examples:
- "PROJ-100: Add feature" β Extract "PROJ-100"
- "chore(PROJ-100): test" β Extract "PROJ-100"
- "Fix PROJ-100 and PROJ-101" β Extract both
Remove duplicates using Set
4. Check Jira status
β
For each unique ticket (from PR title + commits):
- Query Jira API: /rest/api/3/issue/{key}?fields=status
- Get current workflow status
5. Validate & Block
β
If any ticket status is in blocked-statuses list:
- β Fail the check
- π« Block PR merge
- π Show summary table
Otherwise:
- β
Pass the check
- π Allow merge
The action finds tickets in your PR title (case-insensitive):
PROJ-123: Add user authentication
[PROJ-123] Implement feature
proj-123: add feature
PROJ-123 and PROJ-456: Fix bugs
The action finds tickets anywhere in the commit message. All these formats work:
PROJ-123: Add user authentication
feat(PROJ-123): add login page
chore(PROJ-123): update dependencies
fix(PROJ-123): resolve memory leak
[PROJ-123] Implement feature
Fix PROJ-123 and PROJ-456
Add feature for PROJ-123
proj-123: lowercase still works
PROJ-123: UPPERCASE WORKS TOO
PrOj-123: MiXeD CaSe WoRkS
PR Title: PROJ-200: Implement payment gateway
Configuration:
ticket-prefix: 'PROJ'Commits:
abc1234 PROJ-200: Add Stripe integration
def5678 chore(PROJ-200): Add tests
ghi9012 Fix linting issues
Result:
- Finds 1 unique ticket:
PROJ-200(from PR title + commits) - Checks status:
In Progressβ - Decision: PASS - PR can be merged
PR Title: PROJ-2400: Test jira-blocked tickets don't get merged
Configuration:
ticket-prefix: 'PROJ'
blocked-statuses: 'Blocked, Paused'Commits:
123abcd PROJ-2056: Feature A (status: Done)
456efgh PROJ-2112: Feature B (status: Done)
Result:
- Finds 3 unique tickets:
PROJ-2400(from title),PROJ-2056,PROJ-2112(from commits) - Checks statuses:
PROJ-2400:BlockedβPROJ-2056:DoneβPROJ-2112:Doneβ
- Decision: FAIL - Cannot merge due to PROJ-2400
Action Summary:
Jira Ticket Status Check
βββββββββββββ¬βββββββββββββ¬βββββββββββββββ
β Ticket β Status β Result β
βββββββββββββΌβββββββββββββΌββββββββββββ ββ€
β PROJ-2400 β Blocked β β BLOCKED β
β PROJ-2056 β Done β β
OK β
β PROJ-2112 β Done β β
OK β
βββββββββββββ΄βββββββββββββ΄βββββββββββββββ
β The following tickets have blocking statuses:
- PROJ-2400: Blocked
PR Title: Merge develop to staging
Configuration:
ticket-prefix: 'PROJ'Commits:
123abcd PROJ-100: Feature A
456efgh feat(PROJ-101): Feature B
789ijkl PROJ-102: Feature C
012mnop Fix typo in PROJ-100
Result:
- No tickets in PR title
- Finds 3 unique tickets from commits:
PROJ-100,PROJ-101,PROJ-102 - Checks statuses:
PROJ-100:DoneβPROJ-101:BlockedβPROJ-102:In Reviewβ
- Decision: FAIL - Cannot merge due to PROJ-101
PR Title: [noticket] Emergency production fix for memory leak
Result:
- Detects bypass keyword:
noticket - Decision: PASS - Skips all checks
- GitHub Actions notice:
Bypassing Jira check due to keyword: "noticket"
Configuration:
bypass-keywords: 'noticket, hotfix, emergency'PR Titles that bypass:
[noticket] Update dependenciesβ Matchesnoticket[HOTFIX] Critical patchβ Matcheshotfixemergency: Fix productionβ Matchesemergency[no-ticket] Quick fixβ Matchesnoticket(handles variations)
Create a dedicated Jira user for CI/CD:
Email: github-ci@company.com
Name: GitHub CI Bot
Permissions: Browse Projects (read-only)
Benefits:
- β Audit trail of API usage
- β Minimal permissions (least privilege)
- β Easy to rotate credentials
- β No personal API tokens in CI
- Never commit secrets to your repository
- Use environment secrets for organization-wide sharing
- Rotate API tokens regularly (recommended: every 90 days)
- Review access logs in Jira periodically
- Log into Jira with the account you want to use for CI
- Navigate to: https://id.atlassian.com/manage-profile/security/api-tokens
- Click "Create API token"
- Label:
GitHub Actions CI - Click "Create"
β οΈ Copy the token immediately (you won't see it again)
- Go to your repository on GitHub
- Navigate to Settings β Secrets and variables β Actions
- Click "New repository secret"
- Add each secret:
JIRA_BASE_URL
https://your-company.atlassian.net
/browse/ or other paths
JIRA_EMAIL
github-ci@your-company.com
Must match the email of the account that created the API token
JIRA_API_TOKEN
ATATT3xFfGF0abcdefghijklmnop1234567890
Paste the full token copied in Step 1
Your ticket prefix is the part before the dash in your Jira tickets:
PROJ-123β prefix isPROJDEV-456β prefix isDEVJIRA-789β prefix isJIRA
You can find this in your Jira project settings or by looking at any ticket URL:
https://company.atlassian.net/browse/PROJ-123
^^^^
This is your prefix
- Go to Settings β Branches
- Click "Add branch protection rule"
- Branch name pattern:
main(or your default branch) - Enable "Require status checks to pass before merging"
- Search for your job name (e.g.,
check-jira) - Click "Create"
- Create a test PR with a Jira ticket in the title or commit message:
git commit -m "PROJ-123: Test PR for Jira integration" git push origin my-feature-branch - Open a PR on GitHub with title:
PROJ-123: Test integration - Check the Actions tab for workflow run
- Verify the ticket status is checked
Cause: The Jira user doesn't have permission to view the ticket
Solution:
- Log into Jira with the account from
JIRA_EMAIL - Try to access the ticket manually
- If you can't see it, grant Browse Projects permission
- Or use a different account with broader access
Cause: Neither the PR title nor commits contain ticket references with your specified prefix
Solution:
- Add ticket to PR title:
PROJ-123: Your feature - Or add ticket to a commit message:
PROJ-123: Implement feature - Check that
ticket-prefixmatches your Jira project (case-insensitive) - Or add bypass keyword to PR title (e.g.,
[noticket])
Valid formats:
- β
PR Title:
PROJ-123: Add feature - β
PR Title:
[PROJ-123] Add feature - β
Commit:
PROJ-123: Add feature - β
Commit:
chore(PROJ-123): test - β
Commit:
Fix PROJ-123 - β
PROJ 123(no dash)
Cause: Ticket might be in an unexpected format
Solution:
The action searches for the pattern {PREFIX}-{number} anywhere in the PR title:
- β
PROJ-123: Titleworks - β
[PROJ-123] Titleworks - β
Title PROJ-123works - β
proj-123: Titleworks (case-insensitive) - β
PROJ 123doesn't work (must have dash)
Check your PR title format and ensure it includes {PREFIX}-{number}.
Cause: API token hasn't been used successfully
Solution:
- Verify
JIRA_EMAILexactly matches the account that created the token - Check for typos in the token (regenerate if unsure)
- Ensure
JIRA_BASE_URLis correct (no trailing slash)
Expected behavior when using synchronize trigger - this ensures new commits are checked.
To reduce frequency:
on:
pull_request:
types: [opened, edited] # Removed synchronizeA: Yes! Just use your Jira Server URL as jira-base-url:
jira-base-url: https://jira.company.comEnsure the REST API v3 is enabled.
A: No! The action finds tickets anywhere in the PR title and commit messages:
- β
PROJ-123: Add feature - β
[PROJ-123] Add feature - β
chore(PROJ-123): test - β
Add feature for PROJ-123 - β
Fix PROJ-100 and PROJ-101
A: No! Ticket matching is case-insensitive:
PROJ-123,proj-123,Proj-123all match the same ticket- The action normalizes all tickets to uppercase (e.g.,
PROJ-123)
A: The action deduplicates automatically! It will only check each unique ticket once, even if it appears in both the PR title and multiple commits.
A: Not in a single action call, but you can run the action multiple times:
steps:
- uses: designcise/jira-ticket-status-action@v2
with:
ticket-prefix: 'PROJ'
# ... other inputs
- uses: designcise/jira-ticket-status-action@v2
with:
ticket-prefix: 'DEV'
# ... other inputsA: The action logs a warning and continues checking other tickets. Only existing, accessible tickets are validated.
A: Use a comma-separated list:
bypass-keywords: 'noticket, hotfix, emergency, skip-jira'All keywords are case-insensitive and handle variations like no-ticket, noticket, no ticket.
A: Yes, as long as the REST API v3 is available at /rest/api/3/issue/{key}
A: Not directly, but you can achieve this by listing all unwanted statuses in blocked-statuses:
# Allow only: "In Progress", "In Review", "Done"
# Block everything else:
blocked-statuses: 'New, Backlog, Blocked, On Hold, Paused, Rejected'A: Look at any Jira ticket. The prefix is the part before the dash:
- URL:
https://company.atlassian.net/browse/PROJ-123βPROJ - Ticket:
DEV-456βDEV - Ticket:
JIRA-789βJIRA
A: No! All keywords are matched case-insensitively:
noticketmatchesNOTICKET,NoTicket,[NOTICKET], etc.hotfixmatchesHOTFIX,HotFix,[hotfix], etc.
Version 2.0 introduces a simpler, more intuitive format for configuring blocked statuses and bypass keywords.
Input format changed from JSON arrays to comma-separated strings:
# v1.x (Old - JSON arrays)
blocked-statuses: '["Requires fixing", "Paused"]'
bypass-keywords: '["noticket", "hotfix"]'
# v2.0+ (New - Comma-separated strings)
blocked-statuses: 'Requires fixing, Paused'
bypass-keywords: 'noticket, hotfix'Migration Steps:
-
Update your workflow file to use
@v2:- uses: designcise/jira-ticket-status-action@v2
-
Convert JSON arrays to comma-separated strings:
blocked-statuses: 'Blocked, On Hold, Paused' bypass-keywords: 'noticket, hotfix, emergency'
-
Test your workflow
Version 2.1 adds PR title scanning - no breaking changes!
What's new:
- β Tickets in PR title are now detected automatically
- β Case-insensitive ticket matching
- β All tickets normalized to uppercase
Migration:
Simply update to @v2 (or @v2.1) - no configuration changes needed:
- uses: designcise/jira-ticket-status-action@v2Your existing workflows will continue to work, with the added benefit of PR title scanning!
| Jira Type | Supported | API Version |
|---|---|---|
| Jira Cloud | β Yes | REST API v3 |
| Jira Server 8.0+ | β Yes | REST API v3 |
| Jira Data Center | β Yes | REST API v3 |
| Jira Server <8.0 | REST API v2 (not tested) |
Contributions are welcome! Please feel free to submit a Pull Request.
git clone https://github.com/designcise/jira-ticket-status-action.git
cd jira-ticket-status-action
# Test locally using act
act pull_request -s JIRA_BASE_URL=... -s JIRA_EMAIL=... -s JIRA_API_TOKEN=...Found a bug? Open an issue with:
- Workflow YAML
- Error message
- Jira version (Cloud/Server/Data Center)
- Ticket prefix being used
- PR title and example commit messages
MIT License - see LICENSE file for details
Powered by:
- π Documentation
- π Issue Tracker
- π¬ Discussions
β If this action helps your team, please give it a star!