From 33c0dc3d87dc271aff4642723cd77257764a7760 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 15:12:04 +0000 Subject: [PATCH 1/5] Initial plan From 4a24b3b3693924830ef7871634763aad7b0e59fc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 15:18:28 +0000 Subject: [PATCH 2/5] Add 503 response for MCP requests during shutdown and fix difc build issue Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- internal/difc/evaluator.go | 24 +-- internal/server/routed.go | 27 ++- internal/server/shutdown_test.go | 289 +++++++++++++++++++++++++++++++ internal/server/transport.go | 27 ++- 4 files changed, 351 insertions(+), 16 deletions(-) create mode 100644 internal/server/shutdown_test.go diff --git a/internal/difc/evaluator.go b/internal/difc/evaluator.go index 1bdfe2cd4..3b2f7de88 100644 --- a/internal/difc/evaluator.go +++ b/internal/difc/evaluator.go @@ -7,7 +7,7 @@ import ( "github.com/githubnext/gh-aw-mcpg/internal/logger" ) -var log = logger.New("difc:evaluator") +var logEvaluator = logger.New("difc:evaluator") // OperationType indicates the nature of the resource access type OperationType int @@ -78,7 +78,7 @@ func (e *Evaluator) Evaluate( resource *LabeledResource, operation OperationType, ) *EvaluationResult { - log.Printf("Evaluating access: operation=%s, resource=%s", operation, resource.Description) + logEvaluator.Printf("Evaluating access: operation=%s, resource=%s", operation, resource.Description) result := &EvaluationResult{ Decision: AccessAllow, @@ -115,7 +115,7 @@ func (e *Evaluator) evaluateRead( agentIntegrity *IntegrityLabel, resource *LabeledResource, ) *EvaluationResult { - log.Printf("Evaluating read access: resource=%s, agentSecrecy=%v, agentIntegrity=%v", + logEvaluator.Printf("Evaluating read access: resource=%s, agentSecrecy=%v, agentIntegrity=%v", resource.Description, agentSecrecy.Label.GetTags(), agentIntegrity.Label.GetTags()) result := &EvaluationResult{ @@ -128,7 +128,7 @@ func (e *Evaluator) evaluateRead( // Agent must trust the resource (resource has all integrity tags agent requires) ok, missingTags := resource.Integrity.CheckFlow(agentIntegrity) if !ok { - log.Printf("Read denied: integrity check failed, missingTags=%v", missingTags) + logEvaluator.Printf("Read denied: integrity check failed, missingTags=%v", missingTags) result.Decision = AccessDeny result.IntegrityToDrop = missingTags result.Reason = fmt.Sprintf("Resource '%s' has lower integrity than agent requires. "+ @@ -141,7 +141,7 @@ func (e *Evaluator) evaluateRead( // All resource secrecy tags must be present in agent secrecy ok, extraTags := resource.Secrecy.CheckFlow(agentSecrecy) if !ok { - log.Printf("Read denied: secrecy check failed, extraTags=%v", extraTags) + logEvaluator.Printf("Read denied: secrecy check failed, extraTags=%v", extraTags) result.Decision = AccessDeny result.SecrecyToAdd = extraTags result.Reason = fmt.Sprintf("Resource '%s' has secrecy requirements that agent doesn't meet. "+ @@ -150,7 +150,7 @@ func (e *Evaluator) evaluateRead( return result } - log.Printf("Read access allowed: resource=%s", resource.Description) + logEvaluator.Printf("Read access allowed: resource=%s", resource.Description) return result } @@ -160,7 +160,7 @@ func (e *Evaluator) evaluateWrite( agentIntegrity *IntegrityLabel, resource *LabeledResource, ) *EvaluationResult { - log.Printf("Evaluating write access: resource=%s, agentSecrecy=%v, agentIntegrity=%v", + logEvaluator.Printf("Evaluating write access: resource=%s, agentSecrecy=%v, agentIntegrity=%v", resource.Description, agentSecrecy.Label.GetTags(), agentIntegrity.Label.GetTags()) result := &EvaluationResult{ @@ -173,7 +173,7 @@ func (e *Evaluator) evaluateWrite( // Agent must be trustworthy enough (agent has all integrity tags resource requires) ok, missingTags := agentIntegrity.CheckFlow(&resource.Integrity) if !ok { - log.Printf("Write denied: integrity check failed, missingTags=%v", missingTags) + logEvaluator.Printf("Write denied: integrity check failed, missingTags=%v", missingTags) result.Decision = AccessDeny result.IntegrityToDrop = missingTags result.Reason = fmt.Sprintf("Agent lacks required integrity to write to '%s'. "+ @@ -186,7 +186,7 @@ func (e *Evaluator) evaluateWrite( // All agent secrecy tags must be present in resource secrecy ok, extraTags := agentSecrecy.CheckFlow(&resource.Secrecy) if !ok { - log.Printf("Write denied: secrecy check failed, extraTags=%v", extraTags) + logEvaluator.Printf("Write denied: secrecy check failed, extraTags=%v", extraTags) result.Decision = AccessDeny result.SecrecyToAdd = extraTags result.Reason = fmt.Sprintf("Agent has secrecy tags %v that cannot flow to '%s'. "+ @@ -195,7 +195,7 @@ func (e *Evaluator) evaluateWrite( return result } - log.Printf("Write access allowed: resource=%s", resource.Description) + logEvaluator.Printf("Write access allowed: resource=%s", resource.Description) return result } @@ -245,7 +245,7 @@ func (e *Evaluator) FilterCollection( collection *CollectionLabeledData, operation OperationType, ) *FilteredCollectionLabeledData { - log.Printf("Filtering collection: operation=%s, totalItems=%d", operation, len(collection.Items)) + logEvaluator.Printf("Filtering collection: operation=%s, totalItems=%d", operation, len(collection.Items)) filtered := &FilteredCollectionLabeledData{ Accessible: []LabeledItem{}, @@ -264,7 +264,7 @@ func (e *Evaluator) FilterCollection( } } - log.Printf("Collection filtered: accessible=%d, filtered=%d, total=%d", + logEvaluator.Printf("Collection filtered: accessible=%d, filtered=%d, total=%d", len(filtered.Accessible), len(filtered.Filtered), filtered.TotalCount) return filtered } diff --git a/internal/server/routed.go b/internal/server/routed.go index edbe582d4..7148ea36b 100644 --- a/internal/server/routed.go +++ b/internal/server/routed.go @@ -3,6 +3,7 @@ package server import ( "bytes" "context" + "encoding/json" "fmt" "io" "log" @@ -16,6 +17,24 @@ import ( var logRouted = logger.New("server:routed") +// rejectIfShutdown is a middleware that rejects requests with HTTP 503 when gateway is shutting down +// Per spec 5.1.3: "Immediately reject any new RPC requests to /mcp/{server-name} endpoints with HTTP 503" +func rejectIfShutdown(unifiedServer *UnifiedServer, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if unifiedServer.IsShutdown() { + logRouted.Printf("Rejecting request during shutdown: remote=%s, method=%s, path=%s", r.RemoteAddr, r.Method, r.URL.Path) + logger.LogWarn("shutdown", "Request rejected during shutdown, remote=%s, path=%s", r.RemoteAddr, r.URL.Path) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusServiceUnavailable) + json.NewEncoder(w).Encode(map[string]string{ + "error": "Gateway is shutting down", + }) + return + } + next.ServeHTTP(w, r) + }) +} + // filteredServerCache caches filtered server instances per (backend, session) key type filteredServerCache struct { servers map[string]*sdk.Server @@ -141,10 +160,14 @@ func CreateHTTPServerForRoutedMode(addr string, unifiedServer *UnifiedServer, ap // Wrap SDK handler with detailed logging for JSON-RPC translation debugging loggedHandler := WithSDKLogging(routeHandler, "routed:"+backendID) + // Apply shutdown check middleware (spec 5.1.3) + // This must come before auth to ensure shutdown takes precedence + shutdownHandler := rejectIfShutdown(unifiedServer, loggedHandler) + // Apply auth middleware if API key is configured (spec 7.1) - finalHandler := loggedHandler + finalHandler := shutdownHandler if apiKey != "" { - finalHandler = authMiddleware(apiKey, loggedHandler.ServeHTTP) + finalHandler = authMiddleware(apiKey, shutdownHandler.ServeHTTP) } // Mount the handler at both /mcp/ and /mcp// diff --git a/internal/server/shutdown_test.go b/internal/server/shutdown_test.go new file mode 100644 index 000000000..9153e99d0 --- /dev/null +++ b/internal/server/shutdown_test.go @@ -0,0 +1,289 @@ +package server + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/githubnext/gh-aw-mcpg/internal/config" +) + +// TestShutdownBehavior_RoutedMode tests that MCP endpoints return 503 after /close in routed mode +func TestShutdownBehavior_RoutedMode(t *testing.T) { + tests := []struct { + name string + endpoint string + method string + expectStatusCode int + expectError string + }{ + { + name: "MCP endpoint rejected with 503 after shutdown", + endpoint: "/mcp/testserver", + method: "POST", + expectStatusCode: http.StatusServiceUnavailable, + expectError: "Gateway is shutting down", + }, + { + name: "MCP endpoint with trailing slash rejected with 503", + endpoint: "/mcp/testserver/", + method: "POST", + expectStatusCode: http.StatusServiceUnavailable, + expectError: "Gateway is shutting down", + }, + { + name: "Health endpoint still works during shutdown", + endpoint: "/health", + method: "GET", + expectStatusCode: http.StatusOK, + expectError: "", + }, + { + name: "Close endpoint returns 410 on subsequent calls", + endpoint: "/close", + method: "POST", + expectStatusCode: http.StatusGone, + expectError: "Gateway has already been closed", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create config with a test server + cfg := &config.Config{ + Servers: map[string]*config.ServerConfig{ + "testserver": { + Command: "echo", + Args: []string{}, + }, + }, + } + + // Create unified server + ctx := context.Background() + us, err := NewUnified(ctx, cfg) + require.NoError(t, err, "Failed to create unified server") + defer us.Close() + + // Enable test mode to prevent exit + us.SetTestMode(true) + + // Create HTTP server in routed mode + httpServer := CreateHTTPServerForRoutedMode(":0", us, "") + + // Call /close to initiate shutdown + closeReq := httptest.NewRequest("POST", "/close", nil) + closeW := httptest.NewRecorder() + httpServer.Handler.ServeHTTP(closeW, closeReq) + + // Verify close endpoint returned 200 + assert.Equal(t, http.StatusOK, closeW.Code, "Close endpoint should return 200 OK") + + // Now test the endpoint behavior after shutdown + req := httptest.NewRequest(tt.method, tt.endpoint, bytes.NewBufferString(`{}`)) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + + httpServer.Handler.ServeHTTP(w, req) + + // Verify status code + assert.Equal(t, tt.expectStatusCode, w.Code, "Unexpected status code") + + // Verify error message if expected + if tt.expectError != "" { + var response map[string]interface{} + err := json.NewDecoder(w.Body).Decode(&response) + require.NoError(t, err, "Failed to decode JSON response") + if errorMsg, ok := response["error"].(string); ok { + assert.Equal(t, tt.expectError, errorMsg, "Unexpected error message") + } else { + t.Errorf("Expected error field in response") + } + } + }) + } +} + +// TestShutdownBehavior_UnifiedMode tests that MCP endpoints return 503 after /close in unified mode +func TestShutdownBehavior_UnifiedMode(t *testing.T) { + tests := []struct { + name string + endpoint string + method string + expectStatusCode int + expectError string + }{ + { + name: "MCP endpoint rejected with 503 after shutdown", + endpoint: "/mcp", + method: "POST", + expectStatusCode: http.StatusServiceUnavailable, + expectError: "Gateway is shutting down", + }, + { + name: "MCP endpoint with trailing slash rejected with 503", + endpoint: "/mcp/", + method: "POST", + expectStatusCode: http.StatusServiceUnavailable, + expectError: "Gateway is shutting down", + }, + { + name: "Health endpoint still works during shutdown", + endpoint: "/health", + method: "GET", + expectStatusCode: http.StatusOK, + expectError: "", + }, + { + name: "Close endpoint returns 410 on subsequent calls", + endpoint: "/close", + method: "POST", + expectStatusCode: http.StatusGone, + expectError: "Gateway has already been closed", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create config + cfg := &config.Config{ + Servers: map[string]*config.ServerConfig{}, + } + + // Create unified server + ctx := context.Background() + us, err := NewUnified(ctx, cfg) + require.NoError(t, err, "Failed to create unified server") + defer us.Close() + + // Enable test mode to prevent exit + us.SetTestMode(true) + + // Create HTTP server in unified mode + httpServer := CreateHTTPServerForMCP(":0", us, "") + + // Call /close to initiate shutdown + closeReq := httptest.NewRequest("POST", "/close", nil) + closeW := httptest.NewRecorder() + httpServer.Handler.ServeHTTP(closeW, closeReq) + + // Verify close endpoint returned 200 + assert.Equal(t, http.StatusOK, closeW.Code, "Close endpoint should return 200 OK") + + // Now test the endpoint behavior after shutdown + req := httptest.NewRequest(tt.method, tt.endpoint, bytes.NewBufferString(`{}`)) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + + httpServer.Handler.ServeHTTP(w, req) + + // Verify status code + assert.Equal(t, tt.expectStatusCode, w.Code, "Unexpected status code") + + // Verify error message if expected + if tt.expectError != "" { + var response map[string]interface{} + err := json.NewDecoder(w.Body).Decode(&response) + require.NoError(t, err, "Failed to decode JSON response") + if errorMsg, ok := response["error"].(string); ok { + assert.Equal(t, tt.expectError, errorMsg, "Unexpected error message") + } else { + t.Errorf("Expected error field in response") + } + } + }) + } +} + +// TestShutdownBehavior_WithAuth tests shutdown behavior when API key auth is enabled +func TestShutdownBehavior_WithAuth(t *testing.T) { + // Create config + cfg := &config.Config{ + Servers: map[string]*config.ServerConfig{ + "testserver": { + Command: "echo", + Args: []string{}, + }, + }, + } + + // Create unified server + ctx := context.Background() + us, err := NewUnified(ctx, cfg) + require.NoError(t, err, "Failed to create unified server") + defer us.Close() + + // Enable test mode to prevent exit + us.SetTestMode(true) + + apiKey := "test-api-key" + + // Create HTTP server with auth in routed mode + httpServer := CreateHTTPServerForRoutedMode(":0", us, apiKey) + + // Call /close to initiate shutdown (with auth) + closeReq := httptest.NewRequest("POST", "/close", nil) + closeReq.Header.Set("Authorization", apiKey) + closeW := httptest.NewRecorder() + httpServer.Handler.ServeHTTP(closeW, closeReq) + + // Verify close endpoint returned 200 + assert.Equal(t, http.StatusOK, closeW.Code, "Close endpoint should return 200 OK") + + // Try to access MCP endpoint with valid auth after shutdown + req := httptest.NewRequest("POST", "/mcp/testserver", bytes.NewBufferString(`{}`)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", apiKey) + w := httptest.NewRecorder() + + httpServer.Handler.ServeHTTP(w, req) + + // Verify 503 is returned even with valid auth (shutdown takes precedence) + assert.Equal(t, http.StatusServiceUnavailable, w.Code, "MCP endpoint should return 503 even with valid auth") + + var response map[string]interface{} + err = json.NewDecoder(w.Body).Decode(&response) + require.NoError(t, err, "Failed to decode JSON response") + assert.Equal(t, "Gateway is shutting down", response["error"], "Unexpected error message") +} + +// TestShutdownBehavior_BeforeShutdown tests that endpoints work normally before shutdown +func TestShutdownBehavior_BeforeShutdown(t *testing.T) { + // Create config + cfg := &config.Config{ + Servers: map[string]*config.ServerConfig{ + "testserver": { + Command: "echo", + Args: []string{}, + }, + }, + } + + // Create unified server + ctx := context.Background() + us, err := NewUnified(ctx, cfg) + require.NoError(t, err, "Failed to create unified server") + defer us.Close() + + // Create HTTP server in routed mode + httpServer := CreateHTTPServerForRoutedMode(":0", us, "") + + // Try to access MCP endpoint BEFORE shutdown + // Note: Without actual backend, this will fail with different errors, + // but importantly it should NOT return 503 + req := httptest.NewRequest("POST", "/mcp/testserver", bytes.NewBufferString(`{}`)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "test-session-id") + w := httptest.NewRecorder() + + httpServer.Handler.ServeHTTP(w, req) + + // Verify NOT 503 (should be different error or success) + assert.NotEqual(t, http.StatusServiceUnavailable, w.Code, "MCP endpoint should not return 503 before shutdown") +} diff --git a/internal/server/transport.go b/internal/server/transport.go index eccbd8b5b..016f3a5a6 100644 --- a/internal/server/transport.go +++ b/internal/server/transport.go @@ -3,6 +3,7 @@ package server import ( "bytes" "context" + "encoding/json" "io" "log" "net/http" @@ -13,6 +14,24 @@ import ( var logTransport = logger.New("server:transport") +// rejectIfShutdownUnified is a middleware that rejects requests with HTTP 503 when gateway is shutting down +// Per spec 5.1.3: "Immediately reject any new RPC requests to /mcp/{server-name} endpoints with HTTP 503" +func rejectIfShutdownUnified(unifiedServer *UnifiedServer, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if unifiedServer.IsShutdown() { + logTransport.Printf("Rejecting request during shutdown: remote=%s, method=%s, path=%s", r.RemoteAddr, r.Method, r.URL.Path) + logger.LogWarn("shutdown", "Request rejected during shutdown, remote=%s, path=%s", r.RemoteAddr, r.URL.Path) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusServiceUnavailable) + json.NewEncoder(w).Encode(map[string]string{ + "error": "Gateway is shutting down", + }) + return + } + next.ServeHTTP(w, r) + }) +} + // HTTPTransport wraps the SDK's HTTP transport type HTTPTransport struct { Addr string @@ -120,10 +139,14 @@ func CreateHTTPServerForMCP(addr string, unifiedServer *UnifiedServer, apiKey st // Wrap SDK handler with detailed logging for JSON-RPC translation debugging loggedHandler := WithSDKLogging(streamableHandler, "unified") + // Apply shutdown check middleware (spec 5.1.3) + // This must come before auth to ensure shutdown takes precedence + shutdownHandler := rejectIfShutdownUnified(unifiedServer, loggedHandler) + // Apply auth middleware if API key is configured (spec 7.1) - finalHandler := loggedHandler + finalHandler := shutdownHandler if apiKey != "" { - finalHandler = authMiddleware(apiKey, loggedHandler.ServeHTTP) + finalHandler = authMiddleware(apiKey, shutdownHandler.ServeHTTP) } // Mount handler at /mcp endpoint (logging is done in the callback above) From d660fc4f4d6fd1295602c453a1858df74891215b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 15:20:49 +0000 Subject: [PATCH 3/5] Fix unused imports in launcher test file Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- internal/launcher/getorlaunch_stdio_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/launcher/getorlaunch_stdio_test.go b/internal/launcher/getorlaunch_stdio_test.go index 590fbe578..5f96b5bd7 100644 --- a/internal/launcher/getorlaunch_stdio_test.go +++ b/internal/launcher/getorlaunch_stdio_test.go @@ -2,14 +2,12 @@ package launcher import ( "context" - "os" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/githubnext/gh-aw-mcpg/internal/config" "github.com/githubnext/gh-aw-mcpg/internal/mcp" ) From 012b77b1ed09070fbe7ee7e0fbe23432cc0208d4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 15:24:48 +0000 Subject: [PATCH 4/5] Address code review feedback: use pre-formatted JSON and remove unused imports Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- internal/server/routed.go | 6 ++---- internal/server/transport.go | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/internal/server/routed.go b/internal/server/routed.go index 7148ea36b..415c8cd06 100644 --- a/internal/server/routed.go +++ b/internal/server/routed.go @@ -3,7 +3,6 @@ package server import ( "bytes" "context" - "encoding/json" "fmt" "io" "log" @@ -26,9 +25,8 @@ func rejectIfShutdown(unifiedServer *UnifiedServer, next http.Handler) http.Hand logger.LogWarn("shutdown", "Request rejected during shutdown, remote=%s, path=%s", r.RemoteAddr, r.URL.Path) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusServiceUnavailable) - json.NewEncoder(w).Encode(map[string]string{ - "error": "Gateway is shutting down", - }) + // Use pre-formatted JSON to avoid encoding errors + w.Write([]byte(`{"error":"Gateway is shutting down"}`)) return } next.ServeHTTP(w, r) diff --git a/internal/server/transport.go b/internal/server/transport.go index 016f3a5a6..654cae565 100644 --- a/internal/server/transport.go +++ b/internal/server/transport.go @@ -3,7 +3,6 @@ package server import ( "bytes" "context" - "encoding/json" "io" "log" "net/http" @@ -23,9 +22,8 @@ func rejectIfShutdownUnified(unifiedServer *UnifiedServer, next http.Handler) ht logger.LogWarn("shutdown", "Request rejected during shutdown, remote=%s, path=%s", r.RemoteAddr, r.URL.Path) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusServiceUnavailable) - json.NewEncoder(w).Encode(map[string]string{ - "error": "Gateway is shutting down", - }) + // Use pre-formatted JSON to avoid encoding errors + w.Write([]byte(`{"error":"Gateway is shutting down"}`)) return } next.ServeHTTP(w, r) From 30f1e3517d32355bdae331126d619002ebdb1fea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 15:27:01 +0000 Subject: [PATCH 5/5] Extract shutdown error JSON to shared constant to reduce duplication Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- internal/server/handlers.go | 4 ++++ internal/server/routed.go | 3 +-- internal/server/transport.go | 3 +-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/server/handlers.go b/internal/server/handlers.go index a5207e840..405220440 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -12,6 +12,10 @@ import ( var logHandlers = logger.New("server:handlers") +// shutdownErrorJSON is the pre-formatted JSON response for shutdown errors +// Used by middleware to return HTTP 503 during graceful shutdown (spec 5.1.3) +const shutdownErrorJSON = `{"error":"Gateway is shutting down"}` + // handleOAuthDiscovery returns a handler for OAuth discovery endpoint // Returns 404 since the gateway doesn't use OAuth func handleOAuthDiscovery() http.Handler { diff --git a/internal/server/routed.go b/internal/server/routed.go index 415c8cd06..4d7971f8a 100644 --- a/internal/server/routed.go +++ b/internal/server/routed.go @@ -25,8 +25,7 @@ func rejectIfShutdown(unifiedServer *UnifiedServer, next http.Handler) http.Hand logger.LogWarn("shutdown", "Request rejected during shutdown, remote=%s, path=%s", r.RemoteAddr, r.URL.Path) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusServiceUnavailable) - // Use pre-formatted JSON to avoid encoding errors - w.Write([]byte(`{"error":"Gateway is shutting down"}`)) + w.Write([]byte(shutdownErrorJSON)) return } next.ServeHTTP(w, r) diff --git a/internal/server/transport.go b/internal/server/transport.go index 654cae565..7d7ac7bfe 100644 --- a/internal/server/transport.go +++ b/internal/server/transport.go @@ -22,8 +22,7 @@ func rejectIfShutdownUnified(unifiedServer *UnifiedServer, next http.Handler) ht logger.LogWarn("shutdown", "Request rejected during shutdown, remote=%s, path=%s", r.RemoteAddr, r.URL.Path) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusServiceUnavailable) - // Use pre-formatted JSON to avoid encoding errors - w.Write([]byte(`{"error":"Gateway is shutting down"}`)) + w.Write([]byte(shutdownErrorJSON)) return } next.ServeHTTP(w, r)