A GitHub Action that automatically assigns issues to a milestone, gated by GitHub issue type or label. It's most useful for organization repositories using GitHub issue types, and for sharing one milestone-automation step across many repositories with a consistent, versioned contract.
For a single repository doing simple label-based assignment, maybe not — native
workflow gating plus two lines of gh cover it:
- name: Assign to milestone if unset
if: contains(github.event.issue.labels.*.name, 'enhancement')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
n=${{ github.event.issue.number }}
has=$(gh issue view "$n" --json milestone --jq '.milestone.title // ""')
[ -z "$has" ] && gh issue edit "$n" --milestone "Wishlist"This action earns its keep when you want:
- Issue-type gating (
issue-type) — GitHub issue types aren't cleanly expressible in workflowif:conditions, so this is the awkward case to do by hand. - One reusable, versioned step shared across many repositories, with consistent
outputs (
assigned/milestone/reason) and built-in API retries.
If neither applies, the inline snippet above is simpler to own and debug.
- ✅ Assigns issues to a target milestone (resolved case-insensitively)
- ✅ Prevents reassignment if the issue already has a milestone
- ✅ Optional issue-type filtering using GitHub issue types (only available for org-based repos)
- ✅ Optional label filtering (exact label match, case-insensitive)
- ✅ Comprehensive logging and error handling
- ✅ Configurable for any repository
- Breaking —
issue-labelnow matches the exact label (case-insensitive), not a substring. In v1,issue-label: uialso matched a label likebuild, andbugmatcheddebugging, which could assign milestones unexpectedly. Setissue-labelto the exact label name; if you relied on partial matching across several labels, use one action step per label. - Improved (non-breaking) —
target-milestoneresolves case-insensitively against your repository's real milestones (e.g.wishlistfindsWishlist), as documented.
GitHub issue types are awkward to gate on in a workflow if: condition — this is
where the action is most worthwhile. Only assigns issues whose type matches:
- name: Assign Bug Issues to Milestone
uses: davidizzy/issue-milestoner@v2.0.0 # x-release-please-version
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
target-milestone: "my-important-milestone"
issue-type: "bug" # GitHub issue type (org repos only)Only assigns issues carrying the exact label (case-insensitive):
- name: Assign Enhancement Issues to Milestone
uses: davidizzy/issue-milestoner@v2.0.0 # x-release-please-version
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
target-milestone: "my-important-milestone"
issue-label: "enhancement" # exact label matchAssigns the milestone to any issue that doesn't already have one:
- name: Assign Issue to Milestone
uses: davidizzy/issue-milestoner@v2.0.0 # x-release-please-version
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
target-milestone: "my-important-milestone"name: Auto Milestone Assignment
on:
issues:
types: [opened, typed]
permissions:
issues: write
contents: read
jobs:
assign-milestone:
runs-on: ubuntu-latest
steps:
- name: Assign to Current Sprint
uses: davidizzy/issue-milestoner@v2.0.0 # x-release-please-version
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
target-milestone: "Unscheduled Features"
issue-type: "feature" # Only assign feature type issues| Input | Description | Required | Default |
|---|---|---|---|
github-token |
GitHub token with repository access | Yes | - |
issue-number |
Issue number to process | Yes | - |
target-milestone |
Target milestone to assign to the issue | Yes | - |
issue-type |
Optional GitHub issue type filter (e.g., bug, feature, task) | No | - |
issue-label |
Optional exact label filter, case-insensitive (e.g., enhancement, documentation) | No | - |
repository |
Repository in the format owner/repo | No | Current repository |
| Output | Description |
|---|---|
assigned |
Whether the issue was assigned to the milestone (true/false) |
milestone |
The milestone that was assigned |
reason |
Reason for the action taken or not taken |
- Milestone Check: If the issue already has a milestone assigned, the action will not reassign it
- Issue Type Filtering: If
issue-typeis set, the issue's type must match (case-insensitive). Issues with no type (non-org repos) are skipped, not failed. - Label Filtering: If
issue-labelis set, the issue must carry a label equal to the filter (exact match, case-insensitive) - Milestone Matching:
target-milestoneis resolved against the repo's existing milestones by name (case-insensitive); the canonical title is used - Assignment: Only assigns the milestone if all conditions are met
The GitHub token needs the following permissions:
issues: write- to update issue milestonesmetadata: read- to read repository information
- Rate Limits: Subject to GitHub API rate limits (5000/hour for authenticated requests)
- Permissions: Requires
issues: writeandcontents: read - Performance: Optimized for issues with up to 100 labels
- Dependencies: Requires GitHub CLI (gh) v2.0.0+ and jq v1.6+
- Verify milestone exists:
gh api repos/{owner}/{repo}/milestones - Check spelling (matching is case-insensitive)
- Ensure milestone is open
- Verify workflow has
issues: writepermission - Check token has repository access
- Ensure you're not in a fork without secrets
This repository includes a working example of the action in .github/workflows/auto-milestone-wishlist.yaml.
This workflow automatically assigns issues labeled "enhancement" to a "Wishlist" milestone, demonstrating real-world usage of the issue-label filtering feature.
Note how it layers a workflow-level if: (an efficiency gate that avoids spinning up a
runner for non-enhancement issues) with the action's own issue-label filter (which
enforces the label for workflow_dispatch runs). For a simple single-repo setup you'd
typically pick just one — see Do you even need this? above.
You can use this as a template for creating your own milestone automation workflows.
This is a composite action using shell scripts for simplicity and maintainability.
# Clone and test
git clone https://github.com/davidizzy/issue-milestoner.git
cd issue-milestoner
./tests/test-composite.sh
# Local testing (requires GitHub CLI and token)
export GH_TOKEN=your_token
./test-local.shSee CONTRIBUTING.md for detailed development guidelines.
MIT License - see LICENSE file for details.
- Contributing: See CONTRIBUTING.md for guidelines
- Issues: Create an issue for bugs or feature requests