Skip to content

Commit 48f0bb7

Browse files
tgockelclaude
andcommitted
fix(issues): allow delete:false in issue_write issue_fields
The `delete` property of issue_write's issue_fields items was declared with Enum: []any{true}, making true its only legal value. The property is optional, but a client that fills every property of a schema -- common, since OpenAI-style strict function calling requires every property to appear in `required` -- had no way to express "not deleting this field": there is no false in the enum and no null in the type. `value` offers no alternative either, being typed ["string","number","boolean"] with no null. The MCP Go SDK validates arguments against the resolved input schema before the handler runs, so delete: false was rejected at schema validation and never reached optionalIssueWriteFields. Such clients sent delete: true alongside a value instead and hit the handler's mutual-exclusion check, so issue_write could never set an issue field for them. Remove the enum so false is a legal no-op, and document that omitting the property or setting it to false leaves the field unchanged. No handler change is needed: the code already branches on `if deleteField`, so false falls through to the normal value path, and the mutual-exclusion check for delete: true still applies. Add tests for optionalIssueWriteFields, which had none. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 0ea1f77 commit 48f0bb7

3 files changed

Lines changed: 61 additions & 6 deletions

File tree

pkg/github/__toolsnaps__/issue_write.snap

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,7 @@
3737
"additionalProperties": false,
3838
"properties": {
3939
"delete": {
40-
"description": "Set to true to clear this field's current value on the issue. Cannot be combined with 'value' or 'field_option_name'.",
41-
"enum": [
42-
true
43-
],
40+
"description": "Set to true to clear this field's current value on the issue. Cannot be combined with 'value' or 'field_option_name'. Omit this property, or set it to false, to leave the field's current value unchanged.",
4441
"type": "boolean"
4542
},
4643
"field_name": {

pkg/github/issues.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2287,9 +2287,10 @@ Options are:
22872287
},
22882288
"delete": {
22892289
Type: "boolean",
2290-
Enum: []any{true},
22912290
Description: "Set to true to clear this field's current value on the " +
2292-
"issue. Cannot be combined with 'value' or 'field_option_name'.",
2291+
"issue. Cannot be combined with 'value' or 'field_option_name'. " +
2292+
"Omit this property, or set it to false, to leave the field's " +
2293+
"current value unchanged.",
22932294
},
22942295
},
22952296
Required: []string{"field_name"},

pkg/github/issues_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2098,6 +2098,63 @@ func Test_issueWriteHasNonFormParams(t *testing.T) {
20982098
}
20992099
}
21002100

2101+
// Test_optionalIssueWriteFields covers parsing of issue_write's issue_fields
2102+
// items. The delete:false cases matter because the schema deliberately does not
2103+
// constrain 'delete' to a single value: clients that populate every property of
2104+
// a schema need a way to say "not deleting", and false must be a no-op that
2105+
// falls through to the normal value path.
2106+
func Test_optionalIssueWriteFields(t *testing.T) {
2107+
t.Parallel()
2108+
2109+
tests := []struct {
2110+
name string
2111+
item map[string]any
2112+
want issueWriteFieldInput
2113+
wantErr string
2114+
}{
2115+
{
2116+
name: "delete false alongside a value sets the value",
2117+
item: map[string]any{"field_name": "Start date", "value": "2026-08-14", "delete": false, "field_option_name": ""},
2118+
want: issueWriteFieldInput{FieldName: "Start date", Value: "2026-08-14"},
2119+
},
2120+
{
2121+
name: "delete true alone clears the field",
2122+
item: map[string]any{"field_name": "Start date", "delete": true},
2123+
want: issueWriteFieldInput{FieldName: "Start date", Delete: true},
2124+
},
2125+
{
2126+
name: "delete omitted with field_option_name",
2127+
item: map[string]any{"field_name": "Priority", "field_option_name": "High"},
2128+
want: issueWriteFieldInput{FieldName: "Priority", FieldOptionName: "High"},
2129+
},
2130+
{
2131+
name: "delete true with a value is rejected",
2132+
item: map[string]any{"field_name": "Start date", "value": "2026-08-14", "delete": true},
2133+
wantErr: "cannot specify 'delete' together with 'value' or 'field_option_name'",
2134+
},
2135+
{
2136+
name: "delete false with nothing to set is rejected",
2137+
item: map[string]any{"field_name": "Start date", "delete": false},
2138+
wantErr: "must specify either value or field_option_name",
2139+
},
2140+
}
2141+
2142+
for _, tc := range tests {
2143+
t.Run(tc.name, func(t *testing.T) {
2144+
t.Parallel()
2145+
got, err := optionalIssueWriteFields(map[string]any{"issue_fields": []any{tc.item}})
2146+
if tc.wantErr != "" {
2147+
require.Error(t, err)
2148+
assert.Contains(t, err.Error(), tc.wantErr)
2149+
return
2150+
}
2151+
require.NoError(t, err)
2152+
require.Len(t, got, 1)
2153+
assert.Equal(t, tc.want, got[0])
2154+
})
2155+
}
2156+
}
2157+
21012158
// Test_issueWriteSchemaClassification fails when a schema property is added
21022159
// without classifying it as either form-resendable (issueWriteFormParams) or
21032160
// known-non-form (knownNonForm below). Without this guard, an unclassified

0 commit comments

Comments
 (0)