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 owner May 27, 2026 14:48
Copilot AI review requested due to automatic review settings May 27, 2026 14:48
@SamMorrowDrums SamMorrowDrums changed the base branch from main to kelsey/issue-fields-clean May 27, 2026 14:49
@SamMorrowDrums SamMorrowDrums changed the base branch from kelsey/issue-fields-clean to main May 27, 2026 14:50
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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
File Description
README.md Removes issue_fields from the default (flag-off) issue_write parameter docs.
pkg/inventory/server_tool.go Changes ServerTool.FeatureFlagDisable to []string to support multi-flag disable gating.
pkg/inventory/resources.go Changes ServerResourceTemplate.FeatureFlagDisable to []string.
pkg/inventory/prompts.go Changes ServerPrompt.FeatureFlagDisable to []string.
pkg/inventory/filters.go Updates feature gating logic to exclude items when any disable-flag is enabled.
pkg/inventory/registry_test.go Updates inventory tests/helpers to reflect []string disable flags.
pkg/http/handler_test.go Updates test helper to populate FeatureFlagDisable as a slice.
pkg/github/tools.go Registers LegacyIssueWrite alongside IssueWrite in the tool list.
pkg/github/tools_validation_test.go Updates “feature-flagged tool” detection for []string disable flags.
pkg/github/pullrequests.go Migrates FeatureFlagDisable assignments to slice literals.
pkg/github/issues.go Implements IssueWrite/LegacyIssueWrite split, schema trimming, and get_issue enrichment gating.
pkg/github/issues_test.go Updates toolsnap assertions and adds legacy issue_write schema test; adjusts get_issue expectations for flag-off.
pkg/github/csv_output_test.go Updates test tool disable flag to []string.
pkg/github/toolsnaps/issue_write.snap Updates canonical issue_write snapshot to the legacy (flag-off) schema.
pkg/github/toolsnaps/issue_write_ff_remote_mcp_issue_fields.snap Adds snapshot for the flag-enabled issue_write schema variant.
docs/insiders-features.md Regenerated docs to show issue_write.issue_fields only under remote_mcp_issue_fields.
docs/feature-flags.md Regenerated 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 thread pkg/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 thread pkg/github/issues_test.go
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")
}
SamMorrowDrums and 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 thread pkg/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
Collaborator Author

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 main May 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 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.

3 participants