Skip to content

Support issue types and fields for user-owned repositories - #3832

Draft
dewski wants to merge 5 commits into
dewski/drop-issue-fields-graphql-featuresfrom
dewski/owner-agnostic-issue-types-fields
Draft

dewski wants to merge 5 commits into
dewski/drop-issue-fields-graphql-featuresfrom
dewski/owner-agnostic-issue-types-fields

Conversation

@dewski

@dewski dewski commented Nov 12, 2026 •

Copy link
Copy Markdown

Summary

This PR makes list_issue_types, list_issue_fields, and the ui_get issue_types method work for user-owned repositories the same way they work for organizations. It's stacked on #3831.

Important

This PR changes one OAuth behavior. Calls to list_issue_types and list_issue_fields without repo no longer challenge for read:org. The challenge only sees the arguments and the token's scopes, so it can't tell a user owner from an organization, and a user owner doesn't need read:org. An organization caller whose token lacks read:org now gets the GitHub API error at call time instead of an OAuth step-up prompt. read:org is in the default OAuth scopes, so I expect that to be rare. ui_get issue_types also stops challenging for read:org, and challenges for repo when one is named, like the other repository-scoped ui_get methods.

Why

Issue types and issue fields are generally available on user accounts as well as organizations, but the server still assumed the owner was an organization whenever repo was omitted. For a user-owned repository, that meant list_issue_types and list_issue_fields failed against orgs/{owner} or organization(login:). It also meant the issue-write app's type picker came back empty, since it called ui_get issue_types without repo.

There isn't a public issue tracking this.

What changed

Call Before After
With repo Repository endpoint Unchanged, no extra request
Without repo, organization owner orgs/{owner}/issue-types, organization(login:) Unchanged
Without repo, owner is the authenticated user Failed against the organization endpoints GET /user/issue-types, user(login:) issueFields
Without repo, any other user owner Raw FORBIDDEN or not found Tool error asking for repo, since GitHub only lists a user's definitions to that user
Issue-write type picker on a user-owned repository Empty The repository's issue types
  • Added issueMetadataScope in pkg/github/issue_metadata_scope.go, which is the repository, organization, or authenticated user whose issue types and fields a call lists. list_issue_types, list_issue_fields, and ui_get issue_types all resolve it once per call through resolveIssueMetadataScope.
  • The user path returns the same shapes as the organization path, so the tool output doesn't change.
  • ListIssueTypes drops its non-200 body-reading branches, since go-github already returns an error for any non-2xx response.
  • Extracted ownerAccountType from detectOwnerType in pkg/github/projects.go so the projects tools and the new scope share one owner lookup. detectOwnerType behaves the same.
  • Reworded the tool descriptions, the issue_write type and field_name params, the set_issue_fields description, and the issues toolset instructions to describe the owner as a user or organization.

Reviewing this PR

I'd review this commit by commit. Each commit builds and passes go test ./... on its own, and the generated README and toolsnaps match the code at every step.

  1. Extract ownerAccountType from detectOwnerType. This is a refactor of pkg/github/projects.go with no behavior change.
  2. Resolve issue types and fields for user-owned repositories. This is the core change: issueMetadataScope in pkg/github/issue_metadata_scope.go, then the call sites in issues.go, issue_fields.go, ui_tools.go, and ui/src/apps/issue-write/App.tsx, with the tests. The fixture tweaks in typed_issue_outputs_test.go and typed_copilot_ui_outputs_test.go add an owner lookup mock or a repo argument.
  3. Stop challenging read:org for owner-level issue metadata. This is the behavior change called out above, isolated in pkg/github/tool_scopes.go and its tests, plus the two regenerated scope lines in the README.
  4. Describe issue metadata owners as users or organizations. Wording only, plus the regenerated README and toolsnaps. Safe to skim.
  5. Don't echo the owner argument in the user owner error. Code scanning flagged that the error for another user's account reflected the raw owner argument back to the model, so the message now says "the owner" instead.

MCP impact

  • No tool or API changes
  • Tool schema or behavior changed
  • New tool added

Tool names and input shapes are unchanged. list_issue_types and list_issue_fields now succeed for user owners who are the authenticated user, ui_get takes an optional repo for issue_types, and the descriptions and OAuth challenge scopes changed as described above.

Prompts tested (tool changes only)

I didn't run these against a live MCP client. The tool tests exercise each scenario end to end against mocked REST and GraphQL responses:

  • "What issue types can I use in my repository octocat/hello-world?" (repo path, user-owned repository)
  • "List the issue types on my account" as the authenticated user OctoCat (GET /user/issue-types)
  • "List the issue fields on my account" as the authenticated user (user(login:) issueFields)
  • "List issue types for hubot" when hubot is a different user (error asking for repo)
  • "List issue types and fields for the testorg organization" (organization path, unchanged)
  • Opening the issue-write app on a user-owned repository (ui_get issue_types with repo)

Security / limits

  • No security or limits impact
  • Auth / permissions considered
  • Data exposure, filtering, or token/size limits considered

See the read:org note at the top. The user path only lists definitions for the authenticated user, and other user owners get a clear error rather than a request on their behalf. Repository results keep the repo visibility IFC label, and owner-level results keep the conservative private label.

Tool renaming

  • I am renaming tools as part of this PR (e.g. a part of a consolidation effort)
    • I have added the new tool aliases in deprecated_tool_aliases.go
  • I am not renaming tools as part of this PR

Note: if you're renaming tools, you must add the tool aliases. For more information on how to do so, please refer to the official docs.

Lint & tests

  • Linted locally with ./script/lint

  • Tested locally with ./script/test

  • go build ./... passed.

  • script/test passed.

  • script/lint passed with 0 issues.

  • UPDATE_TOOLSNAPS=true go test ./... passed, and the toolsnap updates are committed.

  • script/generate-docs passed, and the README update is committed.

  • In ui/, npm test passed locally (11 tests). CI's build job runs script/build-ui, which runs npm test and npm run build, and it passed on all three platforms. Neither CI nor I ran npm run typecheck. The UI change only adds repo to one callTool argument object.

I also checked the new tests catch regressions. Each of these changes made the named tests fail:

  • Letting any user owner through the viewer check failed the "another user owner asks for repo" cases in Test_ListIssueTypes and Test_ListIssueFields.
  • Routing the authenticated user to orgs/{owner}/issue-types failed the authenticated-user case in Test_ListIssueTypes.
  • Restoring the read:org challenge for owner-level calls failed TestConditionalToolScopeChecks.
  • Having ui_get ignore repo failed the repository case in Test_UIGet.

Docs

  • Not needed
  • Updated (README / docs / examples)

The generated README now describes the owner as a user or organization and lists repo as the only OAuth challenge scope for list_issue_types and list_issue_fields.

@dewski
dewski added this pull request to stack #3833 November 12, 2026 19:18
Comment thread pkg/github/issue_metadata_scope.go Resolved
dewski and others added 4 commits November 12, 2026 11:38
Pull the Users.Get account type lookup out of detectOwnerType so issue
metadata can reuse it to tell a user owner from an organization. The
project probe fallback is unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 48ff87e1-24bb-49f1-887f-9f2576ab0c2d
Issue types and issue fields are available on user accounts as well as
organizations. list_issue_types, list_issue_fields, and the ui_get
issue_types method assumed the owner was an organization when repo was
omitted, so they failed or came back empty for user-owned repositories.

Introduce issueMetadataScope (repository, organization, or the
authenticated user) and resolve it once per call. With repo, the
repository path is unchanged. Without repo, the owner's account type
picks the organization endpoints or, when the owner is the authenticated
user, GET /user/issue-types and GraphQL user(login:).issueFields. GitHub
lists a user's definitions only to that user, so any other user owner
gets a tool error asking for repo instead of a raw FORBIDDEN.

The issue-write app now passes repo to ui_get issue_types, which reads
the repository endpoint.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 48ff87e1-24bb-49f1-887f-9f2576ab0c2d
Owner-level list_issue_types, list_issue_fields, and ui_get issue_types
calls challenged for read:org because the owner was assumed to be an
organization. A user owner needs no extra scope, and the arguments alone
cannot tell a user from an organization, so the challenge would ask user
callers for a scope they do not need.

Rename repositoryOrOrganizationScopeAccess to
optionalRepositoryScopeAccess, which challenges for repo only when repo
is passed, and treat ui_get issue_types like the other repository
methods. An organization caller without read:org now gets the API error
instead of a step-up prompt.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 48ff87e1-24bb-49f1-887f-9f2576ab0c2d
Tool descriptions, toolset instructions, the README, and toolsnaps said
issue types and fields belong to an organization. Describe the owner as
a user or organization, and note that ui_get takes an optional repo for
issue_types.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 48ff87e1-24bb-49f1-887f-9f2576ab0c2d
@dewski
dewski force-pushed the dewski/owner-agnostic-issue-types-fields branch from 9ec3a65 to 001cf21 Compare November 12, 2026 19:39
resolveIssueMetadataScope put the raw owner argument into the tool error
it returns for another user's account. owner comes from the model, and
go-github doesn't escape it in the users/{owner} path, so a value like
"octocat?x=..." still resolves to a real user and its extra text would
reach the model verbatim. Refer to "the owner" instead, since the model
already knows what it passed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 48ff87e1-24bb-49f1-887f-9f2576ab0c2d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants