Skip to content

Commit e659b09

Browse files
authored
fix(proxy): passthrough /rate_limit for CLI proxy liveness probe (#7187)
The gh CLI liveness probe calls `GET /rate_limit` to verify connectivity. The DIFC proxy was blocking this as an unrecognized endpoint (HTTP 403), causing the cli-proxy sidecar to fail fast and prevent agent startup. **Root cause:** `/rate_limit` is a GET request that enters the "read operation" path, but `MatchRoute()` returns nil for it, triggering the fail-closed "access denied: unrecognized endpoint" response. **Fix:** Add `/rate_limit` to the passthrough list alongside `/meta` — both are safe read-only metadata endpoints used by `gh` CLI for initialization. **Affected:** Any workflow using `features.cli-proxy: true` or `features.difc-proxy: true` with mcpg >= v0.3.23. Reported via: https://github.com/github/gh-aw/actions/runs/27112965256/job/80016366723?pr=37708
2 parents 6739e26 + 033eb87 commit e659b09

2 files changed

Lines changed: 27 additions & 3 deletions

File tree

internal/proxy/handler.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ func (h *proxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
5959
return
6060
}
6161

62-
// gh CLI probes /meta during initialization for feature detection.
63-
// Treat it like GraphQL introspection metadata and pass through unfiltered.
64-
if r.Method == http.MethodGet && rawPath == "/meta" {
62+
// gh CLI probes /meta and /rate_limit during initialization for feature detection
63+
// and connectivity checks. These are safe metadata endpoints — pass through unfiltered.
64+
if r.Method == http.MethodGet && (rawPath == "/meta" || rawPath == "/rate_limit") {
6565
h.passthrough(w, r, fullPath)
6666
return
6767
}

internal/proxy/handler_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,30 @@ func TestServeHTTP_MetaPassthrough(t *testing.T) {
118118
assert.Contains(t, w.Body.String(), "verifiable_password_authentication")
119119
}
120120

121+
func TestServeHTTP_RateLimitPassthrough(t *testing.T) {
122+
receivedURLCh := make(chan string, 1)
123+
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
124+
receivedURLCh <- r.URL.RequestURI()
125+
w.Header().Set("Content-Type", "application/json")
126+
w.WriteHeader(http.StatusOK)
127+
_, err := w.Write([]byte(`{"resources":{"core":{"limit":5000}}}`))
128+
require.NoError(t, err)
129+
}))
130+
defer upstream.Close()
131+
132+
s := newTestServer(t, upstream.URL)
133+
h := &proxyHandler{server: s}
134+
135+
req := httptest.NewRequest(http.MethodGet, "/api/v3/rate_limit", nil)
136+
w := httptest.NewRecorder()
137+
h.ServeHTTP(w, req)
138+
139+
receivedURL := <-receivedURLCh
140+
assert.Equal(t, http.StatusOK, w.Code)
141+
assert.Equal(t, "/rate_limit", receivedURL)
142+
assert.Contains(t, w.Body.String(), "core")
143+
}
144+
121145
// ─── ServeHTTP: write operations (non-GraphQL POST/PUT/DELETE/PATCH) ─────────
122146

123147
func TestServeHTTP_WriteOperationsPassthrough(t *testing.T) {

0 commit comments

Comments
 (0)