The case for doing this is short. This is the implementation. One workflow file, two jobs: the first flags TODO, WORKAROUND, and TBC comments on any active pull request, the second creates a Jira ticket when any of them merge without being resolved. Four setup steps.

1. Generate a Jira API token

Jira’s API requires a token, not your password. Go to your Atlassian account security page, generate a new token, and copy it immediately – you only see it once.

https://id.atlassian.com/manage-profile/security/api-tokens

2. Add the secrets to GitHub

Go to your repository’s Settings > Secrets and variables > Actions and add three secrets. The base URL is your Atlassian instance domain with no trailing slash.

JIRA_BASE_URL      https://yourcompany.atlassian.net
JIRA_USER_EMAIL    you@yourcompany.com
JIRA_API_TOKEN     (the token from step 1)

3. Create the workflow file

Create .github/workflows/tech-debt.yml with the content below. The workflow uses two separate triggers because pull_request_target is what gives the Jira job access to your secrets when a PR merges – using pull_request for both would work for the comment job but silently fail on the ticket creation.

Both scan jobs filter to added lines only (grep -E '^\+[^+]'), so they flag new debt introduced by the PR, not keywords already sitting in the codebase. The Jira ticket includes the matching snippets and a link back to the PR, so whoever picks up the ticket has the context they need.

name: Handle Code Technical Debt

on:
  pull_request:
    types: [opened, synchronize, reopened]
  pull_request_target:
    types: [closed]

jobs:
  pr-comment:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Check for new keywords
        id: check_keywords
        run: |
          git fetch origin ${{ github.base_ref }}
          KEYWORDS=$(git diff origin/${{ github.base_ref }}...HEAD | grep -E '^\+[^+]' | grep -Ei 'workaround|todo|tbc' || true)

          if [ -n "$KEYWORDS" ]; then
            echo "MATCH_FOUND=true" >> $GITHUB_OUTPUT

            FOUND_ITEMS=""
            echo "$KEYWORDS" | grep -qi 'workaround' && FOUND_ITEMS="$FOUND_ITEMS WORKAROUND,"
            echo "$KEYWORDS" | grep -qi 'todo'        && FOUND_ITEMS="$FOUND_ITEMS TODO,"
            echo "$KEYWORDS" | grep -qi 'tbc'         && FOUND_ITEMS="$FOUND_ITEMS TBC,"
            FOUND_ITEMS=$(echo "$FOUND_ITEMS" | sed 's/,$//')

            echo "FOUND_ITEMS=$FOUND_ITEMS" >> $GITHUB_OUTPUT
          fi

      - name: Comment on PR
        if: steps.check_keywords.outputs.MATCH_FOUND == 'true'
        uses: actions/github-script@v7
        with:
          script: |
            const found = "${{ steps.check_keywords.outputs.FOUND_ITEMS }}".trim();
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `⚠️ **Technical Debt Tags Detected:** This PR introduces the following tags: **${found}**.\n\nResolve before merging if possible. Any that remain will have a Jira ticket created automatically on merge.`
            })

  jira-tracking:
    if: github.event.pull_request.merged == true
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Scan merged keywords
        id: scan_merged
        run: |
          KEYWORDS=$(git diff HEAD^1 HEAD | grep -E '^\+[^+]' | grep -Ei 'workaround|todo|tbc' || true)

          if [ -n "$KEYWORDS" ]; then
            echo "MATCH_FOUND=true" >> $GITHUB_OUTPUT

            FOUND_ITEMS=""
            echo "$KEYWORDS" | grep -qi 'workaround' && FOUND_ITEMS="$FOUND_ITEMS WORKAROUND,"
            echo "$KEYWORDS" | grep -qi 'todo'        && FOUND_ITEMS="$FOUND_ITEMS TODO,"
            echo "$KEYWORDS" | grep -qi 'tbc'         && FOUND_ITEMS="$FOUND_ITEMS TBC,"
            FOUND_ITEMS=$(echo "$FOUND_ITEMS" | sed 's/,$//' | xargs)

            echo "FOUND_ITEMS=$FOUND_ITEMS" >> $GITHUB_OUTPUT

            CLEANED_KEYWORDS=$(echo "$KEYWORDS" | sed 's/"/\\"/g' | head -n 5)

            echo "JIRA_DESCRIPTION<<EOF" >> $GITHUB_ENV
            echo "Outstanding debt tags merged into main (${FOUND_ITEMS}) via PR #${{ github.event.pull_request.number }}: ${{ github.event.pull_request.html_url }}" >> $GITHUB_ENV
            echo -e "\nSnippets:\n$CLEANED_KEYWORDS" >> $GITHUB_ENV
            echo "EOF" >> $GITHUB_ENV
          fi

      - name: Create Jira Ticket
        if: steps.scan_merged.outputs.MATCH_FOUND == 'true'
        uses: atlassian/gajira-create@v3
        with:
          project: 'PROJ'
          issuetype: 'Task'
          summary: 'Tech Debt: Resolve ${{ steps.scan_merged.outputs.FOUND_ITEMS }} from PR #${{ github.event.pull_request.number }}'
          description: ${{ env.JIRA_DESCRIPTION }}
        env:
          JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
          JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
          JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}

4. Set your project key and issue type

Two values to change before committing. Both are in the jira-tracking job under the Create Jira Ticket step.

        with:
          project: 'PROJ'   # your Jira project key, e.g. ENG, PLATFORM, DATA
          issuetype: 'Task' # Task, Story, Bug -- whatever your backlog uses

To add more keywords, edit the grep -Ei pattern in both scan steps.

grep -Ei 'workaround|todo|tbc|fixme|hack'

5. Verify

Open a PR that adds any of the flagged keywords to a file. The pr-comment job should post a comment within a minute or two. Merge the PR and check your Jira backlog for the new ticket. If the comment fires but no ticket appears, check the Actions log for the Create Jira Ticket step – it will say exactly what Jira rejected.

The comment appears on the PR. The ticket lands in the backlog. From that point, the debt is tracked whether or not anyone remembered to track it.


Full script

# .github/workflows/tech-debt.yml

name: Handle Code Technical Debt

on: pull_request: types: [opened, synchronize, reopened] pull_request_target: types: [closed]

jobs: pr-comment: if: github.event_name == ‘pull_request’ runs-on: ubuntu-latest permissions: pull-requests: write steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0

  - name: Check for new keywords
    id: check_keywords
    run: |
      git fetch origin ${{ github.base_ref }}
      KEYWORDS=$(git diff origin/${{ github.base_ref }}...HEAD | grep -E '^\+[^+]' | grep -Ei 'workaround|todo|tbc' || true)

      if [ -n "$KEYWORDS" ]; then
        echo "MATCH_FOUND=true" >> $GITHUB_OUTPUT

        FOUND_ITEMS=""
        echo "$KEYWORDS" | grep -qi 'workaround' && FOUND_ITEMS="$FOUND_ITEMS WORKAROUND,"
        echo "$KEYWORDS" | grep -qi 'todo'        && FOUND_ITEMS="$FOUND_ITEMS TODO,"
        echo "$KEYWORDS" | grep -qi 'tbc'         && FOUND_ITEMS="$FOUND_ITEMS TBC,"
        FOUND_ITEMS=$(echo "$FOUND_ITEMS" | sed 's/,$//')

        echo "FOUND_ITEMS=$FOUND_ITEMS" >> $GITHUB_OUTPUT
      fi

  - name: Comment on PR
    if: steps.check_keywords.outputs.MATCH_FOUND == 'true'
    uses: actions/github-script@v7
    with:
      script: |
        const found = "${{ steps.check_keywords.outputs.FOUND_ITEMS }}".trim();
        github.rest.issues.createComment({
          issue_number: context.issue.number,
          owner: context.repo.owner,
          repo: context.repo.repo,
          body: `⚠️ **Technical Debt Tags Detected:** This PR introduces the following tags: **${found}**.\n\nResolve before merging if possible. Any that remain will have a Jira ticket created automatically on merge.`
        })

jira-tracking: if: github.event.pull_request.merged == true runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0

  - name: Scan merged keywords
    id: scan_merged
    run: |
      KEYWORDS=$(git diff HEAD^1 HEAD | grep -E '^\+[^+]' | grep -Ei 'workaround|todo|tbc' || true)

      if [ -n "$KEYWORDS" ]; then
        echo "MATCH_FOUND=true" >> $GITHUB_OUTPUT

        FOUND_ITEMS=""
        echo "$KEYWORDS" | grep -qi 'workaround' && FOUND_ITEMS="$FOUND_ITEMS WORKAROUND,"
        echo "$KEYWORDS" | grep -qi 'todo'        && FOUND_ITEMS="$FOUND_ITEMS TODO,"
        echo "$KEYWORDS" | grep -qi 'tbc'         && FOUND_ITEMS="$FOUND_ITEMS TBC,"
        FOUND_ITEMS=$(echo "$FOUND_ITEMS" | sed 's/,$//' | xargs)

        echo "FOUND_ITEMS=$FOUND_ITEMS" >> $GITHUB_OUTPUT

        CLEANED_KEYWORDS=$(echo "$KEYWORDS" | sed 's/"/\\"/g' | head -n 5)

        echo "JIRA_DESCRIPTION<<EOF" >> $GITHUB_ENV
        echo "Outstanding debt tags merged into main (${FOUND_ITEMS}) via PR #${{ github.event.pull_request.number }}: ${{ github.event.pull_request.html_url }}" >> $GITHUB_ENV
        echo -e "\nSnippets:\n$CLEANED_KEYWORDS" >> $GITHUB_ENV
        echo "EOF" >> $GITHUB_ENV
      fi

  - name: Create Jira Ticket
    if: steps.scan_merged.outputs.MATCH_FOUND == 'true'
    uses: atlassian/gajira-create@v3
    with:
      project: 'PROJ'
      issuetype: 'Task'
      summary: 'Tech Debt: Resolve ${{ steps.scan_merged.outputs.FOUND_ITEMS }} from PR #${{ github.event.pull_request.number }}'
      description: ${{ env.JIRA_DESCRIPTION }}
    env:
      JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
      JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
      JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}</code></pre>