Skip to content

PR tools re-fetch data without conditional requests (ETag / If-None-Match) #3025

Description

@joshfree

Summary

The REST client is built with a transport chain (http.DefaultTransport → user-agent → bearer-auth) that has no response-cache or conditional-request layer, so pull-request tools (pull_request_read with get / get_files / get_commits / get_reviews / get_status, and list_pull_requests) issue unconditional GET requests. Repeated identical reads within or across sessions re-download full responses even when nothing has changed; the ETag GitHub returns is never captured or replayed as If-None-Match.

Where

  • Client/transport construction (single site): internal/ghmcp/server.gocreateGitHubClients() wires UserAgentTransport{ Transport: http.DefaultTransport } (and BearerAuthTransport) with no caching transport.
  • Transport package has only user-agent / bearer / graphql-features round-trippers: pkg/http/transport/.
  • PR tool handlers call client.PullRequests.Get/ListFiles/ListCommits/ListReviews/List unconditionally: pkg/github/pullrequests.go.

Impact on users

GitHub's REST API supports conditional requests: replaying the previous ETag as If-None-Match returns 304 Not Modified when nothing changed, and 304 responses don't count against the token's primary rate limit. Agentic sessions frequently re-read the same pull request (e.g. getget_filesget_reviews, retries, multi-step reasoning). Without conditional requests, each re-read is a full response that spends rate-limit budget and bandwidth, making the token's limit easier to hit and slowing tool calls.

Example prompts that trigger repeated reads

Suggested change

Insert an ETag-revalidating http.RoundTripper at the single client-construction site in createGitHubClients(), below the user-agent transport: cache {etag, body} keyed by (auth scope, method, URL), send If-None-Match on GETs, and return the cached body on 304. An always-revalidate ETag transport keeps tool results fresh (no stale window) while turning unchanged reads into cheap 304s. The GraphQL client is unaffected (POST). This keeps results identical while reducing rate-limit pressure and bandwidth.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions