Repository navigation
Add Issues 2.0 support: issue types, sub-issues, and relationships - #13057
Conversation
There was a problem hiding this comment.
Pull request overview
Adds support for GitHub Issues 2.0 capabilities (issue types, sub-issues, and blocked-by/blocking relationships) across gh issue create/edit/view/list, including GHES feature detection for relationships.
Changes:
- Adds new GraphQL fields/types and mutations for issue types, sub-issues, and relationships; exports these via
--json. - Extends issue create/edit/list/view commands with new flags, interactive prompts, and TTY rendering for Issues 2.0 metadata.
- Introduces GHES relationship feature detection via schema introspection and adds/updates tests for flags and behaviors.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/cmd/pr/shared/survey_test.go | Regression test ensuring issue-only editable fields don’t appear unless explicitly allowed. |
| pkg/cmd/pr/shared/params.go | Adds IssueType filtering into search query building. |
| pkg/cmd/pr/shared/editable.go | Adds IssueType/Parent editable fields, survey prompts, and option fetching. |
| pkg/cmd/issue/view/view_test.go | Adds TTY and JSON tests for Issues 2.0 fields in gh issue view. |
| pkg/cmd/issue/view/view.go | Adds default fields and TTY rendering for issue type/parent/sub-issues/relationships; GHES-gates relationship query fields. |
| pkg/cmd/issue/shared/resolve.go | Adds shared helpers to resolve issue refs and type names. |
| pkg/cmd/issue/list/list_test.go | Adds tests for --type flag parsing and query behavior. |
| pkg/cmd/issue/list/list.go | Adds --type filter support to issue list search path. |
| pkg/cmd/issue/edit/edit_test.go | Adds flag parsing and behavior tests for type/parent/sub-issues/relationships including GHES unsupported path. |
| pkg/cmd/issue/edit/edit.go | Implements new edit flags and mutations for type/parent/sub-issues/relationships, plus interactive picker integration. |
| pkg/cmd/issue/create/create_test.go | Adds flag parsing and behavior tests for type/parent/relationships including GHES unsupported path. |
| pkg/cmd/issue/create/create.go | Implements create flags, interactive type selection, and post-create mutations for Issues 2.0 fields. |
| internal/featuredetection/feature_detection_test.go | Adds tests for relationship support detection via schema introspection. |
| internal/featuredetection/feature_detection.go | Adds IssueRelationshipsSupported detection and plumbing. |
| api/query_builder.go | Adds GraphQL fragment cases and issue-only fields for Issues 2.0 fields. |
| api/queries_issue.go | Adds Issue struct fields/types plus mutations and helper queries for issue types/refs/relationships. |
| api/export_pr.go | Extends JSON export mapping for new issue fields (type, parent, sub-issues, relationships). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if err == nil { | ||
| typeNames := make([]string, len(issueTypes)) | ||
| for i, t := range issueTypes { | ||
| typeNames[i] = t.Name | ||
| } | ||
| editable.IssueType.Options = typeNames | ||
| } |
There was a problem hiding this comment.
FetchOptions silently ignores errors from api.RepoIssueTypes when editable.IssueType.Edited is true. In interactive edit flows this can result in the user selecting “Type” but never being prompted (and no update happening) with no indication why. Recommend returning the error (or at least surfacing it) when the user has opted into editing Type, so failures don’t degrade silently.
| if err == nil { | |
| typeNames := make([]string, len(issueTypes)) | |
| for i, t := range issueTypes { | |
| typeNames[i] = t.Name | |
| } | |
| editable.IssueType.Options = typeNames | |
| } | |
| if err != nil { | |
| return fmt.Errorf("fetching issue types: %w", err) | |
| } | |
| typeNames := make([]string, len(issueTypes)) | |
| for i, t := range issueTypes { | |
| typeNames[i] = t.Name | |
| } | |
| editable.IssueType.Options = typeNames |
There was a problem hiding this comment.
I mean I think this could happen if it's a personal repo, so maybe this is actually a deeper thing in that we should be detecting if it's a repo that even supports issue types.
| if err != nil { | ||
| return err | ||
| } | ||
| editable.IssueType.Value = editable.IssueType.Options[selected] |
There was a problem hiding this comment.
If editable.IssueType.Edited is true but IssueType.Options is empty, the survey currently skips prompting and leaves IssueType.Value unchanged, even though the user explicitly chose to edit the field. This can lead to a “successful” submission that does nothing. Consider returning an error in this case (e.g., “no issue types available”) or falling back to a free-text input prompt.
| editable.IssueType.Value = editable.IssueType.Options[selected] | |
| editable.IssueType.Value = editable.IssueType.Options[selected] | |
| } else { | |
| editable.IssueType.Value, err = p.Input("Type", editable.IssueType.Default) | |
| if err != nil { | |
| return err | |
| } |
There was a problem hiding this comment.
Same as above comment - this is a deeper problem. We gotta check if there issue types are even possible.
Though I suppose this could still happen if there are no issue types available.
Add API infrastructure for issue types, sub-issues, and issue relationships (blocked-by/blocking): - New types: IssueType, LinkedIssue, SubIssues, SubIssuesSummary, LinkedIssueConnection - New Issue struct fields for all Issues 2.0 data - GraphQL query builder cases for new fields - ExportData cases for JSON output - Mutation functions: UpdateIssueIssueType, AddSubIssue, RemoveSubIssue, AddBlockedBy, RemoveBlockedBy - Helper functions: RepoIssueTypes, IssueNodeID - Feature detection: IssueRelationshipsSupported for GHES 3.19+ (issue types and sub-issues are GA on GHES 3.17+, no detection needed) Co-authored-by: Copilot <[email protected]>
Display new issue metadata in TTY view: - Issue type on state line (gray, before Open/Closed) - Type, Parent, Blocked by, Blocking metadata rows - Sub-issues section with completion progress (X/Y, Z%) - Cross-repo references show full owner/repo#N format All new fields included in defaultFields and JSON export. Co-authored-by: Copilot <[email protected]>
Post-creation mutations for Issues 2.0 fields: - --type: resolve type name to ID via RepoIssueTypes, then updateIssueIssueType mutation - --parent: resolve issue ref (number or URL), then addSubIssue mutation (supports cross-repo URLs) - --blocked-by: resolve refs, then addBlockedBy mutations - --blocking: resolve refs, then addBlockedBy with swapped args Interactive mode: type picker when repo has issue types configured. GHES: relationships gated behind IssueRelationshipsSupported feature detection (3.19+). Types and sub-issues need no detection (GA 3.17+). Co-authored-by: Copilot <[email protected]>
New flags for issue edit: - --type: set issue type by name - --set-parent / --remove-parent: set or remove parent issue (mutually exclusive via cmdutil.MutuallyExclusive) - --add-sub-issue / --remove-sub-issue: manage sub-issues - --add-blocked-by / --remove-blocked-by: manage blocked-by relationships - --add-blocking: add blocking relationships (swaps API args) Interactive mode: Type and Parent added to the field picker survey. FetchOptions loads issue types when Type is selected. Editable struct: added IssueType and Parent fields with Dirty(), Clone(), FieldsToEditSurvey, and EditFieldsSurvey support. GHES: relationships gated behind IssueRelationshipsSupported. Co-authored-by: Copilot <[email protected]>
Filter issues by type using the search API path. The --type flag appends a type: qualifier to the search query, forcing the search path (same as --label and --milestone). Updated FilterOptions with IssueType field, IsDefault(), and SearchQueryBuild() to include the type qualifier. Co-authored-by: Copilot <[email protected]>
Create tests (11 new): - Flag parsing: --type, --parent (number/URL), --blocked-by, --blocking - Behavior: type resolution + mutation, type not found error, parent resolution + addSubIssue, blocked-by/blocking with swapped args verification, GHES unsupported error Edit tests (18 new): - Flag parsing: --type, --set-parent, --remove-parent, mutual exclusivity, --add-sub-issue, --remove-sub-issue, --add-blocked-by, --remove-blocked-by, --add-blocking - Behavior: type edit, set/remove parent, add/remove sub-issues, add/remove blocked-by, add-blocking with swapped args, batch edit type across multiple issues - Bug fix: copy SetParent value into Editable.Parent.Value View tests (5 new): - TTY: full view with all Issues 2.0 fields, regression test with no new fields - JSON: issueType export, parent/subIssues/subIssuesSummary export, blockedBy/blocking export Co-authored-by: Copilot <[email protected]>
…okup Address code review findings: - Extract resolveIssueRef into shared.ResolveIssueRef (was duplicated between create.go and edit.go) - Extract issue type name→ID resolution into shared.ResolveIssueTypeName (was duplicated between create applyIssueTypes and edit applyEditIssueType) - Fix double import of issue/shared in view.go Co-authored-by: Copilot <[email protected]>
Critical fixes: - GHES data-flow regression: blockedBy/blocking fields now conditionally added to view lookupFields only when IssueRelationshipsSupported is true (GHES 3.19+). Previously would break gh issue view on GHES 3.17-3.18. - State line separator: restore original bullet (•) to avoid breaking downstream parsers. Issue type prefix uses middle dot (·). Optimizations: - Batch edit --type: resolve issueTypeID once before the loop instead of per-goroutine (eliminates N-1 redundant API calls) - Parent removal: include id in parent GraphQL fragment, use issue.Parent.ID directly instead of extra IssueNodeID lookup Nit fixes: - Fix formatLinkedIssueRef godoc to match actual behavior Co-authored-by: Copilot <[email protected]>
Add flag parsing and behavior tests for gh issue list --type: - TestNewCmdList/type_flag: verifies opts.IssueType is set - Test_issueList/with_issue_type: verifies search path is forced and query includes type:Bug qualifier Co-authored-by: Copilot <[email protected]>
Completes the symmetry of relationship flags: - --add-blocked-by / --remove-blocked-by - --add-blocking / --remove-blocking The --remove-blocking flag swaps API args (same as --add-blocking): calls RemoveBlockedBy(issueId=OTHER, blockingIssueId=THIS). Co-authored-by: Copilot <[email protected]>
- Fix PR edit regression: gate Type and Parent behind Allowed bool in FieldsToEditSurvey, matching the Reviewers.Allowed pattern. Only issue edit sets Allowed=true; PR edit won't show these fields. - Add missing RemoveBlocking assertion in flag parsing tests - Quote issue type names containing spaces in search queries (type:"Bug Report" instead of type:Bug Report) - Remove duplicate TODO comment in view.go - Avoid double RepoIssueTypes API call in interactive create: cache the resolved ID from the picker, skip re-resolution - Rename applyIssueTypes → applyIssueType (singular) Co-authored-by: Copilot <[email protected]>
Verifies Type and Parent only appear in the interactive picker when Allowed is explicitly set. Prevents regression where issue-only fields leak into gh pr edit's interactive mode. Co-authored-by: Copilot <[email protected]>
- Restore // TODO projectsV1Deprecation comment above TestProjectsV1Deprecation (was displaced when new tests were inserted at that location) - Add 'relationships unsupported on GHES' test case to edit command using DisabledDetectorMock (parity with create's GHES tests) Co-authored-by: Copilot <[email protected]>
…ions The AddBlockedByPayload and RemoveBlockedByPayload types expose the result as 'issue', not 'blockedIssue'. Found during live spec testing against github.com — the mutations returned empty responses. Updated mutation queries and corresponding test fixtures. Co-authored-by: Copilot <[email protected]>
| cmd.Flags().StringVar(&opts.SetParent, "set-parent", "", "Set the parent issue by `number` or URL") | ||
| cmd.Flags().BoolVar(&opts.RemoveParent, "remove-parent", false, "Remove the parent issue") |
There was a problem hiding this comment.
For consistency with the other --add-* and --remove-* options, it might be good to have a --add-parent <number/URL> option as well, so we'd have:
--remove-parent- only removes parent--add-parent <number/URL>- only adds parent, will not overwrite existing parent--set-parent <number/URL>- will overwrite parent: either add a parent if there is not one, or remove the existing parent and set a new one
I'd imagine these should be mutually exclusive: only pass one of --remove-parent, --add-parent, or --set-parent.
This could be a follow-up though, I think the two existing options are good.
There was a problem hiding this comment.
--add-parent <number/URL>- only adds parent, will not overwrite existing parent
Is it possible to have more than one parent? I think that was my motivation for setting it up this way - my understanding is you can only have one parent per issue. I think that's the line between --add-* and --set-*.
There was a problem hiding this comment.
--add-parent <number/URL>- only adds parent, will not overwrite existing parentIs it possible to have more than one parent? I think that was my motivation for setting it up this way - my understanding is you can only have one parent per issue. I think that's the line between
--add-*and--set-*.
No, that's correct: it is one parent per issue. I was thinking about it from the perspective of matching what the REST API supports (which has the replace_parent flag to disable destructive actions), and what agents might guess is the right flag. This does seem to be consistent though with the existing flags, so sorry for the noise! We can keep the two existing flags and add this later if necessary.
There was a problem hiding this comment.
No worries, this is good perspective! I was thinking as part of this release we'd off a skill file in this repo for issue management that instructs agents how to use these new flags, so maybe that'll also address some of the agentic uses. It would be installable with gh skill install cli/cli
There was a problem hiding this comment.
As we talked about this in sync, we decided that it's best to do it like --parent [ISSUE] and --remove-parent. This is more intuitive and also consistent with other flags like milestones in issue edit.
| stdout: "https://github.com/OWNER/REPO/issue/123\n", | ||
| }, | ||
| { | ||
| name: "edit type", |
There was a problem hiding this comment.
We also need TTY test cases for type and parent prompts.
There was a problem hiding this comment.
Added new tests covering the interactive type and parent prompts, but while writing the type prompt test I found a bug.
In interactive mode, editRun was resolving the issue type ID before EditFieldsSurvey ran, so when a user picked a type from the prompt, the mutation went out with an empty issueTypeId.
prShared.FetchOptions already calls RepoIssueTypes to populate the survey options for both interactive and non-interactive gh issue edit, but it was throwing away the IDs. So editRun was making a second RepoIssueTypes request via ResolveIssueTypeName just to map a name to an ID.
The fix teaches FetchOptions to store a name to ID map on Editable. Both ResolveIssueTypeName calls in editRun are replaced with a map lookup, and that lookup now lives inside the per-issue loop, which fixes the interactive prompt bug as a side effect.
Let me know what you think (leaving thread unresolved for feedback)
There was a problem hiding this comment.
Good call. I think we can actually pull the map lookup out of the loop since it doesn't need the issue under iteration. This bit:
// Look up the issue type ID using the map populated by FetchOptions
var issueTypeID string
if editable.IssueType.Edited && editable.IssueType.Value != "" {
id, ok := editable.IssueTypeNameToID[editable.IssueType.Value]
if !ok {
return fmt.Errorf("type %q not found; available types: %s",
editable.IssueType.Value,
strings.Join(editable.IssueType.Options, ", "))
}
issueTypeID = id
}
babakks
left a comment
There was a problem hiding this comment.
Here are my comments re list and view.
Addresses review feedback on PR #13057. Quick fixes: - Replace em-dashes with regular dashes in code comments per AGENTS.md. - Add JSON tags to SubIssues and LinkedIssueConnection (and TotalCount on the latter) for consistent struct serialization. - Add comment to defensive ID-empty check in IssueNodeID explaining why the guard is kept after a GraphQL call. - Reject --add-sub-issue and --remove-sub-issue when multiple issues are passed to issue edit (one parent per issue makes batching ambiguous). - Add godoc to Editable.{X}.Allowed. Substantive fixes: - Add canonical `id` field to relationship exports (parent, sub-issues nodes, blockedBy nodes, blocking nodes), and update the GraphQL fragments in query_builder.go to fetch them. - Wrap subIssues, blockedBy, and blocking JSON output in {nodes: [...], totalCount: N} so consumers can see the true total when more than 50 items exist. The query_builder fragments now request totalCount for blockedBy/blocking. - In issue edit and issue create, validate IssueRelationshipsSupported up-front when relationship flags are used, before any mutation, so GHES users get a clear error instead of partial state. - Refactor IssueType handling out of ImmutableKeywords and into the Qualifiers struct in pkg/search/query.go. The new IssueType field renders as type:X (with proper quoting via quote()), eliminating the ad-hoc escaping in pr/shared/params.go. - In prShared.FetchOptions, surface errors from api.RepoIssueTypes when the user has explicitly opted into editing the issue type instead of silently ignoring them. - In EditFieldsSurvey, return a clear error when the user opts to edit Type but no issue types are available, instead of silently skipping the prompt. Tests: - New tests for IssueType qualifier in standard and advanced search query strings (including quoting behavior). - Updated TestIssueView_json_ParentSubIssues and TestIssueView_json_BlockedByBlocking to verify the new {nodes, totalCount} shape and `id` fields. - New TestEditFieldsSurvey_IssueTypeNoOptions covering the new error case in the survey. - New cases in TestNewCmdEdit covering the multi-arg sub-issue guard. Pushback / deferred (intentionally not in this round): - T6 (mutex on add/remove relationship flags): rejected. Add and remove collection deltas are not conflicting, matching --add-label/--remove-label. - T19/T24 (large architectural reordering of validate-then-mutate and capture-errors-then-continue): deferred to a follow-up. - T23 (--remove-type/--set-type for unsetting issue type): follow-up. - T3 (cross-host validation in ResolveIssueRef): follow-up. - T5 (non-TTY field selection): follow-up. - TTY+IssueType prompt test for issue create: follow-up (large mock setup). Co-authored-by: Copilot <[email protected]>
gh issue list --json ...,blockedBy,... (issue-claim.sh) needs blockedBy, which shipped in gh 2.94.0 (cli/cli#13057). On an older gh the flag is rejected outright -- gh writes its complaint to stderr and nothing to stdout, so the python3 -c piped after it tries to json.load an empty stream and dies with JSONDecodeError, a traceback naming the wrong problem entirely. mise.toml already pins gh well above the floor, but mise is optional and a session where it is not active gets whatever gh is already on $PATH -- the same class of gap RUSTUP_TOOLCHAIN leaves for the Rust pin. scripts/lib/require-gh.sh is the runtime backstop every scripts/issue-*.sh now sources: a portable (no `sort -V`, which BSD sort lacks -- the exact class of GNU-only assumption #559 already hit once) MAJOR.MINOR.PATCH comparison that refuses in one sentence naming both versions. Three scripts source it immediately after `set -euo pipefail`. issue-land.sh sources it later, right before its first real `gh pr view` call: everything above that point -- the commit guard, the gates, the push -- never touches gh at all (test-issue-land-commit-guard.py's own premise), and demanding a gh version before a dirty-tree refusal or a red gate would have given that path a dependency it never had. Every scripts/tests/*.py that stubs gh for one of the four scripts needed a `--version` case added to its stub (and scripts/lib copied alongside the script it copies into a fixture) to keep working under the new guard -- twelve files, all mechanical. README and mise.toml now name the 2.94.0 floor and why. Refs: #558
Three clients for the issue graph, chosen by a probe: gh 2.94.0, the script, the MCP. *Three roads lead to the same graph;* *the probe, not the wish, decides.* *Where gh is new it walks alone,* *where none is here, say so and wait.* Summary ------- `gh` reaches the whole issue-relationship graph from 2.94.0 on, both directions of blocked-by included. The `issue-deps` skill did not know this: it named the script as the only way to a blocking edge and the MCP as the way to everything else, and the session that reported #129 had an MCP that reached none of it. This change rewrites the skill's client matrix as three columns and adds the probe that picks one, so a session uses the best client its environment has and says plainly what the worst one cannot do. Details ------- **The floor is 2.94.0, not 2.100.0.** The issue measured on 2.100.0. Bisecting `pkg/cmd/issue/edit/edit.go` across the cli/cli tags puts the `--add-blocked-by`, `--add-blocking`, `--add-sub-issue` and `--parent` flags, and the `blockedBy`, `blocking`, `subIssues`, `parent` and `issueType` JSON fields, all in 2.94.0 (cli/cli#13057); `closedByPullRequestsReferences` predates it. The skill states that floor and the two unhelpful errors an older `gh` gives instead of naming its version. **Three branches, in order.** `gh` at the floor or later uses the flags. A token without a usable `gh`, which is every Claude Code web worker, uses `scripts/issue-deps.sh`, now described as load-bearing rather than a gap-filler. The MCP alone gets a plain statement: both generations of the server return counts with no members for blocked-by, and the older generation, the one with `get_issue` rather than `issue_read`, reaches neither sub-issues nor `closed_by_pull_requests` and writes nothing into the graph. The tool list is the probe for which generation is present. **Kept from the old text.** The propose-from-evidence rule, unchanged. The pull-request read that answers empty rather than refusing, now noted for `gh` as well as REST, since `gh` does not close that trap. The from-the-blocked-side rule, now stated as REST's constraint and the script's, with `gh` inverting `--add-blocking` client-side. **Added.** The pipe that eats `gh`'s exit status: `gh issue edit … | tail` reports `tail`'s `$?`. The `gh` refusals, all honest and non-zero. **Expiry widened.** The script's header and the skill's `Expiry` section now retire it when the environment it serves stops existing, not when the MCP grows the endpoints, because the capability arrived through `gh` instead and the MCP still has not caught up. The script's logic is untouched; only its header comments change. Issues ------ - Closes #129 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01GD6mX5vZRcuFi9DiWv9Gtm --- _Generated by [Claude Code](https://claude.ai/code/session_01GD6mX5vZRcuFi9DiWv9Gtm)_ --------- Co-authored-by: Claude <[email protected]>
直近の Issue 71 件はすべて種別なしだった。Form も /issue も種別を指定して いなかったためで、Organization に種別を足しただけでは使われない。 Experiment 種別を 2026-09-30 に足したので、Issue を作る 2 つの経路に 種別を入れる(development-docs の D10)。 種別は Form の type: 1 箇所に書く。Web の Form はそれで付く。CLI の issue-form.sh は Form の type: を読んで skeleton と check に表示し、 /issue は gh issue create --type に渡す。種別を書き写さないので、Form を 直せば両方が変わる。--type は gh 2.94.0 で入った(cli/cli#13057)。古い gh で unknown flag と出たら、--type を外さずに更新を依頼させる。 Form に存在しない種別を書いたときの挙動は公式文書に無い。種別の名前を Organization 側で変えたり無効にしたりしても Form は黙って残るので、監査 (org_audit.py)が Form の type: を Organization の有効な種別と照合する。 取れなかったときは「無い」と言わない。 実測: gh issue create --type Feature で作った #37 に種別が付いた(この Organization で最初)。テスト: 監査 42 件、Form と issue-form.sh 4 件 (gh を差し替え)すべて OK。壊す 9 通りすべてで落ちた。実 Organization への読み取り実行で所見 0 件。 Closes #37 Co-authored-by: Claude Opus 5.5 <[email protected]>
The relationship flags and fields shipped in gh v2.94.0 (cli/cli#13057, "Issue types, sub-issues, and relationships in `gh issue`"), not 2.100.0. GH_MIN_VERSION, the messages, the docstrings and the tests now say 2.94.0, and the constant cites that release. Co-Authored-By: lsimons-bot <[email protected]> Assisted-by: Claude:claude-opus-5-5
The relationship flags and fields shipped in gh v2.94.0 (cli/cli#13057, "Issue types, sub-issues, and relationships in `gh issue`"), not 2.100.0. GH_MIN_VERSION, the messages, the docstrings and the tests now say 2.94.0, and the constant cites that release. Co-Authored-By: lsimons-bot <[email protected]> Assisted-by: Claude:claude-opus-5-5
Add Issues 2.0 support: issue types, sub-issues, and relationships
Closes #10298
Closes #9696
Closes #11757
Closes #12477
Closes #12152
Adds support for GitHub's Issues 2.0 features across
gh issue create,edit,view, andlist.New flags
gh issue create--type Bug--parent 100--blocked-by 200,201--blocking 300Interactive mode prompts for issue type when the repo has types configured.
gh issue edit--type Bug--set-parent 100--remove-parent--add-sub-issue 123,124--remove-sub-issue 123--add-blocked-by 200--remove-blocked-by 201--add-blocking 300,301--remove-blocking 300Interactive mode adds Type and Parent to the field picker. Batch editing
supported for
--typeacross multiple issues.gh issue viewTTY output now displays:
Bug · Open)Sub-issues · 3/5 (60%))JSON output supports new fields:
issueType,parent,subIssues,subIssuesSummary,blockedBy,blocking. ThesubIssues,blockedBy,and
blockingfields are objects of the shape{ "nodes": [...], "totalCount": N }(rather than flat arrays) so consumers can detect when the truncation
limit is reached. Each node and the
parentobject include the canonicalid.gh issue list--type BugGHES compatibility
__typeschema introspection for theblockedByfield on the Issue type. Unsupported GHES versions receive a clear error message. Gated with// TODO IssueRelationshipsCleanupfor cleanup when GHES 3.18 support ends.Implementation details
--blockingand--add-blockingswap API arguments: callsaddBlockedBy(issueId=OTHER, blockingIssueId=THIS)--parent,--blocked-by,--blocking, and sub-issue flagspkg/cmd/issue/shared/resolve.go(ResolveIssueRef,ResolveIssueTypeName)Allowedin the Editable pattern to prevent leaking intogh pr edittype:"Bug Report")Testing
Changes
17 files changed, 2105 insertions(+), 18 deletions(-)
api/queries_issue.goapi/query_builder.goissueOnlyFieldsapi/export_pr.gointernal/featuredetection/IssueRelationshipsSupporteddetectionpkg/cmd/issue/create/create.gopkg/cmd/issue/edit/edit.gopkg/cmd/issue/view/view.gopkg/cmd/issue/list/list.go--typefilter via search pathpkg/cmd/issue/shared/resolve.goResolveIssueRef,ResolveIssueTypeNamepkg/cmd/pr/shared/editable.goIssueType/Parentfields,Allowedgatingpkg/cmd/pr/shared/params.goIssueTypeinFilterOptionsand search queryKey notes for reviewers
--blockingarg swap: The GitHub API only hasaddBlockedBy(issueId, blockingIssueId). To support--blocking, we swap the arguments — the OTHER issue becomesissueIdand THIS issue becomesblockingIssueId. Same for--remove-blocking.Allowedgating:IssueTypeandParentare added to the sharedEditablestruct but gated behindAllowed = true, which only the issue edit command sets. This prevents these issue-only fields from leaking intogh pr edit's interactive picker. Regression test insurvey_test.go.blockedByandblockingare only added to the GraphQL query whenIssueRelationshipsSupportedis true. Without this,gh issue viewwould break on GHES 3.17–3.18 where these fields don't exist.--type, the type name is resolved to an ID once before the loop rather than per-issue, avoiding N-1 redundantRepoIssueTypesAPI calls.Manual test results
All scenarios tested against on github.com.
24/24 scenarios passing (click to expand)
Issue types
create-with-typegh issue create --title "..." --type Bugcreate-error-invalid-typegh issue create --title "Oops" --type Bugztype "Bugz" not found; available types: Task, Bug, Featureedit-change-typegh issue edit 1 --type Featurecreate-interactive-typegh issue create(interactive)Sub-issues
create-with-parentgh issue create --title "..." --parent 3edit-set-parentgh issue edit 5 --set-parent 3--json parentedit-remove-parentgh issue edit 5 --remove-parentedit-add-sub-issuesgh issue edit 3 --add-sub-issue 6,7--json subIssuesedit-remove-sub-issuegh issue edit 3 --remove-sub-issue 6edit-error-mutual-parentgh issue edit 5 --set-parent 1 --remove-parentspecify only one of --set-parent or --remove-parentRelationships
create-with-relationshipsgh issue create --blocked-by 8 --blocking 9edit-add-remove-blocked-bygh issue edit 14 --add-blocked-by 12 --remove-blocked-by 13edit-add-blockinggh issue edit 14 --add-blocking 15,16View
view-with-typeBug · Openon state line,Type: Bugrowview-parent-sub-issuesview-child-parentview-relationshipsview-json-issueType--json issueTypereturns id, name, description, colorview-json-parent-subIssues--json parent,subIssues,subIssuesSummaryreturns structured dataview-json-relationships--json blockedBy,blockingreturns{ nodes: [...], totalCount: N }with id, number, title, url, stateList
list-filter-by-typegh issue list --type BugCombined
create-all-flagsgh issue create --type Task --parent 3 --blocked-by 8 --blocking 9edit-batch-typegh issue edit 18 19 20 --type Bugedit-interactivegh issue edit 21(interactive)TODO: GHES-specific scenarios (
create-error-ghes-relationships), but GHES feature detection does have unit test coverage with mock introspection responses.