Skip to content

[Repo Assist] feat(proxy): rebuild GraphQL responses with accessible items after DIFC filtering#2330

Closed
github-actions[bot] wants to merge 1 commit intomainfrom
repo-assist/feat-graphql-partial-response-2026-03-22-c67354a2f6693d52
Closed

[Repo Assist] feat(proxy): rebuild GraphQL responses with accessible items after DIFC filtering#2330
github-actions[bot] wants to merge 1 commit intomainfrom
repo-assist/feat-graphql-partial-response-2026-03-22-c67354a2f6693d52

Conversation

@github-actions
Copy link
Contributor

🤖 This is an automated PR from Repo Assist.

Summary

When DIFC filtering removes some (but not all) items from a GraphQL collection response, the proxy previously returned {"data":null} — even when accessible items remained. This meant the gh CLI would see an empty result instead of the items the agent is actually allowed to access.

This PR adds RebuildGraphQLCollectionResponse which:

  • Creates a deep copy of the original GraphQL response via a JSON round-trip
  • Replaces the nodes array at the first matching collection path:
    • data.repository.issues.nodes
    • data.repository.pullRequests.nodes
    • data.repository.discussions.nodes
    • data.repository.discussionCategories.nodes
    • data.search.nodes
  • Preserves all other fields (totalCount, pageInfo, cursor fields, etc.)
  • Returns nil when the response doesn't match a known collection path (single-object queries, viewer queries, etc.), allowing the existing writeEmptyResponse fallback to handle those cases

How

graphql.go: New RebuildGraphQLCollectionResponse(originalData interface{}, accessibleItems []interface{}) interface{} function with graphqlCollectionRepoFields var listing the four repo collection fields. The paths mirror the guard's GRAPHQL_COLLECTION_FIELDS constant in helpers.rs.

handler.go Phase 5: replace the else if graphQLBody != nil block:

Before — always returned {"data":null} on any filtering:

} else if graphQLBody != nil {
    h.writeEmptyResponse(w, resp, responseData)
    return

After — rebuilds the response with only the accessible nodes:

} else if graphQLBody != nil {
    rawResult, toResultErr := filtered.ToResult()
    ...
    rebuilt := RebuildGraphQLCollectionResponse(responseData, accessibleItems)
    if rebuilt != nil {
        finalData = rebuilt  // fall through to Phase 6 and serialization
    } else {
        h.writeEmptyResponse(...)  // unrecognized shape
    }

Trade-offs

  • JSON round-trip adds a small allocation per filtered GraphQL response; this only happens when items are actually filtered, not on the fast path.
  • Only the five well-known nodes paths are supported. GraphQL queries that return data in other shapes (e.g. viewer, custom projections) still fall back to {"data":null} — this matches the guard's own extract_graphql_nodes coverage.

Test Status

  • ➕ Added TestRebuildGraphQLCollectionResponse with 9 subtests covering all four repo fields, data.search.nodes, empty accessible items, immutability of the original data, and nil/unrecognized inputs.
  • ⚠️ Local build not possible: the sandbox environment has Go 1.24.x but go.mod requires Go 1.25.0 and proxy.golang.org is firewalled. CI will validate.

Generated by Repo Assist ·

Generated by Repo Assist ·

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/repo-assist.md@851905c06e905bf362a9f6cc54f912e3df747d55

Warning

⚠️ Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • proxy.golang.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "proxy.golang.org"

See Network Configuration for more information.

…FC filtering

Previously, when DIFC filtering removed any items from a GraphQL collection
response, the proxy returned {"data":null} — even when accessible items
remained. This caused the gh CLI to see an empty result instead of the items
the agent is actually allowed to access.

Add RebuildGraphQLCollectionResponse in graphql.go which:
- Makes a deep copy of the original GraphQL response (JSON round-trip)
- Replaces the nodes array at the first known collection path:
    data.repository.{issues,pullRequests,discussions,discussionCategories}.nodes
    data.search.nodes
- Preserves all other fields (totalCount, pageInfo, etc.)
- Returns nil if the response doesn't match a known collection path

Update handler.go Phase 5 to use this when graphQLBody != nil and items
are filtered: instead of writing {"data":null} and returning, we call
RebuildGraphQLCollectionResponse and set finalData to the rebuilt response.
If the response shape is unrecognized (single-object query, etc.) we still
fall back to writeEmptyResponse.

Add TestRebuildGraphQLCollectionResponse with 9 subtests covering:
- All four repo collection fields (issues, pullRequests, discussions, discussionCategories)
- data.search.nodes
- Empty accessible items (all filtered)
- Immutability (original data not modified)
- nil / flat-array inputs returning nil
- Objects with data key but no collection field returning nil

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant