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) {