Skip to content

Add Issues 2.0 support: issue types, sub-issues, and relationships - #13057

Merged
BagToad merged 45 commits into
trunkfrom
kw/issues-2.0
Jun 4, 2026
Merged

Add Issues 2.0 support: issue types, sub-issues, and relationships#13057
BagToad merged 45 commits into
trunkfrom
kw/issues-2.0

Conversation

@BagToad

@BagToad BagToad commented Mar 30, 2026

Copy link
Copy Markdown
Member

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, and list.

New flags

gh issue create

Flag Description
--type Bug Set the issue type
--parent 100 Add as a sub-issue of the specified parent (number or URL)
--blocked-by 200,201 Mark as blocked by these issues
--blocking 300 Mark as blocking these issues

Interactive mode prompts for issue type when the repo has types configured.

gh issue edit

Flag Description
--type Bug Set the issue type
--set-parent 100 Set or replace the parent issue
--remove-parent Remove the parent issue
--add-sub-issue 123,124 Add sub-issues
--remove-sub-issue 123 Remove a sub-issue
--add-blocked-by 200 Add blocked-by relationship
--remove-blocked-by 201 Remove blocked-by relationship
--add-blocking 300,301 Add blocking relationships
--remove-blocking 300 Remove blocking relationship

Interactive mode adds Type and Parent to the field picker. Batch editing
supported for --type across multiple issues.

gh issue view

TTY output now displays:

  • Issue type on the state line (e.g., Bug · Open)
  • Type, Parent, Blocked by, Blocking metadata rows
  • Sub-issues section with completion progress (e.g., Sub-issues · 3/5 (60%))

JSON output supports new fields: issueType, parent, subIssues,
subIssuesSummary, blockedBy, blocking. The subIssues, blockedBy,
and blocking fields 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 parent object include the canonical
id.

gh issue list

Flag Description
--type Bug Filter issues by type

GHES compatibility

  • Issue types and sub-issues: GA on all supported GHES versions (3.17+). No feature detection needed.
  • Relationships (blocked-by/blocking): Requires GHES 3.19+. Feature detection via __type schema introspection for the blockedBy field on the Issue type. Unsupported GHES versions receive a clear error message. Gated with // TODO IssueRelationshipsCleanup for cleanup when GHES 3.18 support ends.

Implementation details

  • Post-creation mutations for type, parent, and relationships (same pattern as projectV2 and assignee mutations)
  • --blocking and --add-blocking swap API arguments: calls addBlockedBy(issueId=OTHER, blockingIssueId=THIS)
  • Cross-repo references supported via URL for --parent, --blocked-by, --blocking, and sub-issue flags
  • Shared helpers extracted to pkg/cmd/issue/shared/resolve.go (ResolveIssueRef, ResolveIssueTypeName)
  • Issue-only fields (Type, Parent) gated behind Allowed in the Editable pattern to prevent leaking into gh pr edit
  • Issue type names with spaces are quoted in search queries (type:"Bug Report")

Testing

  • 34 new flag parsing tests across create, edit, and list
  • 18 new HTTP mock behavior tests covering mutations, error cases, and GHES unsupported paths
  • 5 new view tests (TTY rendering, JSON export)
  • 2 regression tests (issue-only fields don't leak into PR edit, no-new-fields view unchanged)

Changes

17 files changed, 2105 insertions(+), 18 deletions(-)

File Change
api/queries_issue.go New types, Issue struct fields, mutation functions, helpers
api/query_builder.go GraphQL fragment cases, issueOnlyFields
api/export_pr.go ExportData cases for new fields
internal/featuredetection/ IssueRelationshipsSupported detection
pkg/cmd/issue/create/create.go New flags, post-creation mutations, interactive type picker
pkg/cmd/issue/edit/edit.go New flags, mutations, interactive Type/Parent picker
pkg/cmd/issue/view/view.go TTY rendering, conditional relationship fields
pkg/cmd/issue/list/list.go --type filter via search path
pkg/cmd/issue/shared/resolve.go Shared ResolveIssueRef, ResolveIssueTypeName
pkg/cmd/pr/shared/editable.go IssueType/Parent fields, Allowed gating
pkg/cmd/pr/shared/params.go IssueType in FilterOptions and search query

Key notes for reviewers

  • --blocking arg swap: The GitHub API only has addBlockedBy(issueId, blockingIssueId). To support --blocking, we swap the arguments — the OTHER issue becomes issueId and THIS issue becomes blockingIssueId. Same for --remove-blocking.
  • Editable Allowed gating: IssueType and Parent are added to the shared Editable struct but gated behind Allowed = true, which only the issue edit command sets. This prevents these issue-only fields from leaking into gh pr edit's interactive picker. Regression test in survey_test.go.
  • View relationship fields are GHES-gated: blockedBy and blocking are only added to the GraphQL query when IssueRelationshipsSupported is true. Without this, gh issue view would break on GHES 3.17–3.18 where these fields don't exist.
  • Batch edit type resolution: When editing multiple issues with --type, the type name is resolved to an ID once before the loop rather than per-issue, avoiding N-1 redundant RepoIssueTypes API calls.

Manual test results

All scenarios tested against on github.com.

24/24 scenarios passing (click to expand)

Issue types

# Scenario Command Result
1 create-with-type gh issue create --title "..." --type Bug #1 created, type=Bug verified
2 create-error-invalid-type gh issue create --title "Oops" --type Bugz ✅ Error: type "Bugz" not found; available types: Task, Bug, Feature
3 edit-change-type gh issue edit 1 --type Feature ✅ Type changed to Feature verified
4 create-interactive-type gh issue create (interactive) #21 Type picker shown, Bug selected and set

Sub-issues

# Scenario Command Result
5 create-with-parent gh issue create --title "..." --parent 3 #4 created as sub-issue of #3
6 edit-set-parent gh issue edit 5 --set-parent 3 ✅ Parent set, verified via --json parent
7 edit-remove-parent gh issue edit 5 --remove-parent ✅ Parent removed, verified null
8 edit-add-sub-issues gh issue edit 3 --add-sub-issue 6,7 ✅ Sub-issues added, verified via --json subIssues
9 edit-remove-sub-issue gh issue edit 3 --remove-sub-issue 6 ✅ Sub-issue removed
10 edit-error-mutual-parent gh issue edit 5 --set-parent 1 --remove-parent ✅ Error: specify only one of --set-parent or --remove-parent

Relationships

# Scenario Command Result
11 create-with-relationships gh issue create --blocked-by 8 --blocking 9 #11 blockedBy=#8, blocking=#9 verified
12 edit-add-remove-blocked-by gh issue edit 14 --add-blocked-by 12 --remove-blocked-by 13 ✅ Relationships updated
13 edit-add-blocking gh issue edit 14 --add-blocking 15,16 ✅ Blocking relationships added (swapped args verified)

View

# Scenario Verification Result
14 view-with-type TTY shows Bug · Open on state line, Type: Bug row
15 view-parent-sub-issues TTY shows Parent row, Sub-issues section with progress
16 view-child-parent TTY shows parent reference for child issue
17 view-relationships TTY shows Blocked by and Blocking rows
18 view-json-issueType --json issueType returns id, name, description, color
19 view-json-parent-subIssues --json parent,subIssues,subIssuesSummary returns structured data
20 view-json-relationships --json blockedBy,blocking returns { nodes: [...], totalCount: N } with id, number, title, url, state

List

# Scenario Command Result
21 list-filter-by-type gh issue list --type Bug ✅ Filtered results returned

Combined

# Scenario Command Result
22 create-all-flags gh issue create --type Task --parent 3 --blocked-by 8 --blocking 9 #17 all fields set
23 edit-batch-type gh issue edit 18 19 20 --type Bug ✅ All 3 issues updated to Bug
24 edit-interactive gh issue edit 21 (interactive) ✅ Picker shows Type and Parent fields

TODO: GHES-specific scenarios (create-error-ghes-relationships), but GHES feature detection does have unit test coverage with mock introspection responses.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +635 to +641
if err == nil {
typeNames := make([]string, len(issueTypes))
for i, t := range issueTypes {
typeNames[i] = t.Name
}
editable.IssueType.Options = typeNames
}

Copilot AI Mar 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]

Copilot AI Mar 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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
}

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread pkg/cmd/pr/shared/params.go Outdated
Comment thread pkg/cmd/issue/shared/resolve.go Outdated
Comment thread pkg/cmd/issue/view/view.go
Comment thread pkg/cmd/issue/view/view.go
Comment thread pkg/cmd/issue/edit/edit.go
BagToad and others added 14 commits April 30, 2026 08:49
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 <223556219+Copilot@users.noreply.github.com>
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 <223556219+Copilot@users.noreply.github.com>
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 <223556219+Copilot@users.noreply.github.com>
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 <223556219+Copilot@users.noreply.github.com>
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 <223556219+Copilot@users.noreply.github.com>
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 <223556219+Copilot@users.noreply.github.com>
…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 <223556219+Copilot@users.noreply.github.com>
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 <223556219+Copilot@users.noreply.github.com>
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 <223556219+Copilot@users.noreply.github.com>
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 <223556219+Copilot@users.noreply.github.com>
- 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 <223556219+Copilot@users.noreply.github.com>
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 <223556219+Copilot@users.noreply.github.com>
- 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 <223556219+Copilot@users.noreply.github.com>
…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 <223556219+Copilot@users.noreply.github.com>
@BagToad
BagToad marked this pull request as ready for review April 30, 2026 14:49
@BagToad
BagToad requested a review from a team as a code owner April 30, 2026 14:49
@BagToad
BagToad requested a review from williammartin April 30, 2026 14:49
Comment thread pkg/cmd/issue/edit/edit.go Outdated
Comment on lines +222 to +223
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")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • --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-*.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • --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-*.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@BagToad
BagToad requested a review from babakks May 5, 2026 12:52

@babakks babakks left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, @BagToad! 🙏

I went through most of the changes (except list and view) commands. And found these items.

Comment thread internal/featuredetection/feature_detection.go Outdated
Comment thread api/export_pr.go
Comment thread api/export_pr.go
Comment thread api/queries_issue.go
Comment thread api/queries_issue.go
Comment thread pkg/cmd/issue/edit/edit.go
Comment thread pkg/cmd/issue/edit/edit.go
Comment thread pkg/cmd/issue/edit/edit.go Outdated
Comment thread pkg/cmd/issue/edit/edit.go
stdout: "https://github.com/OWNER/REPO/issue/123\n",
},
{
name: "edit type",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also need TTY test cases for type and parent prompts.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

0c48bd0

Let me know what you think (leaving thread unresolved for feedback)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 babakks left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are my comments re list and view.

Comment thread pkg/cmd/pr/shared/params.go Outdated
Comment thread pkg/cmd/issue/list/list_test.go
Comment thread pkg/cmd/pr/shared/editable.go Outdated
BagToad added a commit that referenced this pull request May 6, 2026
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 <223556219+Copilot@users.noreply.github.com>
@BagToad
BagToad enabled auto-merge (squash) June 4, 2026 00:22
@BagToad
BagToad disabled auto-merge June 4, 2026 00:22
@BagToad
BagToad enabled auto-merge (squash) June 4, 2026 00:23
@BagToad
BagToad merged commit 20147bf into trunk Jun 4, 2026
11 checks passed
@BagToad
BagToad deleted the kw/issues-2.0 branch June 4, 2026 00:27
lithammer added a commit to lithammer/tk that referenced this pull request Jun 11, 2026
The ADR's heaviest deferral reason — `gh issue` has no relationship
flags, so any push needs raw `gh api` — was invalidated by gh 2.94.0
(cli/cli#13057, Issues 2.0), which makes dependencies, sub-issues, and
issue types native to `gh issue`. The decision (v1 defers relationship
sync) still holds, but the recorded rationale was false.

Rewrite rather than append an amendment: the false reason sat at the top
of the body, so a reader met retracted reasoning before the correction.
The deferral now rests on slice size and the gh-independent pre-Promote
Epic gate. Issue-type → TicketKind moves into tk-34's Pull (now cheap and
first-class); tk-107 is re-scoped to the native flags. A one-line History
note preserves why tk-107 was originally framed around raw `gh api`.
SamMorrowDrums pushed a commit to github/github-mcp-server that referenced this pull request Jun 26, 2026
* Add issue dependency read/write MCP tools

Add two feature-flagged tools for issue blocked-by / blocking
relationships, gated behind the issue_dependencies flag so they stay off
the default tool surface (auto-enabled for insiders only):

- issue_dependency_read: get_blocked_by / get_blocking via the
  Issue.blockedBy / Issue.blocking GraphQL connections, cursor-paginated.
- issue_dependency_write: add / remove x blocked_by / blocking via the
  addBlockedBy / removeBlockedBy mutations. Accepts issue numbers and
  resolves them to node IDs in a single aliased query; "blocking" is the
  inverse of "blocked_by" with the subject/related roles swapped.

Closes the MCP gap behind the gh CLI dependency verbs (cli/cli#13057).

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

* Fail fast on self-dependency in issue_dependency_write

Reject a write where the subject and related issue are identical before
resolving node IDs or issuing a mutation, avoiding two API round-trips.

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

* Revert addition of `any of` for required ui_get scopes

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
syf2211 added a commit to syf2211/github-mcp-server that referenced this pull request Jun 27, 2026
* Add issue dependency read/write MCP tools

Add two feature-flagged tools for issue blocked-by / blocking
relationships, gated behind the issue_dependencies flag so they stay off
the default tool surface (auto-enabled for insiders only):

- issue_dependency_read: get_blocked_by / get_blocking via the
  Issue.blockedBy / Issue.blocking GraphQL connections, cursor-paginated.
- issue_dependency_write: add / remove x blocked_by / blocking via the
  addBlockedBy / removeBlockedBy mutations. Accepts issue numbers and
  resolves them to node IDs in a single aliased query; "blocking" is the
  inverse of "blocked_by" with the subject/related roles swapped.

Closes the MCP gap behind the gh CLI dependency verbs (cli/cli#13057).

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

* Fail fast on self-dependency in issue_dependency_write

Reject a write where the subject and related issue are identical before
resolving node IDs or issuing a mutation, avoiding two API round-trips.

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

* Revert addition of `any of` for required ui_get scopes

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

4 participants