
Github Issues
- 228 installs
- 2 repo stars
- Updated July 24, 2026
- ilteoood/harness
This is a copy of github-issues by github - installs and ranking accrue to the original listing.
Wire GitHub issue blocked-by and blocking relationships through REST or GraphQL when MCP does not expose dependency APIs.
About
github-issues is a reference skill for formal GitHub issue dependency management—marking one issue as blocked by another so relationships show in the UI and sync via API. Solo and indie builders shipping with agents often track work in GitHub; when they need blocked-by or blocking edges, native MCP tooling may not cover it, so this skill spells out the REST paths under /issues/{number}/dependencies/blocked_by and the GraphQL fields and mutations that mirror the same model. It is aimed at builders comfortable with tokens and HTTP or GraphQL clients who want repeatable instructions an agent can follow without guessing parameter shapes. Use it during backlog grooming, release planning, or when unblocking a chain of tickets programmatically. It complements general issue-creation skills by focusing solely on dependency edges and the ID versus number distinction that trips up first-time integrators.
- Documents REST endpoints for list, add, and remove blocked_by dependencies
- Documents GraphQL queries for blockedBy, blocking, and issueDependenciesSummary
- Documents GraphQL mutations addBlockedBy and removeBlockedBy with issue node IDs
- Clarifies issue numeric ID versus issue number for REST POST bodies
- Explicitly notes no MCP tools for dependencies—direct API calls only
Github Issues by the numbers
- 228 all-time installs (skills.sh)
- +30 installs in the week ending Jul 25, 2026 (Skillselion tracking)
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 25, 2026 (Skillselion catalog sync)
npx skills add https://github.com/ilteoood/harness --skill github-issuesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 228 |
|---|---|
| repo stars | ★ 2 |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 24, 2026 |
| Repository | ilteoood/harness ↗ |
What it does
Wire GitHub issue blocked-by and blocking relationships through REST or GraphQL when MCP does not expose dependency APIs.
Files
GitHub Issues
Manage GitHub issues using the @modelcontextprotocol/server-github MCP server.
Available Tools
MCP Tools (read operations)
| Tool | Purpose |
|---|---|
mcp__github__issue_read | Read issue details, sub-issues, comments, labels (methods: get, get_comments, get_sub_issues, get_labels) |
mcp__github__list_issues | List and filter repository issues by state, labels, date |
mcp__github__search_issues | Search issues across repos using GitHub search syntax |
mcp__github__projects_list | List projects, project fields, project items, status updates |
mcp__github__projects_get | Get details of a project, field, item, or status update |
mcp__github__projects_write | Add/update/delete project items, create status updates |
CLI / REST API (write operations)
The MCP server does not currently support creating, updating, or commenting on issues. Use gh api for these operations.
| Operation | Command |
|---|---|
| Create issue | gh api repos/{owner}/{repo}/issues -X POST -f title=... -f body=... |
| Update issue | gh api repos/{owner}/{repo}/issues/{number} -X PATCH -f title=... -f state=... |
| Add comment | gh api repos/{owner}/{repo}/issues/{number}/comments -X POST -f body=... |
| Close issue | gh api repos/{owner}/{repo}/issues/{number} -X PATCH -f state=closed |
| Set issue type | Include -f type=Bug in the create call (REST API only, not supported by gh issue create CLI) |
Note: gh issue create works for basic issue creation but does not support the --type flag. Use gh api when you need to set issue types.
Workflow
1. Determine action: Create, update, or query? 2. Gather context: Get repo info, existing labels, milestones if needed 3. Structure content: Use appropriate template from references/templates.md 4. Execute: Use MCP tools for reads, gh api for writes 5. Confirm: Report the issue URL to user
Creating Issues
Use gh api to create issues. This supports all parameters including issue types.
gh api repos/{owner}/{repo}/issues \
-X POST \
-f title="Issue title" \
-f body="Issue body in markdown" \
-f type="Bug" \
--jq '{number, html_url}'Optional Parameters
Add any of these flags to the gh api call:
-f type="Bug" # Issue type (Bug, Feature, Task, Epic, etc.)
-f labels[]="bug" # Labels (repeat for multiple)
-f assignees[]="username" # Assignees (repeat for multiple)
-f milestone=1 # Milestone numberIssue types are organization-level metadata. To discover available types, use:
gh api graphql -f query='{ organization(login: "ORG") { issueTypes(first: 10) { nodes { name } } } }' --jq '.data.organization.issueTypes.nodes[].name'Prefer issue types over labels for categorization. When issue types are available (e.g., Bug, Feature, Task), use the type parameter instead of applying equivalent labels like bug or enhancement. Issue types are the canonical way to categorize issues on GitHub. Only fall back to labels when the org has no issue types configured.
Title Guidelines
- Be specific and actionable
- Keep under 72 characters
- When issue types are set, don't add redundant prefixes like
[Bug] - Examples:
Login fails with SSO enabled(with type=Bug)Add dark mode support(with type=Feature)Add unit tests for auth module(with type=Task)
Body Structure
Always use the templates in references/templates.md. Choose based on issue type:
| User Request | Template |
|---|---|
| Bug, error, broken, not working | Bug Report |
| Feature, enhancement, add, new | Feature Request |
| Task, chore, refactor, update | Task |
Updating Issues
Use gh api with PATCH:
gh api repos/{owner}/{repo}/issues/{number} \
-X PATCH \
-f state=closed \
-f title="Updated title" \
--jq '{number, html_url}'Only include fields you want to change. Available fields: title, body, state (open/closed), labels, assignees, milestone.
Examples
Example 1: Bug Report
User: "Create a bug issue - the login page crashes when using SSO"
Action:
gh api repos/github/awesome-copilot/issues \
-X POST \
-f title="Login page crashes when using SSO" \
-f type="Bug" \
-f body="## Description
The login page crashes when users attempt to authenticate using SSO.
## Steps to Reproduce
1. Navigate to login page
2. Click 'Sign in with SSO'
3. Page crashes
## Expected Behavior
SSO authentication should complete and redirect to dashboard.
## Actual Behavior
Page becomes unresponsive and displays error." \
--jq '{number, html_url}'Example 2: Feature Request
User: "Create a feature request for dark mode with high priority"
Action:
gh api repos/github/awesome-copilot/issues \
-X POST \
-f title="Add dark mode support" \
-f type="Feature" \
-f labels[]="high-priority" \
-f body="## Summary
Add dark mode theme option for improved user experience and accessibility.
## Motivation
- Reduces eye strain in low-light environments
- Increasingly expected by users
## Proposed Solution
Implement theme toggle with system preference detection.
## Acceptance Criteria
- [ ] Toggle switch in settings
- [ ] Persists user preference
- [ ] Respects system preference by default" \
--jq '{number, html_url}'Common Labels
Use these standard labels when applicable:
| Label | Use For |
|---|---|
bug | Something isn't working |
enhancement | New feature or improvement |
documentation | Documentation updates |
good first issue | Good for newcomers |
help wanted | Extra attention needed |
question | Further information requested |
wontfix | Will not be addressed |
duplicate | Already exists |
high-priority | Urgent issues |
Tips
- Always confirm the repository context before creating issues
- Ask for missing critical information rather than guessing
- Link related issues when known:
Related to #123 - For updates, fetch current issue first to preserve unchanged fields
Extended Capabilities
The following features require REST or GraphQL APIs beyond the basic MCP tools. Each is documented in its own reference file so the agent only loads the knowledge it needs.
| Capability | When to use | Reference |
|---|---|---|
| Advanced search | Complex queries with boolean logic, date ranges, cross-repo search, issue field filters (field.name:value) | references/search.md |
| Sub-issues & parent issues | Breaking work into hierarchical tasks | references/sub-issues.md |
| Issue dependencies | Tracking blocked-by / blocking relationships | references/dependencies.md |
| Issue types (advanced) | GraphQL operations beyond MCP list_issue_types / type param | references/issue-types.md |
| Projects V2 | Project boards, progress reports, field management | references/projects.md |
| Issue fields | Custom metadata: dates, priority, text, numbers (private preview) | references/issue-fields.md |
| Images in issues | Embedding images in issue bodies and comments via CLI | references/images.md |
Issue Dependencies (Blocked By / Blocking)
Dependencies let you mark that an issue is blocked by another issue. This creates a formal dependency relationship visible in the UI and trackable via API. No MCP tools exist for dependencies; use REST or GraphQL directly.
Using REST API
List issues blocking this issue:
GET /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_byAdd a blocking dependency:
POST /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by
Body: { "issue_id": 12345 }The issue_id is the numeric issue ID (not the issue number).
Remove a blocking dependency:
DELETE /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by/{issue_id}Using GraphQL
Read dependencies:
{
repository(owner: "OWNER", name: "REPO") {
issue(number: 123) {
blockedBy(first: 10) { nodes { number title state } }
blocking(first: 10) { nodes { number title state } }
issueDependenciesSummary { blockedBy blocking totalBlockedBy totalBlocking }
}
}
}Add a dependency:
mutation {
addBlockedBy(input: {
issueId: "BLOCKED_ISSUE_NODE_ID"
blockingIssueId: "BLOCKING_ISSUE_NODE_ID"
}) {
blockingIssue { number title }
}
}Remove a dependency:
mutation {
removeBlockedBy(input: {
issueId: "BLOCKED_ISSUE_NODE_ID"
blockingIssueId: "BLOCKING_ISSUE_NODE_ID"
}) {
blockingIssue { number title }
}
}Tracked issues (read-only)
Task-list tracking relationships are available via GraphQL as read-only fields:
trackedIssues(first: N)- issues tracked in this issue's task listtrackedInIssues(first: N)- issues whose task lists reference this issue
These are set automatically when issues are referenced in task lists (- [ ] #123). There are no mutations to manage them.
Images in Issues and Comments
How to embed images in GitHub issue bodies and comments programmatically via the CLI.
Methods (ranked by reliability)
1. GitHub Contents API (recommended for private repos)
Push image files to a branch in the same repo, then reference them with a URL that works for authenticated viewers.
Step 1: Create a branch
# Get the SHA of the default branch
SHA=$(gh api repos/{owner}/{repo}/git/ref/heads/main --jq '.object.sha')
# Create a new branch
gh api repos/{owner}/{repo}/git/refs -X POST \
-f ref="refs/heads/{username}/images" \
-f sha="$SHA"Step 2: Upload images via Contents API
# Base64-encode the image and upload
BASE64=$(base64 -i /path/to/image.png)
gh api repos/{owner}/{repo}/contents/docs/images/my-image.png \
-X PUT \
-f message="Add image" \
-f content="$BASE64" \
-f branch="{username}/images" \
--jq '.content.path'Repeat for each image. The Contents API creates a commit per file.
Step 3: Reference in markdown
Important: Usegithub.com/{owner}/{repo}/raw/{branch}/{path}format, NOTraw.githubusercontent.com. Theraw.githubusercontent.comURLs return 404 for private repos. Thegithub.com/.../raw/...format works because the browser sends auth cookies when the viewer is logged in and has repo access.
Pros: Works for any repo the viewer has access to, images live in version control, no expiration. Cons: Creates commits, viewers must be authenticated, images won't render in email notifications or for users without repo access.
2. Gist hosting (public images only)
Upload images as files in a gist. Only works for images you're comfortable making public.
# Create a gist with a placeholder file
gh gist create --public -f description.md <<< "Image hosting gist"
# Note: gh gist edit does NOT support binary files.
# You must use the API to add binary content to gists.Limitation: Gists don't support binary file uploads via the CLI. You'd need to base64-encode and store as text, which won't render as images. Not recommended.
3. Browser upload (most reliable rendering)
The most reliable way to get permanent image URLs is through the GitHub web UI:
1. Open the issue/comment in a browser 2. Drag-drop or paste the image into the comment editor 3. GitHub generates a permanent https://github.com/user-attachments/assets/{UUID} URL 4. These URLs work for anyone, even without repo access, and render in email notifications
Why the API can't do this: GitHub'supload/policies/assetsendpoint requires a browser session (CSRF token + cookies). It returns an HTML error page when called with API tokens. There is no public API for generatinguser-attachmentsURLs.
Taking screenshots programmatically
Use puppeteer-core with local Chrome to screenshot HTML mockups:
const puppeteer = require('puppeteer-core');
const browser = await puppeteer.launch({
executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
defaultViewport: { width: 900, height: 600, deviceScaleFactor: 2 }
});
const page = await browser.newPage();
await page.setContent(htmlString);
// Screenshot specific elements
const elements = await page.$$('.section');
for (let i = 0; i < elements.length; i++) {
await elements[i].screenshot({ path: `mockup-${i + 1}.png` });
}
await browser.close();Note: MCP Playwright may not connect to localhost due to network isolation. Use puppeteer-core with a local Chrome installation instead.
Quick reference
| Method | Private repos | Permanent | No auth needed | API-only |
|---|---|---|---|---|
Contents API + github.com/raw/ | ✅ | ✅ | ❌ | ✅ |
Browser drag-drop (user-attachments) | ✅ | ✅ | ✅ | ❌ |
raw.githubusercontent.com | ❌ (404) | ✅ | ❌ | ✅ |
| Gist | Public only | ✅ | ✅ | ❌ (no binary) |
Common pitfalls
- `raw.githubusercontent.com` returns 404 for private repos even with a valid token in the URL. GitHub's CDN does not pass auth headers through.
- API download URLs are temporary. URLs returned by
gh api repos/.../contents/...withdownload_urlinclude a token that expires. - `upload/policies/assets` requires a browser session. Do not attempt to call this endpoint from the CLI.
- Base64 encoding for large files can hit API payload limits. The Contents API has a ~100MB file size limit but practical limits are lower for base64-encoded payloads.
- Email notifications will not render images that require authentication. If email readability matters, use the browser upload method.
Issue Fields
Issue fields are custom metadata (dates, text, numbers, single-select) defined at the organization level and set per-issue. They are separate from labels, milestones, and assignees. Common examples: Start Date, Target Date, Priority, Impact, Effort.
Prefer issue fields over project fields. When you need to set metadata like dates, priority, or status on an issue, use issue fields (which live on the issue itself) rather than project fields (which live on a project item). Issue fields travel with the issue across projects and views, while project fields are scoped to a single project. Only use project fields when issue fields are not available or when the field is project-specific (e.g., sprint iterations).
REST API (recommended)
The REST API is the simplest way to discover fields and set values.
Discovering available fields
gh api orgs/{org}/issue-fields --jq '.[] | {id, name, options: [.options[]? | {id, name}]}'Reading field values on an issue
gh api repos/{owner}/{repo}/issues/{number}/issue-field-valuesSetting field values
gh api repos/{owner}/{repo}/issues/{number}/issue-field-values \
-X POST \
--input - <<'EOF'
{"issue_field_values": [{"field_id": 1, "value": "P1"}]}
EOFImportant: The payload must be a JSON object with an issue_field_values array. Each entry has:
field_id(integer): the field's numeric ID from the org fields listvalue(string): the option name for single-select fields (e.g.,"P1","High"), or the literal value for text/number/date fields
Common mistakes to avoid:
- Passing the option ID instead of the option name as
value(the API expects the display name) - Sending
field_idandvalueas top-level keys without wrapping inissue_field_valuesarray - Using
-fflags instead of--inputwith JSON body
Example: Set priority to P1
# 1. Find the Priority field ID and option names
gh api orgs/{org}/issue-fields --jq '.[] | select(.name == "Priority")'
# 2. Set it (use the option NAME, not ID)
gh api repos/{owner}/{repo}/issues/{number}/issue-field-values \
-X POST \
--input - <<'EOF'
{"issue_field_values": [{"field_id": 1, "value": "P1"}]}
EOFExample: Set multiple fields at once
gh api repos/{owner}/{repo}/issues/{number}/issue-field-values \
-X POST \
--input - <<'EOF'
{"issue_field_values": [
{"field_id": 1, "value": "P1"},
{"field_id": 5, "value": "2026-06-01"},
{"field_id": 7, "value": "High"}
]}
EOFWorkflow for setting fields (REST)
1. Discover fields - gh api orgs/{org}/issue-fields to get field IDs and option names 2. Set values - POST to repos/{owner}/{repo}/issues/{number}/issue-field-values with JSON body 3. Batch when possible - multiple fields can be set in a single request
GraphQL API (alternative)
The GraphQL API requires the GraphQL-Features: issue_fields HTTP header. Without it, the fields are not visible in the schema.
Discovering available fields (GraphQL)
# Header: GraphQL-Features: issue_fields
{
organization(login: "OWNER") {
issueFields(first: 30) {
nodes {
__typename
... on IssueFieldDate { id name }
... on IssueFieldText { id name }
... on IssueFieldNumber { id name }
... on IssueFieldSingleSelect { id name options { id name color } }
}
}
}
}Field types: IssueFieldDate, IssueFieldText, IssueFieldNumber, IssueFieldSingleSelect.
Reading field values (GraphQL)
# Header: GraphQL-Features: issue_fields
{
repository(owner: "OWNER", name: "REPO") {
issue(number: 123) {
issueFieldValues(first: 20) {
nodes {
__typename
... on IssueFieldDateValue {
value
field { ... on IssueFieldDate { id name } }
}
... on IssueFieldTextValue {
value
field { ... on IssueFieldText { id name } }
}
... on IssueFieldNumberValue {
value
field { ... on IssueFieldNumber { id name } }
}
... on IssueFieldSingleSelectValue {
name
color
field { ... on IssueFieldSingleSelect { id name } }
}
}
}
}
}
}Setting field values (GraphQL)
Use setIssueFieldValue to set one or more fields at once. You need the issue's node ID and the field IDs from the discovery query above.
# Header: GraphQL-Features: issue_fields
mutation {
setIssueFieldValue(input: {
issueId: "ISSUE_NODE_ID"
issueFields: [
{ fieldId: "IFD_xxx", dateValue: "2026-04-15" }
{ fieldId: "IFT_xxx", textValue: "some text" }
{ fieldId: "IFN_xxx", numberValue: 3.0 }
{ fieldId: "IFSS_xxx", singleSelectOptionId: "OPTION_ID" }
]
}) {
issue { id title }
}
}Each entry in issueFields takes a fieldId plus exactly one value parameter:
| Field type | Value parameter | Format |
|---|---|---|
| Date | dateValue | ISO 8601 date string, e.g. "2026-04-15" |
| Text | textValue | String |
| Number | numberValue | Float |
| Single select | singleSelectOptionId | Node ID from the field's options list |
To clear a field value, set delete: true instead of a value parameter.
Searching by field values
GraphQL bulk query (recommended)
The most reliable way to find issues by field value is to fetch issues via GraphQL and filter by issueFieldValues. The search qualifier syntax (field.name:value) is not yet reliable across all environments.
# Find all open P1 issues in a repo
gh api graphql -H "GraphQL-Features: issue_fields" -f query='
{
repository(owner: "OWNER", name: "REPO") {
issues(first: 100, states: OPEN) {
nodes {
number
title
updatedAt
assignees(first: 3) { nodes { login } }
issueFieldValues(first: 10) {
nodes {
__typename
... on IssueFieldSingleSelectValue {
name
field { ... on IssueFieldSingleSelect { name } }
}
}
}
}
}
}
}' --jq '
[.data.repository.issues.nodes[] |
select(.issueFieldValues.nodes[] |
select(.field.name == "Priority" and .name == "P1")
) |
{number, title, updatedAt, assignees: [.assignees.nodes[].login]}
]'Schema notes for `IssueFieldSingleSelectValue`:
- The selected option's display text is in
.name(not.value) - Also available:
.color,.description,.id - The parent field reference is in
.field(use inline fragment to get the field name)
Search qualifier syntax (experimental)
Issue fields may also be searchable using dot notation in search queries. This requires advanced_search=true on REST or ISSUE_ADVANCED search type on GraphQL, but results are inconsistent and may return 0 results even when matching issues exist.
field.priority:P0 # Single-select equals value
field.target-date:>=2026-04-01 # Date comparison
has:field.priority # Has any value set
no:field.priority # Has no value setField names use the slug (lowercase, hyphens for spaces). For example, "Target Date" becomes target-date.
# REST API (may not return results in all environments)
gh api "search/issues?q=repo:owner/repo+field.priority:P0+is:open&advanced_search=true" \
--jq '.items[] | "#\(.number): \(.title)"'Warning: The colon notation (field:Priority:P1) is silently ignored. If using search qualifiers, always use dot notation (field.priority:P1). However, the GraphQL bulk query approach above is more reliable. See search.md for the full search guide.
Issue Types (Advanced GraphQL)
Issue types (Bug, Feature, Task, Epic, etc.) are defined at the organization level and inherited by repositories. They categorize issues beyond labels.
For basic usage, the MCP tools handle issue types natively. Call mcp__github__list_issue_types to discover types, and pass type: "Bug" to mcp__github__create_issue or mcp__github__update_issue. This reference covers advanced GraphQL operations.
GraphQL Feature Header
All GraphQL issue type operations require the GraphQL-Features: issue_types HTTP header.
List types (org or repo level)
# Header: GraphQL-Features: issue_types
{
organization(login: "OWNER") {
issueTypes(first: 20) {
nodes { id name color description isEnabled }
}
}
}Types can also be listed per-repo via repository.issueTypes or looked up by name via repository.issueType(name: "Bug").
Read an issue's type
# Header: GraphQL-Features: issue_types
{
repository(owner: "OWNER", name: "REPO") {
issue(number: 123) {
issueType { id name color }
}
}
}Set type on an existing issue
# Header: GraphQL-Features: issue_types
mutation {
updateIssueIssueType(input: {
issueId: "ISSUE_NODE_ID"
issueTypeId: "IT_xxx"
}) {
issue { id issueType { name } }
}
}Create issue with type
# Header: GraphQL-Features: issue_types
mutation {
createIssue(input: {
repositoryId: "REPO_NODE_ID"
title: "Fix login bug"
issueTypeId: "IT_xxx"
}) {
issue { id number issueType { name } }
}
}To clear the type, set issueTypeId to null.
Available colors
GRAY, BLUE, GREEN, YELLOW, ORANGE, RED, PINK, PURPLE
Projects V2
GitHub Projects V2 is managed via GraphQL. The MCP server provides three tools that wrap the GraphQL API, so you typically don't need raw GraphQL.
Using MCP tools (preferred)
List projects: Call mcp__github__projects_list with method: "list_projects", owner, and owner_type ("user" or "organization").
List project fields: Call mcp__github__projects_list with method: "list_project_fields" and project_number.
List project items: Call mcp__github__projects_list with method: "list_project_items" and project_number.
Add an issue/PR to a project: Call mcp__github__projects_write with method: "add_project_item", project_id (node ID), and content_id (issue/PR node ID).
Update a project item field value: Call mcp__github__projects_write with method: "update_project_item", project_id, item_id, field_id, and value (object with one of: text, number, date, singleSelectOptionId, iterationId).
Delete a project item: Call mcp__github__projects_write with method: "delete_project_item", project_id, and item_id.
Workflow for project operations
1. Find the project — see Finding a project by name below 2. Discover fields - use projects_list with list_project_fields to get field IDs and option IDs 3. Find items - use projects_list with list_project_items to get item IDs 4. Mutate - use projects_write to add, update, or delete items
Finding a project by name
⚠️ Known issue: projectsV2(query: "…") does keyword search, not exact name match, and returns results sorted by recency. Common words like "issue" or "bug" return hundreds of false positives. The actual project may be buried dozens of pages deep.Use this priority order:
1. Direct lookup (if you know the number)
gh api graphql -f query='{
organization(login: "ORG") {
projectV2(number: 42) { id title }
}
}' --jq '.data.organization.projectV2'2. Reverse lookup from a known issue (most reliable)
If the user mentions an issue, epic, or milestone that's in the project, query that issue's projectItems to discover the project:
gh api graphql -f query='{
repository(owner: "OWNER", name: "REPO") {
issue(number: 123) {
projectItems(first: 10) {
nodes {
id
project { number title id }
}
}
}
}
}' --jq '.data.repository.issue.projectItems.nodes[] | {number: .project.number, title: .project.title, id: .project.id}'This is the most reliable approach for large orgs where name search fails.
3. GraphQL name search with client-side filtering (fallback)
Query a large page and filter client-side for an exact title match:
gh api graphql -f query='{
organization(login: "ORG") {
projectsV2(first: 100, query: "search term") {
nodes { number title id }
}
}
}' --jq '.data.organization.projectsV2.nodes[] | select(.title | test("(?i)^exact name$"))'If this returns nothing, paginate with after cursor or broaden the regex. Results are sorted by recency so older projects require pagination.
4. MCP tool (small orgs only)
Call mcp__github__projects_list with method: "list_projects". This works well for orgs with <50 projects but has no name filter, so you must scan all results.
Project discovery for progress reports
When a user asks for a progress update on a project (e.g., "Give me a progress update for Project X"), follow this workflow:
1. Find the project — use the finding a project strategies above. Ask the user for a known issue number if name search fails.
2. Discover fields - call projects_list with list_project_fields to find the Status field (its options tell you the workflow stages) and any Iteration field (to scope to the current sprint).
3. Get all items - call projects_list with list_project_items. For large projects (100+ items), paginate through all pages. Each item includes its field values (status, iteration, assignees).
4. Build the report - group items by Status field value and count them. For iteration-based projects, filter to the current iteration first. Present a breakdown like:
Project: Issue Fields (Iteration 42, Mar 2-8)
15 actionable items:
🎉 Done: 4 (27%)
In Review: 3
In Progress: 3
Ready: 2
Blocked: 25. Add context - if items have sub-issues, include subIssuesSummary counts. If items have dependencies, note blocked items and what blocks them.
OAuth Scope Requirements
| Operation | Required scope |
|---|---|
| Read projects, fields, items | read:project |
| Add/update/delete items, change field values | project |
Common pitfall: The default gh auth token often only has read:project. Mutations will fail with INSUFFICIENT_SCOPES. To add the write scope:
gh auth refresh -h github.com -s projectThis triggers a browser-based OAuth flow. You must complete it before mutations will work.
Finding an Issue's Project Item ID
When you know the issue but need its project item ID (e.g., to update its Status), query from the issue side:
gh api graphql -f query='
{
repository(owner: "OWNER", name: "REPO") {
issue(number: 123) {
projectItems(first: 5) {
nodes {
id
project { title number }
fieldValues(first: 10) {
nodes {
... on ProjectV2ItemFieldSingleSelectValue {
name
field { ... on ProjectV2SingleSelectField { name } }
}
}
}
}
}
}
}
}' --jq '.data.repository.issue.projectItems.nodes'This returns the item ID, project info, and current field values in one query.
Using GraphQL via gh api (recommended)
Use gh api graphql to run GraphQL queries and mutations. This is more reliable than MCP tools for write operations.
Find a project and its Status field options:
gh api graphql -f query='
{
organization(login: "ORG") {
projectV2(number: 5) {
id
title
field(name: "Status") {
... on ProjectV2SingleSelectField {
id
options { id name }
}
}
}
}
}' --jq '.data.organization.projectV2'List all fields (including iterations):
gh api graphql -f query='
{
node(id: "PROJECT_ID") {
... on ProjectV2 {
fields(first: 20) {
nodes {
... on ProjectV2Field { id name }
... on ProjectV2SingleSelectField { id name options { id name } }
... on ProjectV2IterationField { id name configuration { iterations { id startDate } } }
}
}
}
}
}' --jq '.data.node.fields.nodes'Update a field value (e.g., set Status to "In Progress"):
gh api graphql -f query='
mutation {
updateProjectV2ItemFieldValue(input: {
projectId: "PROJECT_ID"
itemId: "ITEM_ID"
fieldId: "FIELD_ID"
value: { singleSelectOptionId: "OPTION_ID" }
}) {
projectV2Item { id }
}
}'Value accepts one of: text, number, date, singleSelectOptionId, iterationId.
Add an item:
gh api graphql -f query='
mutation {
addProjectV2ItemById(input: {
projectId: "PROJECT_ID"
contentId: "ISSUE_OR_PR_NODE_ID"
}) {
item { id }
}
}'Delete an item:
gh api graphql -f query='
mutation {
deleteProjectV2Item(input: {
projectId: "PROJECT_ID"
itemId: "ITEM_ID"
}) {
deletedItemId
}
}'End-to-End Example: Set Issue Status to "In Progress"
# 1. Get the issue's project item ID, project ID, and current status
gh api graphql -f query='{
repository(owner: "github", name: "planning-tracking") {
issue(number: 2574) {
projectItems(first: 1) {
nodes { id project { id title } }
}
}
}
}' --jq '.data.repository.issue.projectItems.nodes[0]'
# 2. Get the Status field ID and "In Progress" option ID
gh api graphql -f query='{
node(id: "PROJECT_ID") {
... on ProjectV2 {
field(name: "Status") {
... on ProjectV2SingleSelectField { id options { id name } }
}
}
}
}' --jq '.data.node.field'
# 3. Update the status
gh api graphql -f query='mutation {
updateProjectV2ItemFieldValue(input: {
projectId: "PROJECT_ID"
itemId: "ITEM_ID"
fieldId: "FIELD_ID"
value: { singleSelectOptionId: "IN_PROGRESS_OPTION_ID" }
}) { projectV2Item { id } }
}'Advanced Issue Search
The search_issues MCP tool uses GitHub's issue search query format for cross-repo searches, supporting implicit-AND queries, date ranges, and metadata filters (but not explicit OR/NOT operators).
When to Use Search vs List vs Advanced Search
There are three ways to find issues, each with different capabilities:
| Capability | list_issues (MCP) | search_issues (MCP) | Advanced search (gh api) |
|---|---|---|---|
| Scope | Single repo only | Cross-repo, cross-org | Cross-repo, cross-org |
Issue field filters (field.priority:P0) | No | No | Yes (dot notation) |
Issue type filter (type:Bug) | No | Yes | Yes |
| Boolean logic (AND/OR/NOT, nesting) | No | Yes (implicit AND only) | Yes (explicit AND/OR/NOT) |
| Label/state/date filters | Yes | Yes | Yes |
| Assignee/author/mentions | No | Yes | Yes |
Negation (-label:x, no:label) | No | Yes | Yes |
| Text search (title/body/comments) | No | Yes | Yes |
| `since` filter | Yes | No | No |
| Result limit | No cap (paginate all) | 1,000 max | 1,000 max |
| How to call | MCP tool directly | MCP tool directly | gh api with advanced_search=true |
Decision guide:
- Single repo, simple filters (state, labels, recent updates): use
list_issues - Cross-repo, text search, author/assignee, issue types: use
search_issues - Issue field values (Priority, dates, custom fields) or complex boolean logic: use
gh apiwithadvanced_search=true
Query Syntax
The query parameter is a string of search terms and qualifiers. A space between terms is implicit AND.
Scoping
repo:owner/repo # Single repo (auto-added if you pass owner+repo params)
org:github # All repos in an org
user:octocat # All repos owned by user
in:title # Search only in title
in:body # Search only in body
in:comments # Search only in commentsState & Close Reason
is:open # Open issues (auto-added: is:issue)
is:closed # Closed issues
reason:completed # Closed as completed
reason:"not planned" # Closed as not plannedPeople
author:username # Created by
assignee:username # Assigned to
mentions:username # Mentions user
commenter:username # Has comment from
involves:username # Author OR assignee OR mentioned OR commenter
author:@me # Current authenticated user
team:org/team # Team mentionedLabels, Milestones, Projects, Types
label:"bug" # Has label (quote multi-word labels)
label:bug label:priority # Has BOTH labels (AND)
label:bug,enhancement # Has EITHER label (OR)
-label:wontfix # Does NOT have label
milestone:"v2.0" # In milestone
project:github/57 # In project board
type:"Bug" # Issue typeMissing Metadata
no:label # No labels assigned
no:milestone # No milestone
no:assignee # Unassigned
no:project # Not in any projectDates
All date qualifiers support >, <, >=, <=, and range (..) operators with ISO 8601 format:
created:>2026-01-01 # Created after Jan 1
updated:>=2026-03-01 # Updated since Mar 1
closed:2026-01-01..2026-02-01 # Closed in January
created:<2026-01-01 # Created before Jan 1Linked Content
linked:pr # Issue has a linked PR
-linked:pr # Issues not yet linked to any PR
linked:issue # PR is linked to an issueNumeric Filters
comments:>10 # More than 10 comments
comments:0 # No comments
interactions:>100 # Reactions + comments > 100
reactions:>50 # More than 50 reactionsBoolean Logic & Nesting
Use AND, OR, and parentheses (up to 5 levels deep, max 5 operators):
label:bug AND assignee:octocat
assignee:octocat OR assignee:hubot
(type:"Bug" AND label:P1) OR (type:"Feature" AND label:P1)
-author:app/dependabot # Exclude bot issuesA space between terms without an explicit operator is treated as AND.
Common Query Patterns
Unassigned bugs:
repo:owner/repo type:"Bug" no:assignee is:openIssues closed this week:
repo:owner/repo is:closed closed:>=2026-03-01Stale open issues (no updates in 90 days):
repo:owner/repo is:open updated:<2026-01-01Open issues without a linked PR (needs work):
repo:owner/repo is:open -linked:prIssues I'm involved in across an org:
org:github involves:@me is:openHigh-activity issues:
repo:owner/repo is:open comments:>20Issues by type and priority label:
repo:owner/repo type:"Epic" label:P1 is:openIssue Field Search
Reliability warning: The field.name:value search qualifier syntax is experimental and may return 0 results even when matching issues exist. For reliable filtering by field values, use the GraphQL bulk query approach documented in issue-fields.md.Issue fields can theoretically be searched via the field.name:value qualifier using advanced search mode. This works in the web UI but results from the API are inconsistent.
REST API
Add advanced_search=true as a query parameter:
gh api "search/issues?q=org:github+field.priority:P0+type:Epic+is:open&advanced_search=true" \
--jq '.items[] | "#\(.number): \(.title)"'GraphQL
Use type: ISSUE_ADVANCED instead of type: ISSUE:
{
search(query: "org:github field.priority:P0 type:Epic is:open", type: ISSUE_ADVANCED, first: 10) {
issueCount
nodes {
... on Issue { number title }
}
}
}Issue Field Qualifiers
The syntax uses dot notation with the field's slug name (lowercase, hyphens for spaces):
field.priority:P0 # Single-select field equals value
field.priority:P1 # Different option value
field.target-date:>=2026-04-01 # Date comparison
has:field.priority # Has any value set
no:field.priority # Has no value setMCP limitation: The search_issues MCP tool does not pass advanced_search=true. You must use gh api directly for issue field searches.
Common Field Search Patterns
P0 epics across an org:
org:github field.priority:P0 type:Epic is:openIssues with a target date this quarter:
org:github field.target-date:>=2026-04-01 field.target-date:<=2026-06-30 is:openOpen bugs missing priority:
org:github no:field.priority type:Bug is:openLimitations
- Query text: max 256 characters (excluding operators/qualifiers)
- Boolean operators: max 5 AND/OR/NOT per query
- Results: max 1,000 total (use
list_issuesif you need all issues) - Repo scan: searches up to 4,000 matching repositories
- Rate limit: 30 requests/minute for authenticated search
- Issue field search requires
advanced_search=true(REST) orISSUE_ADVANCED(GraphQL); not available through MCPsearch_issues
Sub-Issues and Parent Issues
Sub-issues let you break down work into hierarchical tasks. Each parent issue can have up to 100 sub-issues, nested up to 8 levels deep. Sub-issues can span repositories within the same owner.
Recommended Workflow
The simplest way to create a sub-issue is two steps: create the issue, then link it.
# Step 1: Create the issue and capture its numeric ID
ISSUE_ID=$(gh api repos/{owner}/{repo}/issues \
-X POST \
-f title="Sub-task title" \
-f body="Description" \
--jq '.id')
# Step 2: Link it as a sub-issue of the parent
# IMPORTANT: sub_issue_id must be an integer. Use --input (not -f) to send JSON.
echo "{\"sub_issue_id\": $ISSUE_ID}" | gh api repos/{owner}/{repo}/issues/{parent_number}/sub_issues -X POST --input -Why `--input` instead of `-f`? The gh api -f flag sends all values as strings, but the API requires sub_issue_id as an integer. Using -f sub_issue_id=12345 will return a 422 error.
Alternatively, use GraphQL createIssue with parentIssueId to do it in one step (see GraphQL section below).
Using MCP tools
List sub-issues: Call mcp__github__issue_read with method: "get_sub_issues", owner, repo, and issue_number.
Create an issue as a sub-issue: There is no MCP tool for creating sub-issues directly. Use the workflow above or GraphQL.
Using REST API
List sub-issues:
gh api repos/{owner}/{repo}/issues/{issue_number}/sub_issuesGet parent issue:
gh api repos/{owner}/{repo}/issues/{issue_number}/parentAdd an existing issue as a sub-issue:
# sub_issue_id is the numeric issue ID (not the issue number)
# Get it from the .id field when creating or fetching an issue
echo '{"sub_issue_id": 12345}' | gh api repos/{owner}/{repo}/issues/{parent_number}/sub_issues -X POST --input -To move a sub-issue that already has a parent, add "replace_parent": true to the JSON body.
Remove a sub-issue:
echo '{"sub_issue_id": 12345}' | gh api repos/{owner}/{repo}/issues/{parent_number}/sub_issue -X DELETE --input -Reprioritize a sub-issue:
echo '{"sub_issue_id": 6, "after_id": 5}' | gh api repos/{owner}/{repo}/issues/{parent_number}/sub_issues/priority -X PATCH --input -Use after_id or before_id to position the sub-issue relative to another.
Using GraphQL
Read parent and sub-issues:
{
repository(owner: "OWNER", name: "REPO") {
issue(number: 123) {
parent { number title }
subIssues(first: 50) {
nodes { number title state }
}
subIssuesSummary { total completed percentCompleted }
}
}
}Add a sub-issue:
mutation {
addSubIssue(input: {
issueId: "PARENT_NODE_ID"
subIssueId: "CHILD_NODE_ID"
}) {
issue { id }
subIssue { id number title }
}
}You can also use subIssueUrl instead of subIssueId (pass the issue's HTML URL). Add replaceParent: true to move a sub-issue from another parent.
Create an issue directly as a sub-issue:
mutation {
createIssue(input: {
repositoryId: "REPO_NODE_ID"
title: "Implement login validation"
parentIssueId: "PARENT_NODE_ID"
}) {
issue { id number }
}
}Remove a sub-issue:
mutation {
removeSubIssue(input: {
issueId: "PARENT_NODE_ID"
subIssueId: "CHILD_NODE_ID"
}) {
issue { id }
}
}Reprioritize a sub-issue:
mutation {
reprioritizeSubIssue(input: {
issueId: "PARENT_NODE_ID"
subIssueId: "CHILD_NODE_ID"
afterId: "OTHER_CHILD_NODE_ID"
}) {
issue { id }
}
}Use afterId or beforeId to position relative to another sub-issue.
Issue Templates
Copy and customize these templates for issue bodies.
Bug Report Template
## Description
[Clear description of the bug]
## Steps to Reproduce
1. [First step]
2. [Second step]
3. [And so on...]
## Expected Behavior
[What should happen]
## Actual Behavior
[What actually happens]
## Environment
- Browser: [e.g., Chrome 120]
- OS: [e.g., macOS 14.0]
- Version: [e.g., v1.2.3]
## Screenshots/Logs
[If applicable]
## Additional Context
[Any other relevant information]Feature Request Template
## Summary
[One-line description of the feature]
## Motivation
[Why is this feature needed? What problem does it solve?]
## Proposed Solution
[How should this feature work?]
## Acceptance Criteria
- [ ] [Criterion 1]
- [ ] [Criterion 2]
- [ ] [Criterion 3]
## Alternatives Considered
[Other approaches considered and why they weren't chosen]
## Additional Context
[Mockups, examples, or related issues]Task Template
## Objective
[What needs to be accomplished]
## Details
[Detailed description of the work]
## Checklist
- [ ] [Subtask 1]
- [ ] [Subtask 2]
- [ ] [Subtask 3]
## Dependencies
[Any blockers or related work]
## Notes
[Additional context or considerations]Minimal Template
For simple issues:
## Description
[What and why]
## Tasks
- [ ] [Task 1]
- [ ] [Task 2]Related skills
FAQ
Is Github Issues safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.