Skip to content

feat: gate issue_write and get_issue behind remote_mcp_issue_fields flag - #2558

Merged
SamMorrowDrums merged 6 commits into
mainfrom
sammorrowdrums/gate-issue-write
May 28, 2026
Merged

feat: gate issue_write and get_issue behind remote_mcp_issue_fields flag#2558
SamMorrowDrums merged 6 commits into
mainfrom
sammorrowdrums/gate-issue-write

Conversation

@SamMorrowDrums

Copy link
Copy Markdown
Collaborator

Summary

Ports the gating work from #2553 onto main. #2553 was merged into the kelsey/issue-fields-clean stack base, but that stack base never landed on main — so issue_write and get_issue are currently ungated on main even though list_issues, search_issues, and list_issue_fields are.

Picks the simpler half of #2553's inventory change: keep FeatureFlagEnable as a single string, only promote FeatureFlagDisable to []string (OR-of-any). The AND-on-enable shape in the original PR had no real use site and encoded dependencies rather than rollout, so I dropped it.

What changed

  • pkg/inventory: FeatureFlagDisable on ServerTool/ServerResourceTemplate/ServerPrompt becomes []string with OR semantics (any listed flag enabled → tool hidden). FeatureFlagEnable stays a single string.
  • pkg/github/issues.go:
    • Split into IssueWrite (FeatureFlagEnable = FeatureFlagIssueFields, exposes issue_fields) and LegacyIssueWrite (FeatureFlagDisable = {FeatureFlagIssuesGranular, FeatureFlagIssueFields}, omits issue_fields).
    • Both register as issue_write; mutually exclusive annotations pick exactly one at runtime.
    • Refactored into a shared buildIssueWrite(t, includeIssueFields) helper instead of duplicating the ~250-line tool definition.
    • GetIssue field-values enrichment now wrapped in a runtime IsFeatureEnabled(ctx, FeatureFlagIssueFields) check; the verbose REST IssueFieldValues is always cleared.
  • Existing single-flag FeatureFlagDisable call sites converted to slice literals.
  • New toolsnap: issue_write_ff_remote_mcp_issue_fields.snap. The canonical issue_write.snap is now owned by LegacyIssueWrite (no issue_fields property).
  • README, docs/feature-flags.md, docs/insiders-features.md regenerated.

MCP impact

  • Flag OFF: issue_write schema has no issue_fields property; get_issue response has no field_values and no raw issue_field_values. Behaviour matches pre-Issues-2.0.
  • Flag ON: issue_write accepts issue_fields with value / field_option_name / delete: true (fetch+merge semantics from Add support for issue fields values to issues.read and issues.write #2551 preserved); get_issue returns the enriched field_values.

Verification

Smoke-tested live against github/plan-track-agentic-toolkit#33:

WITHOUT --features: WITH --features remote_mcp_issue_fields:
issue_write: issue_fields=False issue_write: issue_fields=True
list_issue_fields: hidden list_issue_fields: exposed

Update via issue_fields: [{field_name: "Priority", field_option_name: "P2"}] against an issue with Priority=P1, Effort=Low produced Priority=P2, Effort=Low — non-destructive merge from #2551 confirmed still working under the gated path.

script/lint, script/test (with race), and live MCP stdio calls all pass.

Ports the gating from PR #2553 onto main (the original merge landed on a
stack base that did not make it to main).
Changes:
- pkg/inventory: FeatureFlagDisable becomes []string (any-listed-on → hide).
FeatureFlagEnable stays as a single string. This avoids the AND-of-enable
semantics from the earlier proposal, which encoded dependencies rather
than rollout knobs and had no real call site. Disable-OR is the case
that does need the slice (LegacyIssueWrite below).
- pkg/github/issues.go: split IssueWrite into IssueWrite (flag-enabled,
exposes issue_fields) and LegacyIssueWrite (flag-disabled, omits it).
Both register as 'issue_write'; mutually exclusive flag annotations
pick exactly one at runtime. Refactored into a shared buildIssueWrite
helper instead of duplicating the ~250-line tool definition.
- pkg/github/issues.go: GetIssue field_values enrichment now requires
the flag at runtime. The verbose REST IssueFieldValues is always
cleared from the response.
- Existing single-flag Disable call sites converted to slices.
- New toolsnap variant issue_write_ff_remote_mcp_issue_fields.snap; the
canonical issue_write.snap is owned by LegacyIssueWrite.
- README + flag docs regenerated.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SamMorrowDrums
SamMorrowDrums requested a review from a team as a code ownerMay 27, 2026 14:48
CopilotAI review requested due to automatic review settings May 27, 2026 14:48
@SamMorrowDrums
SamMorrowDrums changed the base branch from main to kelsey/issue-fields-cleanMay 27, 2026 14:49
@SamMorrowDrums
SamMorrowDrums changed the base branch from kelsey/issue-fields-clean to mainMay 27, 2026 14:50

CopilotAI 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

This PR aligns issue_write and get_issue with existing Issues 2.0 gating by moving issue-fields schema/output behind the remote_mcp_issue_fields feature flag, while also extending inventory support to allow disabling an item when any of several flags are enabled.

Changes:

  • Split issue_write into two mutually-exclusive inventory registrations (IssueWrite flag-enabled + LegacyIssueWrite flag-disabled) and added a new toolsnap for the flag-enabled schema.
  • Gated get_issue field-values enrichment behind remote_mcp_issue_fields, and always cleared the verbose REST issue_field_values output.
  • Changed inventory FeatureFlagDisable annotations from string to []string with OR semantics, and updated call sites/tests/docs accordingly.
Show a summary per file
FileDescription
README.mdRemoves issue_fields from the default (flag-off) issue_write parameter docs.
pkg/inventory/server_tool.goChanges ServerTool.FeatureFlagDisable to []string to support multi-flag disable gating.
pkg/inventory/resources.goChanges ServerResourceTemplate.FeatureFlagDisable to []string.
pkg/inventory/prompts.goChanges ServerPrompt.FeatureFlagDisable to []string.
pkg/inventory/filters.goUpdates feature gating logic to exclude items when any disable-flag is enabled.
pkg/inventory/registry_test.goUpdates inventory tests/helpers to reflect []string disable flags.
pkg/http/handler_test.goUpdates test helper to populate FeatureFlagDisable as a slice.
pkg/github/tools.goRegisters LegacyIssueWrite alongside IssueWrite in the tool list.
pkg/github/tools_validation_test.goUpdates “feature-flagged tool” detection for []string disable flags.
pkg/github/pullrequests.goMigrates FeatureFlagDisable assignments to slice literals.
pkg/github/issues.goImplements IssueWrite/LegacyIssueWrite split, schema trimming, and get_issue enrichment gating.
pkg/github/issues_test.goUpdates toolsnap assertions and adds legacy issue_write schema test; adjusts get_issue expectations for flag-off.
pkg/github/csv_output_test.goUpdates test tool disable flag to []string.
pkg/github/toolsnaps/issue_write.snapUpdates canonical issue_write snapshot to the legacy (flag-off) schema.
pkg/github/toolsnaps/issue_write_ff_remote_mcp_issue_fields.snapAdds snapshot for the flag-enabled issue_write schema variant.
docs/insiders-features.mdRegenerated docs to show issue_write.issue_fields only under remote_mcp_issue_fields.
docs/feature-flags.mdRegenerated docs to show issue_write.issue_fields only under remote_mcp_issue_fields.

Copilot's findings

  • Files reviewed: 17/17 changed files
  • Comments generated: 2

Comment threadpkg/github/issues.go
Comment on lines 2019 to 2022
gqlClient, err := deps.GetGQLClient(ctx)
if err != nil {
return utils.NewToolResultErrorFromErr("failed to get GraphQL client", err), nil, nil
}
Comment on lines 396 to 465
@@ -457,24 +459,9 @@ func Test_GetIssue_FieldValues(t *testing.T) {
err = json.Unmarshal([]byte(textContent.Text), &returnedIssue)
require.NoError(t, err)

require.Len(t, returnedIssue.IssueFieldValues, 2, "expected two issue field values")

first := returnedIssue.IssueFieldValues[0]
assert.Equal(t, int64(1001), first.IssueFieldID)
assert.Equal(t, "FV_node_1", first.NodeID)
assert.Equal(t, "single_select", first.DataType)
assert.Equal(t, "High", first.Value)
require.NotNil(t, first.SingleSelectOption)
assert.Equal(t, int64(42), first.SingleSelectOption.ID)
assert.Equal(t, "High", first.SingleSelectOption.Name)
assert.Equal(t, "red", first.SingleSelectOption.Color)

second := returnedIssue.IssueFieldValues[1]
assert.Equal(t, int64(1002), second.IssueFieldID)
assert.Equal(t, "FV_node_2", second.NodeID)
assert.Equal(t, "text", second.DataType)
assert.Equal(t, "some text value", second.Value)
assert.Nil(t, second.SingleSelectOption)
// Flag is off: raw REST IssueFieldValues must be cleared, enriched FieldValues absent.
assert.Empty(t, returnedIssue.IssueFieldValues, "raw REST issue_field_values should not be exposed when flag is off")
assert.Empty(t, returnedIssue.FieldValues, "enriched field_values should not be present when flag is off")
}
SamMorrowDrumsand others added 4 commits May 27, 2026 17:08
Replace the shared buildIssueWrite(includeIssueFields) helper with two
fully duplicated tool definitions. When the FeatureFlagIssueFields flag
is retired, LegacyIssueWrite can be deleted as a single function with no
merge thinking required.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Companion to Test_GetIssue_FieldValues: when remote_mcp_issue_fields is
enabled, the GraphQL nodes() round-trip populates the enriched
field_values while the raw REST issue_field_values stays cleared.
Addresses the Copilot review suggestion on #2558.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mergeIssueFieldValues built the merged slice by iterating a Go map,
which produces non-deterministic ordering and caused a flake in
Test_UpdateIssue/partial_update_with_issue_fields_reconciled_by_names
(introduced in #2551). Switch to an order-preserving merge: emit
incoming entries first in their original order, then any existing
entries (in their original order) whose field IDs weren't seen in
incoming. Semantics (incoming wins, existing preserved) unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment threadpkg/github/issues.go
}
})
st.FeatureFlagDisable = FeatureFlagIssuesGranular
st.FeatureFlagEnable = FeatureFlagIssueFields

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.

Do we need to add this flag to set_issue_fields as well? It currently is still available when issues_granular is enabled. Does that need to also have the remote_mcp_issue_fields flag enabled, so that issue field writes are consistently behind the same flag?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Issues granular I'm ok with having it alreayd.

@SamMorrowDrums
SamMorrowDrums merged commit 51a7383 into mainMay 28, 2026
20 checks passed
SamMorrowDrums added a commit that referenced this pull request May 28, 2026
Companion to Test_GetIssue_FieldValues: when remote_mcp_issue_fields is
enabled, the GraphQL nodes() round-trip populates the enriched
field_values while the raw REST issue_field_values stays cleared.
Addresses the Copilot review suggestion on #2558.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SamMorrowDrums
SamMorrowDrums deleted the sammorrowdrums/gate-issue-write branch May 28, 2026 11:11
Sign up for freeto 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.

3 participants

@SamMorrowDrums@RossTarrant