Add update_project_items batch/bulk field-value writes via aliased GraphQL mutations#2903
Draft
veralizeth wants to merge 2 commits into
Draft
Conversation
|
go |
13 tasks
zwick
force-pushed
the
veralizeth/mcp-batch-bulk-field-value
branch
from
July 20, 2026 19:43
15ca2ee to
b8f5ca9
Compare
…utations Rewrite update_project_items to resolve the project, every distinct field, and every item once up front, then write via chunked, aliased updateProjectV2ItemFieldValue / clearProjectV2ItemFieldValue GraphQL mutations built at runtime with reflect.StructOf, instead of one REST PATCH per item. - Extend ResolvedField/projectFieldsCache with GraphQL node IDs and numeric-ID lookup, alongside existing name-based lookup. - Add node_id as a third item-reference form (alongside item_id and item_owner/item_repo/issue_number), bypassing REST lookup entirely. - Deduplicate numeric item_id lookups with bounded-concurrency REST GETs; extend the #2914 paginated issue resolver to also return the item's node ID. - Convert updated_field.value to the correct ProjectV2FieldValue member for TEXT, NUMBER, DATE, SINGLE_SELECT, and ITERATION; route null values to the clear mutation instead of an empty update. - Reject duplicate item+field targets before any writes. - Isolate the reflection mechanics in projects_batch_mutation.go: a cached-by-(kind,size) reflected struct type with positional aliases (item0..itemN-1) and positional input variables (, , ...), matching the first-input workaround required by the pinned githubv4.Client.Mutate signature. - Execute updates then clears in sequential chunks of 20 (provisional, documented headroom rationale), tracking tri-state per-item results (succeeded/failed/unknown); abort remaining chunks after an ambiguous transport-level failure or context cancellation without retrying. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d7dc302d-e6f2-41e9-a2c8-ed598de47067
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
update_project_itemsnow writes via chunked, aliasedupdateProjectV2ItemFieldValue/clearProjectV2ItemFieldValueGraphQL mutations (built at runtime withreflect.StructOf) instead of one REST PATCH per item.Why
Fixes https://github.com/github/planning-tracking/issues/3331 (MCP half only; the CLI half will follow in a separate PR).
Stacking note: this PR is stacked on #2914 (
zwick-paginate-project-item-lookup), which extended the issue-number project-item resolver to paginate. This PR's base branch is set to that branch so the diff here only shows the batch-write changes.What changed
ResolvedField/projectFieldsCacheto also retain the GraphQL field node ID and to support lookup by numeric field ID (in addition to name), amortized once per batch.node_idas a third item-reference form (alongsideitem_idanditem_owner+item_repo+issue_number), bypassing REST lookup entirely; each item must supply exactly one form.item_idvalues are deduplicated and resolved to project-item node IDs via bounded-concurrency (5 in flight) REST GETs; the Paginate project item lookup across memberships #2914 paginated issue resolver now also returns the item's node ID.updated_field.valueis converted to the correctProjectV2FieldValuemember forTEXT,NUMBER,DATE,SINGLE_SELECT, andITERATION; other data types return a deterministic per-item error directing callers toupdate_project_item.value: nullnow routes to the clear mutation instead of sending an empty update.pkg/github/projects_batch_mutation.goisolates the reflection: a struct type cached by(kind, chunk size)with positional fields (Item0..ItemN-1), positional GraphQL aliases (item0..), and positional input variables. Per the pinnedgithubv4.Client.Mutatesignature, alias 0's input is always bound to$input; aliases 1+ use$input1,$input2, ... via the variables map (the "first-input workaround"). No owner/id/name/value data is ever embedded in a struct tag.succeeded/failed/unknown): a populated alias is a confirmed success even if the overall response also carried a GraphQL error; an unconfirmed alias after an error isunknown(the pinned client dropserrors[].path, so we can't attribute an error to a specific alias). After an ambiguous/transport-level chunk failure or context cancellation, no further chunks are sent — we don't retry or fall back to per-item mutations.MCP impact
update_project_itemsgainsnode_idas an item-reference option; its response now includes tri-statestatusandunknowncounts alongsidesucceeded/failed.Prompts tested (tool changes only)
Security / limits
Tool-level cap stays at 100 items/call; the internal wire chunk size of 20 is provisional (not benchmarked against the live API) and chosen to leave headroom under typical ~10s gateway/client timeouts for a single aliased-mutation round trip.
Tool renaming
deprecated_tool_aliases.goLint & tests
./script/lint./script/testDocs