Improve github-issues skill: fix MCP tools, add search reference, fix sub-issues docs (#888)

* Add advanced search reference with query syntax guide

Covers search qualifiers, boolean logic, date ranges, missing metadata
filters, common patterns, and when to use search vs list_issues.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add issue fields search support to search and issue-fields references

- Add Advanced Search Mode section to search.md covering field: qualifier,
  has:field: syntax, REST advanced_search=true, and GraphQL ISSUE_ADVANCED
- Add Searching by Field Values section to issue-fields.md with REST/GraphQL
  examples and qualifier reference table
- Note MCP search_issues limitation (no advanced_search support)
- Update SKILL.md capability table to mention issue field filters

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Clarify three search approaches: list_issues vs search_issues vs advanced search

Replace the simple two-column comparison with a capability matrix showing
what each approach supports (field filters, boolean logic, scope, etc.)
and a decision guide for when to use each one.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix issue field search syntax: use dot notation (field.name:value)

The colon notation (field:Name:Value) is silently ignored by the API.
The correct syntax is dot notation (field.priority:P0) which works in
REST (advanced_search=true), GraphQL (ISSUE_ADVANCED), and web UI.

Also supports has:field.name, no:field.name, and date comparisons.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix github-issues skill: correct MCP tools and add gh api workflows

The skill listed 5 MCP tools that don't exist (create_issue, update_issue,
get_issue, add_issue_comment, list_issue_types), causing tool-not-found
errors when agents tried to follow the skill instructions.

Changes:
- Split tools table into MCP (read ops) and CLI/API (write ops)
- Add gh api commands for creating, updating, commenting on issues
- Document that gh issue create doesn't support --type flag
- Add GraphQL query for discovering issue types
- Remove redundant [Bug]/[Feature] title prefixes (use type param instead)
- Update examples to use gh api instead of non-existent MCP tools

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix sub-issues reference: add gh api examples and integer gotcha

The sub_issue_id parameter requires an integer but gh api -f sends strings,
causing 422 errors. Updated all REST examples to use --input with raw JSON.

Also added:
- Recommended two-step workflow (create issue, then link)
- Explicit warning about -f vs --input for integer params
- Proper gh api command syntax instead of raw HTTP notation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address PR review: fix search docs accuracy and broken link

- Fix misleading claim about full boolean logic support (implicit AND only)
- Remove sort:updated from query example (it's an API param, not a qualifier)
- Clarify -linked:pr comment to avoid confusion with authoring status
- Fix relative link in issue-fields.md (sibling file, not nested path)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Improve projects reference: scope warning, gh api examples, issue-side queries

From hands-on usage: hit INSUFFICIENT_SCOPES trying to update project
status because token had read:project but not project write scope.

Added:
- OAuth scope requirements table with gh auth refresh workaround
- How to find an issue's project item ID (query from issue side)
- All GraphQL examples now use gh api graphql (copy-paste ready)
- End-to-end example: set issue status to In Progress

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add images-in-issues reference: hosting methods, pitfalls, screenshots

Documents three approaches for embedding images in issue comments
via CLI: Contents API with github.com/raw/ URLs, browser drag-drop
for permanent user-attachments URLs, and gist hosting limitations.

Includes puppeteer screenshot recipe and comparison table.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Update README.skills.md with images reference

Run npm run build to regenerate skill listing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix issue-fields search: add GraphQL bulk query, caveat search syntax

The field.name:value search qualifier is unreliable via API (returns
0 results even when matching issues exist). Added a recommended
GraphQL approach that fetches issues and filters by issueFieldValues
client-side. Documented the correct IssueFieldSingleSelectValue
schema (name, not value). Marked search qualifier syntax as
experimental with a reliability warning.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(github-issues): improve project discovery guidance

The projectsV2 query param does keyword search, not exact match,
and sorts by recency. For large orgs like github, common words like
'issue' return 400+ results and bury the target project.

Added a priority-ordered discovery strategy:
1. Direct lookup by number (instant)
2. Reverse lookup from a known issue's projectItems (most reliable)
3. GraphQL name search with client-side jq filtering (fallback)
4. MCP tool (small orgs only)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Tadas Labudis
2026-03-06 10:20:36 +00:00
committed by GitHub
parent 1b15663c46
commit fca5de1f6a
7 changed files with 721 additions and 99 deletions

View File

@@ -122,7 +122,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-skills) for guidelines on how to
| [git-commit](../skills/git-commit/SKILL.md) | Execute git commit with conventional commit message analysis, intelligent staging, and message generation. Use when user asks to commit changes, create a git commit, or mentions "/commit". Supports: (1) Auto-detecting type and scope from changes, (2) Generating conventional commit messages from diff, (3) Interactive commit with optional type/scope/description overrides, (4) Intelligent file staging for logical grouping | None |
| [git-flow-branch-creator](../skills/git-flow-branch-creator/SKILL.md) | Intelligent Git Flow branch creator that analyzes git status/diff and creates appropriate branches following the nvie Git Flow branching model. | None |
| [github-copilot-starter](../skills/github-copilot-starter/SKILL.md) | Set up complete GitHub Copilot configuration for a new project based on technology stack | None |
| [github-issues](../skills/github-issues/SKILL.md) | Create, update, and manage GitHub issues using MCP tools. Use this skill when users want to create bug reports, feature requests, or task issues, update existing issues, add labels/assignees/milestones, set issue fields (dates, priority, custom fields), set issue types, or manage issue workflows. Triggers on requests like "create an issue", "file a bug", "request a feature", "update issue X", "set the priority", "set the start date", or any GitHub issue management task. | `references/dependencies.md`<br />`references/issue-fields.md`<br />`references/issue-types.md`<br />`references/projects.md`<br />`references/sub-issues.md`<br />`references/templates.md` |
| [github-issues](../skills/github-issues/SKILL.md) | Create, update, and manage GitHub issues using MCP tools. Use this skill when users want to create bug reports, feature requests, or task issues, update existing issues, add labels/assignees/milestones, set issue fields (dates, priority, custom fields), set issue types, or manage issue workflows. Triggers on requests like "create an issue", "file a bug", "request a feature", "update issue X", "set the priority", "set the start date", or any GitHub issue management task. | `references/dependencies.md`<br />`references/images.md`<br />`references/issue-fields.md`<br />`references/issue-types.md`<br />`references/projects.md`<br />`references/search.md`<br />`references/sub-issues.md`<br />`references/templates.md` |
| [go-mcp-server-generator](../skills/go-mcp-server-generator/SKILL.md) | Generate a complete Go MCP server project with proper structure, dependencies, and implementation using the official github.com/modelcontextprotocol/go-sdk. | None |
| [image-manipulation-image-magick](../skills/image-manipulation-image-magick/SKILL.md) | Process and manipulate images using ImageMagick. Supports resizing, format conversion, batch processing, and retrieving image metadata. Use when working with images, creating thumbnails, resizing wallpapers, or performing batch image operations. | None |
| [import-infrastructure-as-code](../skills/import-infrastructure-as-code/SKILL.md) | Import existing Azure resources into Terraform using Azure CLI discovery and Azure Verified Modules (AVM). Use when asked to reverse-engineer live Azure infrastructure, generate Infrastructure as Code from existing subscriptions/resource groups/resource IDs, map dependencies, derive exact import addresses from downloaded module source, prevent configuration drift, and produce AVM-based Terraform files ready for validation and planning across any Azure resource type. | None |

View File

@@ -7,63 +7,81 @@ description: 'Create, update, and manage GitHub issues using MCP tools. Use this
Manage GitHub issues using the `@modelcontextprotocol/server-github` MCP server.
## Available MCP Tools
## Available Tools
### MCP Tools (read operations)
| Tool | Purpose |
|------|---------|
| `mcp__github__create_issue` | Create new issues |
| `mcp__github__update_issue` | Update existing issues |
| `mcp__github__get_issue` | Fetch issue details |
| `mcp__github__search_issues` | Search issues |
| `mcp__github__add_issue_comment` | Add comments |
| `mcp__github__list_issues` | List repository issues |
| `mcp__github__list_issue_types` | List available issue types for an organization |
| `mcp__github__issue_read` | Read issue details, sub-issues, comments, labels |
| `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](references/templates.md)
4. **Execute**: Call the appropriate MCP tool
4. **Execute**: Use MCP tools for reads, `gh api` for writes
5. **Confirm**: Report the issue URL to user
## Creating Issues
### Required Parameters
Use `gh api` to create issues. This supports all parameters including issue types.
```
owner: repository owner (org or user)
repo: repository name
title: clear, actionable title
body: structured markdown content
```bash
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:
```
labels: ["bug", "enhancement", "documentation", ...]
assignees: ["username1", "username2"]
milestone: milestone number (integer)
type: issue type name (e.g., "Bug", "Feature", "Task", "Epic")
-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 number
```
**Issue types** are organization-level metadata. Before using `type`, call `mcp__github__list_issue_types` with the org name to discover available types. If the org has no issue types configured, omit the parameter.
**Issue types** are organization-level metadata. To discover available types, use:
```bash
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
- Start with type prefix when useful: `[Bug]`, `[Feature]`, `[Docs]`
- Be specific and actionable
- Keep under 72 characters
- When issue types are set, don't add redundant prefixes like `[Bug]`
- Examples:
- `[Bug] Login fails with SSO enabled`
- `[Feature] Add dark mode support`
- `Add unit tests for auth module`
- `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
@@ -77,14 +95,17 @@ Always use the templates in [references/templates.md](references/templates.md).
## Updating Issues
Use `mcp__github__update_issue` with:
Use `gh api` with PATCH:
```
owner, repo, issue_number (required)
title, body, state, labels, assignees, milestone (optional - only changed fields)
```bash
gh api repos/{owner}/{repo}/issues/{number} \
-X PATCH \
-f state=closed \
-f title="Updated title" \
--jq '{number, html_url}'
```
State values: `open`, `closed`
Only include fields you want to change. Available fields: `title`, `body`, `state` (open/closed), `labels`, `assignees`, `milestone`.
## Examples
@@ -92,31 +113,54 @@ State values: `open`, `closed`
**User**: "Create a bug issue - the login page crashes when using SSO"
**Action**: Call `mcp__github__create_issue` with:
```json
{
"owner": "github",
"repo": "awesome-copilot",
"title": "[Bug] Login page crashes when using SSO",
"body": "## Description\nThe login page crashes when users attempt to authenticate using SSO.\n\n## Steps to Reproduce\n1. Navigate to login page\n2. Click 'Sign in with SSO'\n3. Page crashes\n\n## Expected Behavior\nSSO authentication should complete and redirect to dashboard.\n\n## Actual Behavior\nPage becomes unresponsive and displays error.\n\n## Environment\n- Browser: [To be filled]\n- OS: [To be filled]\n\n## Additional Context\nReported by user.",
"type": "Bug"
}
**Action**:
```bash
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**: Call `mcp__github__create_issue` with:
```json
{
"owner": "github",
"repo": "awesome-copilot",
"title": "[Feature] Add dark mode support",
"body": "## Summary\nAdd dark mode theme option for improved user experience and accessibility.\n\n## Motivation\n- Reduces eye strain in low-light environments\n- Increasingly expected by users\n- Improves accessibility\n\n## Proposed Solution\nImplement theme toggle with system preference detection.\n\n## Acceptance Criteria\n- [ ] Toggle switch in settings\n- [ ] Persists user preference\n- [ ] Respects system preference by default\n- [ ] All UI components support both themes\n\n## Alternatives Considered\nNone specified.\n\n## Additional Context\nHigh priority request.",
"type": "Feature",
"labels": ["high-priority"]
}
**Action**:
```bash
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
@@ -148,8 +192,10 @@ The following features require REST or GraphQL APIs beyond the basic MCP tools.
| 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](references/search.md) |
| Sub-issues & parent issues | Breaking work into hierarchical tasks | [references/sub-issues.md](references/sub-issues.md) |
| Issue dependencies | Tracking blocked-by / blocking relationships | [references/dependencies.md](references/dependencies.md) |
| Issue types (advanced) | GraphQL operations beyond MCP `list_issue_types` / `type` param | [references/issue-types.md](references/issue-types.md) |
| Projects V2 | Project boards, progress reports, field management | [references/projects.md](references/projects.md) |
| Issue fields | Custom metadata: dates, priority, text, numbers (private preview) | [references/issue-fields.md](references/issue-fields.md) |
| Images in issues | Embedding images in issue bodies and comments via CLI | [references/images.md](references/images.md) |

View File

@@ -0,0 +1,116 @@
# 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**
```bash
# 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**
```bash
# 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**
```markdown
![Description](https://github.com/{owner}/{repo}/raw/{username}/images/docs/images/my-image.png)
```
> **Important:** Use `github.com/{owner}/{repo}/raw/{branch}/{path}` format, NOT `raw.githubusercontent.com`. The `raw.githubusercontent.com` URLs return 404 for private repos. The `github.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.
```bash
# 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's `upload/policies/assets` endpoint requires a browser session (CSRF token + cookies). It returns an HTML error page when called with API tokens. There is no public API for generating `user-attachments` URLs.
## Taking screenshots programmatically
Use `puppeteer-core` with local Chrome to screenshot HTML mockups:
```javascript
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/...` with `download_url` include 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.

View File

@@ -125,3 +125,67 @@ mutation {
}
}'
```
## 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.
```bash
# 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 set
```
Field names use the **slug** (lowercase, hyphens for spaces). For example, "Target Date" becomes `target-date`.
```bash
# 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](search.md) for the full search guide.

View File

@@ -24,16 +24,69 @@ Call `mcp__github__projects_write` with `method: "delete_project_item"`, `projec
## Workflow for project operations
1. **Find the project** - use `projects_list` with `list_projects` to get the project number and node ID
1. **Find the project** — see [Finding a project by name](#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)
```bash
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:
```bash
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:
```bash
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. **Search by name** - call `projects_list` with `list_projects` and scan results for a title matching the user's query. Project names are often informal, so match flexibly (e.g., "issue fields" matches "Issue fields" or "Issue Fields and Types").
1. **Find the project** — use the [finding a project](#finding-a-project-by-name) 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).
@@ -53,23 +106,77 @@ When a user asks for a progress update on a project (e.g., "Give me a progress u
5. **Add context** - if items have sub-issues, include `subIssuesSummary` counts. If items have dependencies, note blocked items and what blocks them.
**Tip:** For org-level projects, use GraphQL with `organization.projectsV2(first: 20, query: "search term")` to search by name directly, which is faster than listing all projects.
## OAuth Scope Requirements
## Using GraphQL directly (advanced)
| Operation | Required scope |
|-----------|---------------|
| Read projects, fields, items | `read:project` |
| Add/update/delete items, change field values | `project` |
Required scope: `read:project` for queries, `project` for mutations.
**Common pitfall:** The default `gh auth` token often only has `read:project`. Mutations will fail with `INSUFFICIENT_SCOPES`. To add the write scope:
**Find a project:**
```graphql
{
organization(login: "ORG") {
projectV2(number: 5) { id title }
}
}
```bash
gh auth refresh -h github.com -s project
```
**List fields (including single-select options):**
```graphql
This 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:
```bash
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:**
```bash
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):**
```bash
gh api graphql -f query='
{
node(id: "PROJECT_ID") {
... on ProjectV2 {
@@ -82,23 +189,12 @@ Required scope: `read:project` for queries, `project` for mutations.
}
}
}
}
}' --jq '.data.node.fields.nodes'
```
**Add an item:**
```graphql
mutation {
addProjectV2ItemById(input: {
projectId: "PROJECT_ID"
contentId: "ISSUE_OR_PR_NODE_ID"
}) {
item { id }
}
}
```
**Update a field value:**
```graphql
**Update a field value (e.g., set Status to "In Progress"):**
```bash
gh api graphql -f query='
mutation {
updateProjectV2ItemFieldValue(input: {
projectId: "PROJECT_ID"
@@ -108,13 +204,27 @@ mutation {
}) {
projectV2Item { id }
}
}
}'
```
Value accepts one of: `text`, `number`, `date`, `singleSelectOptionId`, `iterationId`.
**Add an item:**
```bash
gh api graphql -f query='
mutation {
addProjectV2ItemById(input: {
projectId: "PROJECT_ID"
contentId: "ISSUE_OR_PR_NODE_ID"
}) {
item { id }
}
}'
```
**Delete an item:**
```graphql
```bash
gh api graphql -f query='
mutation {
deleteProjectV2Item(input: {
projectId: "PROJECT_ID"
@@ -122,5 +232,42 @@ mutation {
}) {
deletedItemId
}
}
}'
```
## End-to-End Example: Set Issue Status to "In Progress"
```bash
# 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 } }
}'
```
```

View File

@@ -0,0 +1,231 @@
# 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 api` with `advanced_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 comments
```
### State & 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 planned
```
### People
```
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 mentioned
```
### Labels, 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 type
```
### Missing Metadata
```
no:label # No labels assigned
no:milestone # No milestone
no:assignee # Unassigned
no:project # Not in any project
```
### Dates
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 1
```
### Linked Content
```
linked:pr # Issue has a linked PR
-linked:pr # Issues not yet linked to any PR
linked:issue # PR is linked to an issue
```
### Numeric Filters
```
comments:>10 # More than 10 comments
comments:0 # No comments
interactions:>100 # Reactions + comments > 100
reactions:>50 # More than 50 reactions
```
### Boolean 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 issues
```
A space between terms without an explicit operator is treated as AND.
## Common Query Patterns
**Unassigned bugs:**
```
repo:owner/repo type:"Bug" no:assignee is:open
```
**Issues closed this week:**
```
repo:owner/repo is:closed closed:>=2026-03-01
```
**Stale open issues (no updates in 90 days):**
```
repo:owner/repo is:open updated:<2026-01-01
```
**Open issues without a linked PR (needs work):**
```
repo:owner/repo is:open -linked:pr
```
**Issues I'm involved in across an org:**
```
org:github involves:@me is:open
```
**High-activity issues:**
```
repo:owner/repo is:open comments:>20
```
**Issues by type and priority label:**
```
repo:owner/repo type:"Epic" label:P1 is:open
```
## Issue 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.md#searching-by-field-values).
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:
```bash
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`:
```graphql
{
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 set
```
**MCP 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:open
```
**Issues with a target date this quarter:**
```
org:github field.target-date:>=2026-04-01 field.target-date:<=2026-06-30 is:open
```
**Open bugs missing priority:**
```
org:github no:field.priority type:Bug is:open
```
## Limitations
- Query text: max **256 characters** (excluding operators/qualifiers)
- Boolean operators: max **5** AND/OR/NOT per query
- Results: max **1,000** total (use `list_issues` if 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) or `ISSUE_ADVANCED` (GraphQL); not available through MCP `search_issues`

View File

@@ -2,46 +2,64 @@
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.
```bash
# 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 REST or GraphQL (see below).
There is no MCP tool for creating sub-issues directly. Use the workflow above or GraphQL.
## Using REST API
**List sub-issues:**
```
GET /repos/{owner}/{repo}/issues/{issue_number}/sub_issues
```bash
gh api repos/{owner}/{repo}/issues/{issue_number}/sub_issues
```
**Get parent issue:**
```
GET /repos/{owner}/{repo}/issues/{issue_number}/parent
```bash
gh api repos/{owner}/{repo}/issues/{issue_number}/parent
```
**Add an existing issue as a sub-issue:**
```
POST /repos/{owner}/{repo}/issues/{issue_number}/sub_issues
Body: { "sub_issue_id": 12345 }
```bash
# 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 -
```
The `sub_issue_id` is the numeric issue **ID** (not the issue number). Get it from the issue's `id` field in any API response.
To move a sub-issue that already has a parent, add `"replace_parent": true`.
To move a sub-issue that already has a parent, add `"replace_parent": true` to the JSON body.
**Remove a sub-issue:**
```
DELETE /repos/{owner}/{repo}/issues/{issue_number}/sub_issue
Body: { "sub_issue_id": 12345 }
```bash
echo '{"sub_issue_id": 12345}' | gh api repos/{owner}/{repo}/issues/{parent_number}/sub_issue -X DELETE --input -
```
**Reprioritize a sub-issue:**
```
PATCH /repos/{owner}/{repo}/issues/{issue_number}/sub_issues/priority
Body: { "sub_issue_id": 6, "after_id": 5 }
```bash
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.