Repository navigation
fix(api): answer upstream MCP, OAuth, and id-conflict failures with 4xx; keep PII keep-alive past the load balancer - #9210
Conversation
…xx; keep PII keep-alive past the load balancer - MCP tool discovery answers 422 (warn) when the registered server is unreachable or cooling down - MCP OAuth start answers 422 (warn) with the server's message for any authorization-server refusal - POST /api/workflows answers 409 when a caller-supplied id already exists - PII service keeps idle connections open longer than the load balancer's idle timeout - PII mask client replaces NUL with U+FFFD so the route does not reject the whole chunk
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
All reported issues were addressed across 11 files
Reply with feedback, questions, or to request a fix.
Turn on auto-fix | Re-trigger cubic
[Medium impact] The PR appears safe to merge; no actionable new issues were found. SummaryThe PR changes how MCP and workflow creation failures are reported, and improves PII request reliability.
No actionable new issues were found. No previous review threads were supplied. Tests were not run during this review. Reviews (2) · Last reviewed commit: "fix(mcp): treat tools/list timeouts and ..." · Reviewed by Greptile |
… server; fix test route contexts - Share the v2 discovery classification (timeout, connection, protocol, HTTP error) as isMcpServerUnavailableError and use it in the internal discover route - Restore the original POST refresh catch, which never sees per-server failures - Pass a route context in the new and existing oauth start and mask client tests; drop the oauth start test-types baseline entry
|
@greptile |
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
Summary
422and logs at warn, instead of502/503+ ERROR. It is the registered server's fault, not Sim's, and the request itself was valid.422is what the MCP route family already uses when an upstream can't complete the operation (the DCR-unsupported branch inoauth/start). The repo uses no424anywhere.400would blame the request. Other discovery failures keepcategorizeError's status and log at error only for 5xx.categorizeErroritself is unchanged, so tool execution (lib/internal/mcp/execute-tool.ts) and server refresh keep their mapping. The v2 tools endpoint has its own typed policy (documented503) and is untouched.McpClientconnect and ping failures log at warn.OAuthErrorfrom the authorization server answers422with the same surfaced message. Previously it was500+ ERROR, plus a second ERROR fromtimedStep. The refusal is caught inside the bounded step, like the redirect signal, sotimedStepno longer logs it either. This deliberately includesServerError: SDK 1.31.0parseErrorResponsemaps any error code it doesn't know (RFC 7591invalid_redirect_uriamong them) and any non-spec body toServerError, so filtering on the subclass would miss the refusals we actually get. Non-OAuth failures (DB, decryption) stay500with the generic message.23505onworkflow_pkey(a caller-supplied id that already exists) is now aconflict→409, not a500. It also no longer triggers a deduplicate-name retry, and it logs the id at warn. The name-index handling is unchanged.502. The entrypoint now sets--timeout-keep-alive 310(overridable withPII_TIMEOUT_KEEP_ALIVE), so the load balancer is always the side that closes.sanitizeForJsonb(NUL and unpaired surrogates → U+FFFD). The substitution is length-preserving, and text without either character is unchanged.Type of Change
Testing
app/api/mcp/tools/discover/route.test.ts(real handler): connection error → 422, cooldown → 422, a non-server error stays 500. Red on the old code (502/503).app/api/mcp/oauth/start/route.test.ts(real handler, real SDKregisterClientwith an injected fetch): unknown RFC 7591 code, a typed OAuth code, and a non-spec body all → 422 with the server message. A non-OAuth failure stays 500. Red on the old code (500 ×3).lib/guardrails/mask-client.test.ts: the client is driven through the real mask-batch route. A NUL string is masked instead of scrubbing its chunk, and NUL-free text reaches Presidio byte-identical. Red on the old code (2 strings scrubbed).lib/workflows/orchestration/create-workflow-with-state.integration.ts(real Postgres): a taken id → conflict and the original row is untouched, the deduplicate path with a taken id → conflict, and the exact-name conflict is unchanged. Red on the old code (2 failures,internal).uvicorn(defaults and env overrides).--timeout-keep-alivedefault of 5 confirmed in uvicorn 0.49.0.bun run lint,check:test-patterns,check:comment-hygiene. Existing MCP, workflow, and guardrails suites pass.Checklist
test-auditauthoring gate)