From 0db81ac4e46989534823316f5754656d8bc567ad Mon Sep 17 00:00:00 2001 From: Manas Srivastava Date: Sat, 30 May 2026 14:11:29 +0530 Subject: [PATCH] fix(api): /auth/github + /auth/google/callback agent-actionable missing-field copy (BUG-API-184) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit POST /auth/github body `{}` used to return: error: "missing_code" message: "code field is required" Accurate, but agent-unhelpful. An LLM hitting this 4xx needed to either know the GitHub OAuth code-exchange contract already or open /openapi.json to learn what to send. Stamp the full request-body shape in the message so the agent can retry without a second round trip: "Request body is missing the required `code` field. POST `{\"code\": \"\"}` after exchanging your OAuth authorization code at GitHub." Per rule 16 (enumerate ALL call sites), the same `"code field is required"` string lived on /auth/google/callback — mirrored the same upgrade there, plus the adjacent `missing_redirect_uri` message. Error codes (`missing_code`, `missing_redirect_uri`) stay stable so agents branching on `.error` are unaffected. Coverage block: Symptom: /auth/github POST nofields 400 — message should list required fields (BUG-API-184) Enumeration: rg -F 'code field is required' internal/ (2 sites) rg -F 'redirect_uri field is required' internal/ (1 site) rg -F 'missing_code' internal/handlers/ (2 emit sites in auth.go) Sites found: 2 missing_code emits + 1 missing_redirect_uri emit Sites touched: 3 Coverage test: TestAuth_GitHub_MissingCodeAndBadBody asserts message contains both ``code`` (the field name) and `{"code":` (the body shape). Error code unchanged so back-compat tests stay green. Live verified: pending auto-deploy + curl -s -X POST -H 'Content-Type: application/json' -d '{}' \ https://api.instanode.dev/auth/github | jq .message Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/handlers/auth.go | 25 ++++++++++++++++--- internal/handlers/auth_oauth_coverage_test.go | 16 ++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/internal/handlers/auth.go b/internal/handlers/auth.go index ae578fba..c61460b3 100644 --- a/internal/handlers/auth.go +++ b/internal/handlers/auth.go @@ -408,7 +408,18 @@ func (h *AuthHandler) GitHub(c *fiber.Ctx) error { return respondError(c, fiber.StatusBadRequest, "invalid_body", "Request body must be valid JSON") } if body.Code == "" { - return respondError(c, fiber.StatusBadRequest, "missing_code", "code field is required") + // BUG-API-184 (QA 2026-05-29): the error message used to read + // "code field is required" — accurate but agent-unhelpful. An + // LLM agent that 4xx'd here needed to either know the GitHub + // OAuth code-exchange contract already or open /openapi.json to + // learn what to send. Stamp the full required-fields list inline + // (the request body has only one field today, but the contract + // shape is what the agent needs — `{ "code": "" }`) + // so the message is a self-contained instruction. Keep the + // `missing_code` error code stable for back-compat (agents + // branching on .error stay green). + return respondError(c, fiber.StatusBadRequest, "missing_code", + "Request body is missing the required `code` field. POST `{\"code\": \"\"}` after exchanging your OAuth authorization code at GitHub.") } if h.cfg.GitHubClientID == "" || h.cfg.GitHubClientSecret == "" { @@ -514,10 +525,18 @@ func (h *AuthHandler) GoogleCallback(c *fiber.Ctx) error { return respondError(c, fiber.StatusBadRequest, "invalid_body", "Request body must be valid JSON") } if body.Code == "" { - return respondError(c, fiber.StatusBadRequest, "missing_code", "code field is required") + // BUG-API-184 (QA 2026-05-29): mirror the GitHub surface so the + // agent-actionable message names BOTH fields the Google callback + // expects (code + redirect_uri). Same code stays for back-compat; + // only the human/agent-facing message gains the shape hint. + return respondError(c, fiber.StatusBadRequest, "missing_code", + "Request body is missing the required `code` field. POST `{\"code\": \"\", \"redirect_uri\": \"\"}` after exchanging your OAuth authorization code at Google.") } if body.RedirectURI == "" { - return respondError(c, fiber.StatusBadRequest, "missing_redirect_uri", "redirect_uri field is required") + // BUG-API-184: same treatment — name the field and the body shape + // so an LLM hitting this 4xx has everything it needs to retry. + return respondError(c, fiber.StatusBadRequest, "missing_redirect_uri", + "Request body is missing the required `redirect_uri` field. POST `{\"code\": \"\", \"redirect_uri\": \"\"}` matching the redirect_uri you registered with Google.") } accessToken, err := exchangeGoogleAuthorizationCode(c.Context(), h.cfg.GoogleClientID, h.cfg.GoogleClientSecret, body.Code, body.RedirectURI) diff --git a/internal/handlers/auth_oauth_coverage_test.go b/internal/handlers/auth_oauth_coverage_test.go index b9e622d7..b04fada3 100644 --- a/internal/handlers/auth_oauth_coverage_test.go +++ b/internal/handlers/auth_oauth_coverage_test.go @@ -248,7 +248,23 @@ func TestAuth_GitHub_MissingCodeAndBadBody(t *testing.T) { r2 := oauthPostJSON(t, app, "/auth/github", `{}`) assert.Equal(t, http.StatusBadRequest, r2.StatusCode) + // BUG-API-184 (QA 2026-05-29): the missing-code message used to read + // "code field is required" — accurate but agent-unhelpful. Pin the + // agent-actionable copy: the message MUST name the field AND the + // expected request body shape so an LLM hitting this 4xx has + // everything it needs to retry without opening the OpenAPI spec. + var env struct { + Error string `json:"error"` + Message string `json:"message"` + } + require.NoError(t, json.NewDecoder(r2.Body).Decode(&env)) r2.Body.Close() + assert.Equal(t, "missing_code", env.Error, + "BUG-API-184: error code MUST stay missing_code for back-compat") + assert.Contains(t, env.Message, "`code`", + "BUG-API-184: message must name the required field (`code`); got %q", env.Message) + assert.Contains(t, env.Message, `{"code":`, + "BUG-API-184: message must show the request body shape `{\"code\": \"<...>\"}`; got %q", env.Message) } func TestAuth_GitHub_NotConfigured(t *testing.T) {