Skip to content

Commit 5d40905

Browse files
committed
Use empty strings to clear issue types
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ea8faa5c-7f26-4e2d-bf9c-6f0b5f173e8c
1 parent c8eea3e commit 5d40905

11 files changed

Lines changed: 58 additions & 103 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -941,7 +941,7 @@ The following sets of tools are available:
941941
- `state`: New state (string, optional)
942942
- `state_reason`: Reason for the state change. Ignored unless state is changed. (string, optional)
943943
- `title`: Issue title (string, optional)
944-
- `type`: Type of this issue. Use list_issue_types to get valid values. For updates, pass null to remove the current type. If the repository doesn't support issue types, omit this parameter. (, optional)
944+
- `type`: Type of this issue. For updates, use an empty string to remove the current type. Only use if issue types are enabled for this repository. Use list_issue_types to get valid type values for this repository or its owner organization. If the repository doesn't support issue types, omit this parameter. (string, optional)
945945

946946
- **list_issue_fields** - List issue fields
947947
- **Required OAuth Scopes (any of)**: `repo`, `read:org`

docs/feature-flags.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ runtime behavior (such as output formatting) won't appear here.
7171
- `state`: New state (string, optional)
7272
- `state_reason`: Reason for the state change. Ignored unless state is changed. (string, optional)
7373
- `title`: Issue title (string, optional)
74-
- `type`: Type of this issue. Use list_issue_types to get valid values. For updates, pass null to remove the current type. If the repository doesn't support issue types, omit this parameter. (, optional)
74+
- `type`: Type of this issue. For updates, use an empty string to remove the current type. Only use if issue types are enabled for this repository. Use list_issue_types to get valid type values for this repository or its owner organization. If the repository doesn't support issue types, omit this parameter. (string, optional)
7575

7676
- **ui_get** - Get UI data
7777
- **Required OAuth Scopes (any of)**: `repo`, `read:org`
@@ -200,7 +200,7 @@ runtime behavior (such as output formatting) won't appear here.
200200
- `confidence`: How confident you are in this choice. Use 'HIGH' for clear signal or explicit user request, 'MEDIUM' for reasonable inference with some ambiguity, 'LOW' for best guess with limited signal. (string, optional)
201201
- `is_suggestion`: If true, this issue type change is sent to the API as a suggestion (suggest:true) rather than an applied value. Whether the type is applied or recorded as a proposal is determined by the API. (boolean, optional)
202202
- `issue_number`: The issue number to update (number, required)
203-
- `issue_type`: The issue type to set, or null to remove the current type (, required)
203+
- `issue_type`: The issue type to set, or an empty string to remove the current type (string, required)
204204
- `owner`: Repository owner (username or organization) (string, required)
205205
- `rationale`: One concise sentence explaining what specifically about the issue led you to choose this type. State the concrete signal (e.g. 'Reports a crash when saving' → bug, 'Asks for dark mode support' → feature). (string, optional)
206206
- `repo`: Repository name (string, required)

docs/insiders-features.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ The list below is generated from the Go source. It covers tool **inventory and s
6565
- `state`: New state (string, optional)
6666
- `state_reason`: Reason for the state change. Ignored unless state is changed. (string, optional)
6767
- `title`: Issue title (string, optional)
68-
- `type`: Type of this issue. Use list_issue_types to get valid values. For updates, pass null to remove the current type. If the repository doesn't support issue types, omit this parameter. (, optional)
68+
- `type`: Type of this issue. For updates, use an empty string to remove the current type. Only use if issue types are enabled for this repository. Use list_issue_types to get valid type values for this repository or its owner organization. If the repository doesn't support issue types, omit this parameter. (string, optional)
6969

7070
- **ui_get** - Get UI data
7171
- **Required OAuth Scopes (any of)**: `repo`, `read:org`

pkg/github/__toolsnaps__/issue_write.snap

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,8 @@
120120
"type": "string"
121121
},
122122
"type": {
123-
"description": "Type of this issue. Use list_issue_types to get valid values. For updates, pass null to remove the current type. If the repository doesn't support issue types, omit this parameter.",
124-
"type": [
125-
"string",
126-
"null"
127-
]
123+
"description": "Type of this issue. For updates, use an empty string to remove the current type. Only use if issue types are enabled for this repository. Use list_issue_types to get valid type values for this repository or its owner organization. If the repository doesn't support issue types, omit this parameter.",
124+
"type": "string"
128125
}
129126
},
130127
"required": [

pkg/github/__toolsnaps__/update_issue_type.snap

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"readOnlyHint": false,
77
"title": "Update Issue Type"
88
},
9-
"description": "Set or remove the type of an existing issue. Pass null to remove the current type. When setting a value, include a confidence level (LOW, MEDIUM, or HIGH) reflecting how certain you are about the choice.",
9+
"description": "Set or remove the type of an existing issue. Use an empty issue_type to remove the current type. When setting a value, include a confidence level (LOW, MEDIUM, or HIGH) reflecting how certain you are about the choice.",
1010
"inputSchema": {
1111
"properties": {
1212
"confidence": {
@@ -28,11 +28,8 @@
2828
"type": "number"
2929
},
3030
"issue_type": {
31-
"description": "The issue type to set, or null to remove the current type",
32-
"type": [
33-
"string",
34-
"null"
35-
]
31+
"description": "The issue type to set, or an empty string to remove the current type",
32+
"type": "string"
3633
},
3734
"owner": {
3835
"description": "Repository owner (username or organization)",

pkg/github/granular_tools_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,7 @@ func TestGranularUpdateIssueMilestone(t *testing.T) {
756756

757757
func TestGranularUpdateIssueType(t *testing.T) {
758758
toolSchema := GranularUpdateIssueType(translations.NullTranslationHelper).Tool.InputSchema.(*jsonschema.Schema)
759-
assert.ElementsMatch(t, []string{"string", "null"}, toolSchema.Properties["issue_type"].Types)
759+
assert.Equal(t, "string", toolSchema.Properties["issue_type"].Type)
760760

761761
tests := []struct {
762762
name string
@@ -792,12 +792,12 @@ func TestGranularUpdateIssueType(t *testing.T) {
792792
},
793793
},
794794
{
795-
name: "remove type",
795+
name: "remove type with empty string",
796796
requestArgs: map[string]any{
797797
"owner": "owner",
798798
"repo": "repo",
799799
"issue_number": float64(1),
800-
"issue_type": nil,
800+
"issue_type": "",
801801
},
802802
expectedReq: map[string]any{
803803
"type": nil,

pkg/github/issues.go

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2150,8 +2150,8 @@ Options are:
21502150
Description: "Milestone number",
21512151
},
21522152
"type": {
2153-
Types: []string{"string", "null"},
2154-
Description: "Type of this issue. Use list_issue_types to get valid values. For updates, pass null to remove the current type. If the repository doesn't support issue types, omit this parameter.",
2153+
Type: "string",
2154+
Description: "Type of this issue. For updates, use an empty string to remove the current type. Only use if issue types are enabled for this repository. Use list_issue_types to get valid type values for this repository or its owner organization. If the repository doesn't support issue types, omit this parameter.",
21552155
},
21562156
"state": {
21572157
Type: "string",
@@ -2275,14 +2275,10 @@ Options are:
22752275
}
22762276

22772277
// Get optional type
2278-
issueTypeParam, issueTypeProvided, err := OptionalNullableStringParam(args, "type")
2278+
issueType, issueTypeProvided, err := OptionalParamOK[string](args, "type")
22792279
if err != nil {
22802280
return utils.NewToolResultError(err.Error()), nil, nil
22812281
}
2282-
issueType := ""
2283-
if issueTypeParam != nil {
2284-
issueType = *issueTypeParam
2285-
}
22862282

22872283
// Handle state, state_reason and duplicateOf parameters
22882284
state, err := OptionalParam[string](args, "state")
@@ -2340,7 +2336,7 @@ Options are:
23402336
result, err := UpdateIssue(ctx, client, gqlClient, owner, repo, issueNumber, title, body, assignees, labels, milestoneNum, issueType, issueFieldValues, fieldIDsToDelete, state, stateReason, duplicateOf, UpdateIssueOptions{
23412337
AssigneesProvided: assigneesProvided,
23422338
LabelsProvided: labelsProvided,
2343-
ClearIssueType: issueTypeProvided && issueTypeParam == nil,
2339+
ClearIssueType: issueTypeProvided && issueType == "",
23442340
})
23452341
return result, nil, err
23462342
default:

pkg/github/issues_granular.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ func GranularUpdateIssueType(t translations.TranslationHelperFunc) inventory.Ser
685685
ToolsetMetadataIssues,
686686
mcp.Tool{
687687
Name: "update_issue_type",
688-
Description: t("TOOL_UPDATE_ISSUE_TYPE_DESCRIPTION", "Set or remove the type of an existing issue. Pass null to remove the current type. When setting a value, include a confidence level (LOW, MEDIUM, or HIGH) reflecting how certain you are about the choice."),
688+
Description: t("TOOL_UPDATE_ISSUE_TYPE_DESCRIPTION", "Set or remove the type of an existing issue. Use an empty issue_type to remove the current type. When setting a value, include a confidence level (LOW, MEDIUM, or HIGH) reflecting how certain you are about the choice."),
689689
Annotations: &mcp.ToolAnnotations{
690690
Title: t("TOOL_UPDATE_ISSUE_TYPE_USER_TITLE", "Update Issue Type"),
691691
ReadOnlyHint: false,
@@ -709,8 +709,8 @@ func GranularUpdateIssueType(t translations.TranslationHelperFunc) inventory.Ser
709709
Minimum: jsonschema.Ptr(1.0),
710710
},
711711
"issue_type": {
712-
Types: []string{"string", "null"},
713-
Description: "The issue type to set, or null to remove the current type",
712+
Type: "string",
713+
Description: "The issue type to set, or an empty string to remove the current type",
714714
},
715715
"rationale": {
716716
Type: "string",
@@ -746,7 +746,7 @@ func GranularUpdateIssueType(t translations.TranslationHelperFunc) inventory.Ser
746746
if err != nil {
747747
return utils.NewToolResultError(err.Error()), nil, nil
748748
}
749-
issueType, issueTypeProvided, err := OptionalNullableStringParam(args, "issue_type")
749+
issueType, issueTypeProvided, err := OptionalParamOK[string](args, "issue_type")
750750
if err != nil {
751751
return utils.NewToolResultError(err.Error()), nil, nil
752752
}
@@ -773,7 +773,7 @@ func GranularUpdateIssueType(t translations.TranslationHelperFunc) inventory.Ser
773773
if err != nil {
774774
return utils.NewToolResultError(err.Error()), nil, nil
775775
}
776-
if issueType == nil && (rationale != "" || confidence != "" || isSuggestion) {
776+
if issueType == "" && (rationale != "" || confidence != "" || isSuggestion) {
777777
return utils.NewToolResultError("rationale, confidence, and is_suggestion cannot be used when removing an issue type"), nil, nil
778778
}
779779

@@ -784,19 +784,19 @@ func GranularUpdateIssueType(t translations.TranslationHelperFunc) inventory.Ser
784784

785785
var body any
786786
switch {
787-
case issueType == nil:
787+
case issueType == "":
788788
body = map[string]any{"type": nil}
789789
case rationale != "" || isSuggestion || confidence != "":
790790
body = &issueTypeUpdateRequest{
791791
Type: issueTypeWithIntent{
792-
Value: *issueType,
792+
Value: issueType,
793793
Rationale: rationale,
794794
Confidence: confidence,
795795
Suggest: isSuggestion,
796796
},
797797
}
798798
default:
799-
body = &github.UpdateIssueRequest{Type: issueType}
799+
body = &github.UpdateIssueRequest{Type: &issueType}
800800
}
801801

802802
apiURL := fmt.Sprintf("repos/%s/%s/issues/%d", owner, repo, issueNumber)

pkg/github/issues_test.go

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1434,7 +1434,7 @@ func Test_CreateIssue(t *testing.T) {
14341434
assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "labels")
14351435
assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "milestone")
14361436
assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "type")
1437-
assert.ElementsMatch(t, []string{"string", "null"}, tool.InputSchema.(*jsonschema.Schema).Properties["type"].Types)
1437+
assert.Equal(t, "string", tool.InputSchema.(*jsonschema.Schema).Properties["type"].Type)
14381438
assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "issue_fields")
14391439
assert.ElementsMatch(t, tool.InputSchema.(*jsonschema.Schema).Required, []string{"method", "owner", "repo"})
14401440

@@ -1513,6 +1513,36 @@ func Test_CreateIssue(t *testing.T) {
15131513
State: github.Ptr("open"),
15141514
},
15151515
},
1516+
{
1517+
name: "empty issue type is omitted when creating",
1518+
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
1519+
PostReposIssuesByOwnerByRepo: expectRequestBody(t, map[string]any{
1520+
"title": "Issue without a type",
1521+
"body": "",
1522+
}).andThen(
1523+
mockResponse(t, http.StatusCreated, &github.Issue{
1524+
Number: github.Ptr(125),
1525+
Title: github.Ptr("Issue without a type"),
1526+
HTMLURL: github.Ptr("https://github.com/owner/repo/issues/125"),
1527+
State: github.Ptr("open"),
1528+
}),
1529+
),
1530+
}),
1531+
requestArgs: map[string]any{
1532+
"method": "create",
1533+
"owner": "owner",
1534+
"repo": "repo",
1535+
"title": "Issue without a type",
1536+
"type": "",
1537+
},
1538+
expectError: false,
1539+
expectedIssue: &github.Issue{
1540+
Number: github.Ptr(125),
1541+
Title: github.Ptr("Issue without a type"),
1542+
HTMLURL: github.Ptr("https://github.com/owner/repo/issues/125"),
1543+
State: github.Ptr("open"),
1544+
},
1545+
},
15161546
{
15171547
name: "successful issue creation with issue fields reconciled by names",
15181548
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
@@ -1829,7 +1859,7 @@ func Test_issueWriteHasNonFormParams(t *testing.T) {
18291859
{name: "assignees present", args: map[string]any{"title": "t", "assignees": []any{"octocat"}}, want: false},
18301860
{name: "milestone present", args: map[string]any{"title": "t", "milestone": float64(2)}, want: false},
18311861
{name: "type present", args: map[string]any{"title": "t", "type": "Bug"}, want: false},
1832-
{name: "type clear present", args: map[string]any{"title": "t", "type": nil}, want: false},
1862+
{name: "type clear present", args: map[string]any{"title": "t", "type": ""}, want: false},
18331863
{name: "issue_fields present", args: map[string]any{"issue_fields": []any{map[string]any{"field_name": "Priority"}}}, want: false},
18341864
{name: "state present", args: map[string]any{"state": "closed"}, want: false},
18351865
{name: "state_reason present", args: map[string]any{"state_reason": "completed"}, want: false},
@@ -2965,7 +2995,7 @@ func Test_UpdateIssue(t *testing.T) {
29652995
expectedIssue: mockUpdatedIssue,
29662996
},
29672997
{
2968-
name: "partial update clears issue type",
2998+
name: "partial update clears issue type from empty string",
29692999
mockedRESTClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
29703000
PatchReposIssuesByOwnerByRepoByIssueNumber: expectRequestBody(t, map[string]any{
29713001
"type": nil,
@@ -2979,7 +3009,7 @@ func Test_UpdateIssue(t *testing.T) {
29793009
"owner": "owner",
29803010
"repo": "repo",
29813011
"issue_number": float64(123),
2982-
"type": nil,
3012+
"type": "",
29833013
},
29843014
expectError: false,
29853015
expectedIssue: mockUpdatedIssue,

pkg/github/params.go

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -193,24 +193,6 @@ func OptionalParam[T any](args map[string]any, p string) (T, error) {
193193
return args[p].(T), nil
194194
}
195195

196-
// OptionalNullableStringParam returns a nullable string while preserving
197-
// whether the parameter was omitted.
198-
func OptionalNullableStringParam(args map[string]any, p string) (*string, bool, error) {
199-
value, ok := args[p]
200-
if !ok {
201-
return nil, false, nil
202-
}
203-
if value == nil {
204-
return nil, true, nil
205-
}
206-
207-
stringValue, ok := value.(string)
208-
if !ok {
209-
return nil, true, fmt.Errorf("parameter %s is not of type string or null, is %T", p, value)
210-
}
211-
return &stringValue, true, nil
212-
}
213-
214196
// OptionalIntParam is a helper function that can be used to fetch a requested parameter from the request.
215197
// It does the following checks:
216198
// 1. Checks if the parameter is present in the request, if not, it returns its zero-value

0 commit comments

Comments
 (0)