The --type filter was concatenated into ImmutableKeywords in
SearchQueryBuild, which bypassed search.Query's quoting and let values
that contained quotes (or extra qualifiers) corrupt the final query.
Per babakks's suggestion in the review thread, model it as a regular
qualifier instead.
Add an IssueType field to Qualifiers tagged `qualifier:"type"` so it
shares a key with the existing Type field, and rework Qualifiers.Map
to honour the tag and concatenate values when multiple fields share
the same key. SearchQueryBuild now drops its bespoke type:X
formatting and just sets Qualifiers.IssueType, leaving the
keyword/qualifier escaping to pkg/search.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pair --type with --remove-type so callers can clear an issue's type
without going through the interactive editor, mirroring the
--milestone / --remove-milestone and --parent / --remove-parent
patterns. The two type flags are mutually exclusive.
UpdateIssueIssueType now sends a null issueTypeId when the caller
passes an empty string, which is what the API requires to clear the
field. The orchestrator fires the mutation when either IssueTypeID is
non-empty or RemoveIssueType is set.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The post-creation Issues 2.0 mutations (issue type, parent, blocked-by,
blocking) ran sequentially in three separate apply* helpers. Replace
them with a single call to api.DeferredUpdateIssue, which fans the
mutations out in parallel and joins their errors. The new
newCreateDeferredOpts helper resolves the user-supplied refs to node
IDs (re-using the cached opts.issueTypeID from the interactive prompt)
and hands them to the orchestrator.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The --milestone / --remove-milestone pair has long been the
established pattern for value-takes-or-removes flag pairs in
gh issue edit. Bring --set-parent into line as --parent / --remove-parent.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The interactive Parent prompt was a free-text input with no
candidate-listing UX, an oversight from the initial Issues 2.0
landing. Sub-issues, blocked-by, and blocking already live as bare
flag fields outside of Editable for the same reason; bring Parent
into line.
editRun now reads opts.SetParent / opts.RemoveParent directly when
constructing DeferredUpdateIssueOptions, and the survey machinery
no longer sees Parent at all.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Issues 2.0 mutations (issue type, parent set/remove, sub-issues,
blocked-by, blocking) are deferred until after the main UpdateIssue
because they target IDs that the standard mutation does not handle.
Move them behind a shared api.DeferredUpdateIssue orchestrator that
fans them out in parallel and joins all errors so a single failure
does not abort the rest.
editRun no longer carries its own applyEditParent / applyEditSubIssues
/ applyEditRelationships helpers; the per-issue goroutine resolves
refs to node IDs via a small deferredUpdateIssueOptions builder, then
hands the populated DeferredUpdateIssueOptions to api.DeferredUpdateIssue.
Also moves ResolveIssueRef and ResolveIssueTypeName from the deleted
resolve.go into lookup.go.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Let the API return its own "unsupported" error rather than gating the
relationship mutations behind a client-side IssueRelationships check.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Non-interactive mode resolves the type once before the loop instead of
every iteration. Interactive mode still resolves inside the loop after
the survey populates IssueType.Value. Both paths share a small
lookupIssueTypeID helper.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A sub-issue can have only one parent, so applying --add-sub-issue
across multiple parent args is ambiguous.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add issue-type, parent, sub-issues, sub-issues-completed, blocked-by,
and blocking lines to the raw issue preview. Empty values still print
to keep line counts stable for head|grep workflows.
Pull the issue-ref formatting into a small set of helpers so the human
and machine renderers share a single owner/repo#N source of truth.
formatLinkedIssueRef no longer takes baseRepo: callers in this package
always have repository.nameWithOwner on the LinkedIssue, so the
disambiguation between same-repo and cross-repo references is no
longer needed and the resulting refs are unambiguous.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Relationship mutations (parent, sub-issue, blocked-by, blocking) run
against baseRepo's host with node IDs that must come from that same
host. Catch a different-host ref up front with a clear error instead
of letting the mutation fail with a confusing node-ID error.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
prShared.FetchOptions already fetches RepoIssueTypes when IssueType is
edited, but only kept the names. editRun then called RepoIssueTypes a
second time via ResolveIssueTypeName.
Have FetchOptions store the name->ID map on Editable, and look the ID
up directly in editRun. The lookup now also lives inside the per-issue
loop, which fixes a bug where the interactive type prompt's chosen
value was set after the upfront resolve, sending an empty issueTypeId
on the mutation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two values per relationship verifies each ref is resolved and applied
independently rather than masking a single-iteration bug.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Same code path, same scope. Test_createRun's httpStubs signature picks
up *testing.T to match the V2 cases that need it for input assertions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The five Issues 2.0 cases (type, parent by number, parent by URL,
blocked-by, blocking) live as a separate parallel test today. They
exercise the same NewCmdCreate code path as the existing cases, so
fold them into TestNewCmdCreate and extend the assertions to cover
IssueType, Parent, BlockedBy, and Blocking.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The subIssues, blockedBy, and blocking JSON output is currently shaped
as a flat array, which silently truncates when there are more entries
than the GraphQL fragment fetches. That's misleading once a user
crosses the page size, so this switches each connection to a
{nodes, totalCount} object so consumers can see when there's more.
While confirming page sizes, the GitHub limits turn out to be:
- sub-issues: up to 100 per parent
https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/adding-sub-issues
- blocked-by / blocking: up to 50 per relationship type
https://github.blog/changelog/2025-08-21-dependencies-on-issues/
So subIssues moves to first:100 to fetch the full set; blockedBy and
blocking stay at first:50, which already covers their cap.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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>
- 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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
The four tests in this file (TestVerifyIntegration,
TestVerifyIntegrationCustomIssuer, TestVerifyIntegrationReusableWorkflow,
TestVerifyIntegrationReusableWorkflowSignerWorkflow) call
NewLiveSigstoreVerifier which requires network access to Sigstore and
GitHub TUF servers. Unlike the other integration test files in this
package (attestation_integration_test.go, sigstore_integration_test.go,
inspect_integration_test.go), this file was missing the //go:build
integration tag, causing these tests to run during a regular
'go test ./...' and fail in network-isolated build environments.
The review hint printed after `gh skill install --allow-hidden-dirs`
suggests `gh skill preview` commands. Those commands would fail for
hidden-dir skills because preview would filter them out. Pass the
flag through so the suggested commands work as-is.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Most agent clients (Claude Code, Copilot, etc.) only discover immediate
subdirectories of their skills folder. When a skill repository used
namespaced paths like skills/author/my-skill/, the installer created
nested directories (e.g. .claude/skills/author/my-skill/) that clients
could not find.
This separates the skill's identity (InstallName, used for lockfile keys,
search, filtering, display) from the filesystem path (Name, used for the
install directory). Skills are now always installed flat:
.claude/skills/my-skill/SKILL.md (not .claude/skills/author/my-skill/)
Changes:
- installer: use skill.Name for directory paths instead of InstallName
- install.go: use skill.Name for overwrite checks and prompts
- collisions: detect conflicts by Name since flat install means two
skills with the same Name but different Namespace values will collide
- update: clean up old namespaced directories when migrating to flat
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add support for the --allow-hidden-dirs flag in `gh skill preview`,
matching the existing pattern in `gh skill install`. This allows users
to preview skills located in hidden directories (e.g. .claude/skills/,
.agents/skills/).
Changes:
- Add AllowHiddenDirs field to PreviewOptions
- Register --allow-hidden-dirs flag on the preview command
- Switch from DiscoverSkills to DiscoverSkillsWithOptions to get all
skills including hidden-dir ones
- Add filterHiddenDirSkills to exclude hidden-dir skills by default,
showing a hint when they are found but excluded
- Print a warning when --allow-hidden-dirs is used and hidden skills
are present
- Return an error when only hidden-dir skills exist without the flag
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Widen ValidateSupportedHost to accept tenancy hosts (*.ghe.com) alongside
github.com. GHEC with data residency uses these domains, and all skill
subcommands (search, install, preview, publish, update) now allow them.
GitHub Enterprise Server remains unsupported and is explicitly rejected
with a clear error message.
Also fix the lockfile writer to use the actual host when constructing
SourceURL instead of hardcoding github.com.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When installing a skill whose SKILL.md contains github-repo metadata
pointing to a different repository, the CLI detects it as a re-published
skill and offers to redirect the install to the upstream source.
In interactive mode, the user is prompted to choose between the
re-publisher (default) and the upstream. In non-interactive mode,
the install proceeds from the re-publisher with a notice.
The --upstream flag skips the prompt and redirects to upstream directly,
enabling non-interactive upstream installs in CI/scripts.
If the user chooses upstream, the install restarts from that repo,
resolving the latest version and discovering skills fresh. A
skill_upstream_redirect telemetry event is emitted to track redirects.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use a two-pass search so exact DisplayName and Name matches are
preferred over InstallName. This avoids incorrectly selecting a
plugins-convention skill via InstallName when a standard namespaced
skill with a matching DisplayName exists later in the sorted slice.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The preview command's selectSkill function only matched skills by
DisplayName() and Name. For plugins-convention skills, the install
hint outputs InstallName() (namespace/name), which matched neither -
DisplayName() includes a [plugins] prefix and Name is just the base
name. This caused 'skill not found' errors when users ran the
suggested preview command after install.
Add InstallName() as an additional match criterion so that namespaced
skill identifiers produced by the install hint are accepted.
Closes#13248
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>