Bulk-migrates GitHub repo labels and Project V2 fields to org-level issue fields. Converts priority labels to Priority field, copies project values, optionally removes old labels.
From awesome-copilotnpx claudepluginhub ctr26/dotfiles --plugin awesome-copilotThis skill uses the workspace's default tool permissions.
references/issue-fields-api.mdreferences/labels-api.mdreferences/projects-api.mdFetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
Uses ctx7 CLI to fetch current library docs, manage AI coding skills (install/search/generate), and configure Context7 MCP for AI editors.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
Issue fields are org-level typed metadata (single select, text, number, date) that replace label-based workarounds with structured, searchable, cross-repo fields. Every organization gets Priority, Effort, Start date, and Target date preconfigured, with support for up to 25 custom fields.
This skill bulk-migrates existing metadata into issue fields from two sources:
p0, p1, priority/high into structured issue field values (e.g. the Priority field). Supports migrating multiple labels at once and optionally removing them after migration.gh CLI must be authenticated with appropriate scopes| Tool | Purpose |
|---|---|
mcp__github__projects_list | List project fields (list_project_fields), list project items with values (list_project_items) |
mcp__github__projects_get | Get details of a specific project field or item |
| Operation | Command |
|---|---|
| List org issue fields | gh api /orgs/{org}/issue-fields -H "X-GitHub-Api-Version: 2026-03-10" |
| Read issue field values | gh api /repos/{owner}/{repo}/issues/{number}/issue-field-values -H "X-GitHub-Api-Version: 2026-03-10" |
| Write issue field values | gh api /repositories/{repo_id}/issues/{number}/issue-field-values -X POST -H "X-GitHub-Api-Version: 2026-03-10" --input - |
| Get repository ID | gh api /repos/{owner}/{repo} --jq .id |
| List repo labels | gh label list -R {owner}/{repo} --limit 1000 --json name,color,description |
| List issues by label | gh issue list -R {owner}/{repo} --label "{name}" --state all --json number,title,labels --limit 1000 |
| Remove label from issue | gh api /repos/{owner}/{repo}/issues/{number}/labels/{label_name} -X DELETE |
See references/issue-fields-api.md, references/projects-api.md, and references/labels-api.md for full API details.
Ask the user what they are migrating:
"Are you migrating labels or project fields?"
If the user says labels:
If the user says project fields:
Use this flow when the user wants to convert repo labels into issue field values. Labels can only map to single_select issue fields (each label name maps to one option value).
gh label list -R {owner}/{repo} --limit 1000 --json name,color,description
gh api /orgs/{org}/issue-fields \
-H "X-GitHub-Api-Version: 2026-03-10" \
--jq '.[] | {id, name, content_type, options: [.options[]?.name]}'
Filtering (for repos with many labels): if the repo has 50+ labels, group by common prefix (e.g., priority-*, team-*, type-*) or color. Let the user filter with "show labels matching priority" or "show blue labels" before mapping. Never dump 100+ labels at once.
Ask the user which labels map to which issue field and option. Support these patterns:
Auto-suggest mappings: for each label, attempt to match issue field options using these patterns (in order):
Bug → option Bug{prefix}-{n} → {P}{n}): label priority-1 → option P1good_first_issue → option Good First Issuetype: bug → option BugPresent all suggestions at once for the user to confirm, correct, or skip.
Example output:
Labels in github/my-repo (showing relevant ones):
p0, p1, p2, p3, bug, enhancement, frontend, backend
Org issue fields (single_select):
Priority: Critical, P0, P1, P2, P3
Type: Bug, Feature, Task
Team: Frontend, Backend, Design
Suggested mappings:
Label "p0" → Priority "P0"
Label "p1" → Priority "P1"
Label "p2" → Priority "P2"
Label "p3" → Priority "P3"
Label "bug" → Type "Bug"
Label "frontend" → Team "Frontend"
Label "backend" → Team "Backend"
Label "enhancement" → (no auto-match; skip or map manually)
Confirm, adjust, or add more mappings?
After finalizing the label-to-option mappings, check for conflicts. A conflict occurs when an issue has multiple labels that map to the same issue field (since single_select fields can hold only one value).
Example:
Potential conflict: labels "p0" and "p1" both map to the Priority field.
If an issue has both labels, which value should win?
Options:
1. First match (use "p0" since it appears first in the mapping)
2. Skip conflicting issues
3. I'll decide case by case
repository_id:gh api /repos/{owner}/{repo} --jq '{full_name, id, permissions: .permissions}'
gh issue list -R {owner}/{repo} --label "{label_name}" --state all \
--json number,title,labels,type --limit 1000
Warning: --limit 1000 silently truncates results. If you expect a label may have more than 1000 issues, paginate manually or verify the total count first (e.g., gh issue list --label "X" --state all --json number | jq length).
PR filtering: gh issue list returns both issues and PRs. Include type in the --json output and filter for type == "Issue" if the user only wants issues migrated.
If all selected labels return 0 issues, stop and tell the user. Suggest: try different labels, check spelling, or try a different repository. Do not proceed with an empty migration.
For multi-repo migrations, repeat across all specified repos.
For each issue found:
Present a summary before any writes.
Example preview:
Label Migration Preview
Source: labels in github/my-repo
Target fields: Priority, Type, Team
| Category | Count |
|-------------------------|-------|
| Issues to migrate | 156 |
| Already set (skip) | 12 |
| Conflicting labels (skip)| 3 |
| Total issues with labels| 171 |
Label breakdown:
"p1" → Priority "P1": 42 issues
"p2" → Priority "P2": 67 issues
"p3" → Priority "P3": 38 issues
"bug" → Type "Bug": 9 issues
Sample changes (first 5):
github/my-repo#101: Priority → "P1"
github/my-repo#203: Priority → "P2", Type → "Bug"
github/my-repo#44: Priority → "P3"
github/my-repo#310: Priority → "P1"
github/my-repo#7: Type → "Bug"
After migration, do you also want to remove the migrated labels from issues? (optional)
Estimated time: ~24s (156 API calls at 0.15s each)
Proceed?
echo '{"issue_field_values": [{"field_id": FIELD_ID, "value": "OPTION_NAME"}]}' | \
gh api /repositories/{repo_id}/issues/{number}/issue-field-values \
-X POST \
-H "X-GitHub-Api-Version: 2026-03-10" \
--input -
Replace FIELD_ID with the integer field ID (e.g., 1) and OPTION_NAME with the option name string.
gh api /repos/{owner}/{repo}/issues/{number}/labels/{label_name} -X DELETE
URL-encode label names that contain spaces or special characters.
Label Migration Complete
| Result | Count |
|-----------------------|-------|
| Fields set | 153 |
| Labels removed | 153 |
| Skipped | 15 |
| Failed (field write) | 2 |
| Failed (label remove) | 1 |
Failed items:
github/my-repo#501: 403 Forbidden (insufficient permissions)
github/my-repo#88: 422 Validation failed (field not available on repo)
github/my-repo#120: label removal failed (404, label already removed)
Use this flow when the user wants to copy values from a GitHub Project V2 field to the corresponding org-level issue field.
Follow these six phases in order. Always preview before executing.
# Use MCP tool
mcp__github__projects_list(owner: "{org}", project_number: {n}, method: "list_project_fields")
gh api /orgs/{org}/issue-fields \
-H "X-GitHub-Api-Version: 2026-03-10" \
--jq '.[] | {id, name, content_type, options: [.options[]?.name]}'
Filter out proxy fields: after issue fields are enabled on a project, some project fields appear as "proxy" entries with empty options: [] for single-select types. These mirror the real issue fields and should be ignored. Only match against project fields that have actual option values.
Auto-match fields by name (case-insensitive) with compatible types:
| Project Field Type | Issue Field Type | Compatible? |
|---|---|---|
| TEXT | text | Yes, direct copy |
| SINGLE_SELECT | single_select | Yes, option mapping needed |
| NUMBER | number | Yes, direct copy |
| DATE | date | Yes, direct copy |
| ITERATION | (none) | No equivalent; skip with warning |
Example output:
Found 3 potential field mappings:
| # | Project Field | Type | Issue Field | Status |
|---|-------------------|---------------|--------------------|------------|
| 1 | Priority (renamed) | SINGLE_SELECT | Priority | Auto-match |
| 2 | Due Date | DATE | Due Date | Auto-match |
| 3 | Sprint | ITERATION | (no equivalent) | Skipped |
Proceed with fields 1 and 2? You can also add manual mappings.
For each matched single-select pair:
Example output:
Option mapping for "Release - Target":
Auto-matched (case-insensitive):
"GA" → "GA"
"Private Preview" → "Private Preview"
"Public Preview" → "Public Preview"
Unmapped project options (need your input):
1. "Internal Only" → which issue field option? (or skip)
2. "Retired" → which issue field option? (or skip)
3. "Beta" → which issue field option? (or skip)
4. "Deprecated" → which issue field option? (or skip)
Available issue field options not yet mapped: "Internal", "Sunset", "Beta Testing", "End of Life"
Please provide mappings for all 4 options above (e.g., "1→Internal, 2→Sunset, 3→Beta Testing, 4→skip").
Before scanning items, verify write access to each repository that may be touched:
{owner}/{repo} values.gh api /repos/{owner}/{repo} --jq '{full_name, permissions: .permissions}'
push: false or triage: false, warn the user before proceeding. Items in those repos will fail at write time.repository_id (integer) for each repo now; you will need it in Phase 6:gh api /repos/{owner}/{repo} --jq .id
gh api graphql --paginate is unreliable (it concatenates JSON responses without proper separators and can time out). Use the MCP tool which handles pagination internally, or use explicit cursor-based pagination:# Preferred: use MCP tool (handles pagination automatically)
mcp__github__projects_list(owner: "{org}", project_number: {n}, method: "list_project_items")
# Fallback for large projects: manual cursor-based pagination
# Fetch 100 items per page, advancing the cursor each time.
# Process each page before fetching the next to avoid memory issues.
# Save progress (page number or last cursor) so you can resume if interrupted.
gh api /repos/{owner}/{repo}/issues/{number}/issue-field-values \
-H "X-GitHub-Api-Version: 2026-03-10"
Present a summary before any writes.
If user requested dry-run: show the full detailed report (every issue, its current value, proposed new value, and skip reason) and stop. Do not execute.
Otherwise (preview mode): show summary counts and a sample of changes, then ask for confirmation.
Example preview:
Migration Preview for Project #42
Fields to migrate: Priority, Due Date
| Category | Count |
|------------------------|-------|
| Items to migrate | 847 |
| Already set (skip) | 23 |
| No source value (skip) | 130 |
| Draft items (skip) | 12 |
| Total project items | 1,012 |
Sample changes (first 5):
github/repo-a#101: Priority → "High"
github/repo-a#203: Priority → "Medium", Due Date → "2025-03-15"
github/repo-b#44: Priority → "Low"
github/repo-a#310: Due Date → "2025-04-01"
github/repo-c#7: Priority → "Critical"
Estimated time: ~127s (847 API calls at 0.15s each)
Proceed with migration? This will update 847 issues across 3 repositories.
Use the repository_id values cached in Phase 3.
For each item to migrate, write the issue field value:
echo '{"issue_field_values": [{"field_id": FIELD_ID, "value": "VALUE"}]}' | \
gh api /repositories/{repo_id}/issues/{number}/issue-field-values \
-X POST \
-H "X-GitHub-Api-Version: 2026-03-10" \
--input -
Replace FIELD_ID with the integer field ID (e.g., 1) and VALUE with the value string.
Migration Complete
| Result | Count |
|---------|-------|
| Success | 842 |
| Skipped | 165 |
| Failed | 5 |
Failed items:
github/repo-a#501: 403 Forbidden (insufficient permissions)
github/repo-b#88: 422 Validation failed (field not available on repo)
...
repository_id (integer), not owner/repo. Always look up the repo ID first with gh api /repos/{owner}/{repo} --jq .id..single_select_option.name for the human-readable value. The .value property returns the internal option ID (an integer like 1201), not the display name.X-GitHub-Api-Version: 2026-03-10.good%20first%20issue).--limit 1000 truncation: gh issue list --limit 1000 silently stops at 1000 results. For labels with more issues, paginate with --jq and cursor-based pagination or run multiple filtered queries (e.g., by date range).declare -A (associative arrays). Generated scripts should use POSIX-compatible constructs or note the incompatibility and suggest brew install bash.gh issue list returns both issues and pull requests. If the migration should only target issues, include type in --json output and filter for type == "Issue".User: "I need to migrate Priority values from our project to the new org Priority issue field"
Action: Follow Phases P1-P6. Discover fields, map options, check permissions, scan items, preview, execute.
User: "Show me what would happen if I migrated fields from project #42, but don't actually do it"
Action: Follow Phases P1-P5 only. Present the full dry-run report with every item listed. Do not execute.
User: "Migrate Priority and Due Date from project #15 to issue fields"
Action: Same workflow, but process both fields in a single pass. During the data scan, collect values for all mapped fields per item. Write all field values in a single API call per issue.
User: "I want to migrate the 'bug' label to the Type issue field"
Action: Route to Label Migration Flow. Ask for org/repo, list labels, confirm mapping: label "bug" → Type field "Bug" option. Scan issues with that label, preview, execute. Ask whether to remove the label after migration.
User: "We have p0, p1, p2, p3 labels and want to convert them to the Priority issue field"
Action: Route to Label Migration Flow. Map all four labels to Priority field options (p0→P0, p1→P1, p2→P2, p3→P3). Check for conflicts (issues with multiple priority labels). Preview all changes in one summary. Execute in one pass. Optionally remove all four labels from migrated issues.
User: "Migrate the 'frontend' and 'backend' labels to the Team issue field across github/issues, github/memex, and github/mobile, then remove the old labels"
Action: Route to Label Migration Flow. Confirm repos and label mappings: "frontend"→Team "Frontend", "backend"→Team "Backend". Scan all three repos for issues with these labels. Detect conflicts (issues with both labels). Preview across repos. Execute field writes, then remove labels from migrated issues. Report per-repo stats.