[GH-ISSUE #7035] Better Auth returns HTTP 200 for error responses instead of proper status codes (400, 401, 403) when used with Elysia.js #28032

Closed
opened 2026-04-17 19:23:22 -05:00 by GiteaMirror · 9 comments
Owner

Originally created by @elek453 on GitHub (Dec 28, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/7035

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

To Reproduce:

  1. Set up Better Auth with Elysia.js and email OTP authentication
  2. Mount Better Auth handler in Elysia routes
  3. Send an invalid OTP to the sign-in endpoint
  4. Check browser DevTools and server logs

Code to reproduce:

// auth.ts
import { betterAuth } from 'better-auth';
import { emailOTP } from 'better-auth/plugins';

export const auth = betterAuth({
  plugins: [
    emailOTP({
      otpLength: 6,
      expiresIn: 600,
    }),
  ],
  // ... other config
});

// routes.ts
import { Elysia } from 'elysia';
import { auth } from './auth';

export const authRoutes = new Elysia({ prefix: '/auth' })
  .post('/sign-in/email-otp', (c) => auth.handler(c.request));

// main.ts
import { Elysia } from 'elysia';
import { logger } from '@rasla/logify';

const app = new Elysia()
  .use(logger())
  .use(authRoutes)
  .listen(3000);

Steps:

  1. Request OTP via /auth/email-otp/send-verification-otp
  2. Submit invalid OTP to /auth/sign-in/email-otp
  3. Observe response

Current vs. Expected behavior

Current behavior:

  • Browser receives: {"code":"INVALID_OTP","message":"Invalid OTP"}
  • HTTP Status Code shown in browser: 200 OK (sometimes shows 400)
  • Server logger (@rasla/logify) logs: [200] 1386.5ms POST /api/v1/auth/sign-in/email-otp

Expected behavior:

  • Response body: {"code":"INVALID_OTP","message":"Invalid OTP"} (correct)
  • HTTP Status Code: 400 Bad Request (not 200)
  • Server logger should log: [400] 1386.5ms POST /api/v1/auth/sign-in/email-otp

The issue:
Better Auth is returning error responses with HTTP 200 status code instead of the appropriate error status codes (400 for validation errors, 401 for auth errors, 403 for forbidden, etc.).

This causes:

  1. Incorrect status codes in browser DevTools
  2. Incorrect status codes in server logs
  3. Client-side error handling becomes difficult (can't use response.ok or status code checks)

Note:

  • Tried using Elysia's onAfterHandle hook to intercept and modify status codes, but it doesn't trigger for Better Auth routes

What version of Better Auth are you using?

1.4.9

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.1.0: Mon Oct 20 19:34:05 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T6041",
    "release": "25.1.0",
    "cpuCount": 14,
    "cpuModel": "Apple M4 Max",
    "totalMemory": "36.00 GB",
    "freeMemory": "0.31 GB"
  },
  "node": {
    "version": "v20.19.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "10.8.2"
  },
  "frameworks": null,
  "databases": null,
  "betterAuth": {
    "version": "Unknown",
    "config": null
  }
}

Which area(s) are affected? (Select all that apply)

Backend, Client

Auth config (if applicable)

export const auth = betterAuth({
  basePath: '/api/v1/auth',
  database: drizzleAdapter(db, {
    provider: 'pg',
    schema: authSchema,
  }),
  emailAndPassword: {
    enabled: false,
  },
  plugins: [
    emailOTP({
      otpLength: 6,
      expiresIn: 1000,
      sendVerificationOnSignUp: true,
      disableSignUp: false,
      allowedAttempts: 3,
      async sendVerificationOTP({ email, otp, type }, epContext) {
        // Send email logic
      },
    }),
  ],
});

Additional context

This issue appears to be specific to how Better Auth returns error responses. The framework is returning a Response object with the error body but keeping the status as 200. This is problematic for:

  1. RESTful API conventions (errors should use appropriate 4xx/5xx status codes)
  2. Server-side logging and monitoring
  3. Client-side error handling patterns
  4. HTTP standards compliance

Is this intentional behavior in Better Auth, or is there a configuration option to enable proper HTTP status codes for error responses?

Originally created by @elek453 on GitHub (Dec 28, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/7035 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce **To Reproduce:** 1. Set up Better Auth with Elysia.js and email OTP authentication 2. Mount Better Auth handler in Elysia routes 3. Send an invalid OTP to the sign-in endpoint 4. Check browser DevTools and server logs **Code to reproduce:** ```typescript // auth.ts import { betterAuth } from 'better-auth'; import { emailOTP } from 'better-auth/plugins'; export const auth = betterAuth({ plugins: [ emailOTP({ otpLength: 6, expiresIn: 600, }), ], // ... other config }); // routes.ts import { Elysia } from 'elysia'; import { auth } from './auth'; export const authRoutes = new Elysia({ prefix: '/auth' }) .post('/sign-in/email-otp', (c) => auth.handler(c.request)); // main.ts import { Elysia } from 'elysia'; import { logger } from '@rasla/logify'; const app = new Elysia() .use(logger()) .use(authRoutes) .listen(3000); ``` **Steps:** 1. Request OTP via `/auth/email-otp/send-verification-otp` 2. Submit invalid OTP to `/auth/sign-in/email-otp` 3. Observe response ### Current vs. Expected behavior **Current behavior:** - Browser receives: `{"code":"INVALID_OTP","message":"Invalid OTP"}` - HTTP Status Code shown in browser: **200 OK** (sometimes shows 400) - Server logger (@rasla/logify) logs: `[200] 1386.5ms POST /api/v1/auth/sign-in/email-otp` **Expected behavior:** - Response body: `{"code":"INVALID_OTP","message":"Invalid OTP"}` ✅ (correct) - HTTP Status Code: **400 Bad Request** (not 200) - Server logger should log: `[400] 1386.5ms POST /api/v1/auth/sign-in/email-otp` **The issue:** Better Auth is returning error responses with HTTP 200 status code instead of the appropriate error status codes (400 for validation errors, 401 for auth errors, 403 for forbidden, etc.). This causes: 1. Incorrect status codes in browser DevTools 2. Incorrect status codes in server logs 3. Client-side error handling becomes difficult (can't use `response.ok` or status code checks) **Note:** - Tried using Elysia's `onAfterHandle` hook to intercept and modify status codes, but it doesn't trigger for Better Auth routes ### What version of Better Auth are you using? 1.4.9 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.1.0: Mon Oct 20 19:34:05 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T6041", "release": "25.1.0", "cpuCount": 14, "cpuModel": "Apple M4 Max", "totalMemory": "36.00 GB", "freeMemory": "0.31 GB" }, "node": { "version": "v20.19.0", "env": "development" }, "packageManager": { "name": "npm", "version": "10.8.2" }, "frameworks": null, "databases": null, "betterAuth": { "version": "Unknown", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Backend, Client ### Auth config (if applicable) ```typescript export const auth = betterAuth({ basePath: '/api/v1/auth', database: drizzleAdapter(db, { provider: 'pg', schema: authSchema, }), emailAndPassword: { enabled: false, }, plugins: [ emailOTP({ otpLength: 6, expiresIn: 1000, sendVerificationOnSignUp: true, disableSignUp: false, allowedAttempts: 3, async sendVerificationOTP({ email, otp, type }, epContext) { // Send email logic }, }), ], }); ``` ### Additional context This issue appears to be specific to how Better Auth returns error responses. The framework is returning a `Response` object with the error body but keeping the status as 200. This is problematic for: 1. RESTful API conventions (errors should use appropriate 4xx/5xx status codes) 2. Server-side logging and monitoring 3. Client-side error handling patterns 4. HTTP standards compliance Is this intentional behavior in Better Auth, or is there a configuration option to enable proper HTTP status codes for error responses?
GiteaMirror added the lockedbug labels 2026-04-17 19:23:22 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Dec 28, 2025):

It seems you've encountered an issue where Better Auth is returning an HTTP 200 status code for error responses instead of the appropriate status codes like 400 or 401 when an invalid OTP is submitted. This behavior is problematic as it complicates error handling both on the client and server side.

This issue might be an unintended behavior of Better Auth, and currently, there is no documented configuration option in the provided contexts that explicitly addresses this. It might be helpful to check if there are any updates or patches that address this issue or to explore how the error handling is implemented in the Better Auth codebase.

For your next steps, it might be beneficial to tag @bekacru or @ping__ to bring this concern directly to the Better Auth team, as they could provide more insight or a fix for this behavior.

Have you already tried reaching out to them, or do you need help figuring out what to ask?

If you need more help, tag @better-auth-agent in a comment so I can respond.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3694481019 --> @better-auth-agent[bot] commented on GitHub (Dec 28, 2025): It seems you've encountered an issue where Better Auth is returning an HTTP 200 status code for error responses instead of the appropriate status codes like 400 or 401 when an invalid OTP is submitted. This behavior is problematic as it complicates error handling both on the client and server side. This issue might be an unintended behavior of Better Auth, and currently, there is no documented configuration option in the provided contexts that explicitly addresses this. It might be helpful to check if there are any updates or patches that address this issue or to explore how the error handling is implemented in the Better Auth codebase. For your next steps, it might be beneficial to tag @bekacru or @ping__ to bring this concern directly to the Better Auth team, as they could provide more insight or a fix for this behavior. Have you already tried reaching out to them, or do you need help figuring out what to ask? _If you need more help, tag @better-auth-agent in a comment so I can respond._ <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) • [Discord](https://discord.gg/fG2XXEuQX3) • [GitHub](https://github.com/Skyvern-AI/Skyvern) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/fG2XXEuQX3) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/Skyvern-AI/Skyvern)
Author
Owner

@dosubot[bot] commented on GitHub (Dec 28, 2025):

Better Auth is designed to return the correct HTTP status codes for errors by throwing APIError instances (like 400, 401, 403) at the endpoint level. There’s no config option to globally toggle this—status codes are set by the framework and surfaced in the Response object returned by auth.handler (referencereference).

If you’re seeing HTTP 200 for errors in Elysia.js, it’s likely because of how the Elysia route is consuming or forwarding the Response from auth.handler, not a bug or limitation in Better Auth itself. The most robust pattern is to use .mount(auth.handler) directly on your Elysia app, which ensures the Response (including its status code) is forwarded as-is. If you use a custom handler, make sure you return the result of auth.handler(context.request) directly, without wrapping or modifying it (referencereference).

Here’s a minimal working example:

import { Elysia } from "elysia";
import { auth } from "./auth";

const app = new Elysia().mount(auth.handler).listen(3000);

If you need to restrict methods or add logic, use:

const betterAuthView = (context) => {
  if (["POST", "GET"].includes(context.request.method)) {
    return auth.handler(context.request);
  }
  context.status(405);
};

const app = new Elysia().all("/api/auth/*", betterAuthView).listen(3000);

Avoid using .post(..., c => auth.handler(c.request)) if you want status codes to propagate, as this can cause Elysia to treat the returned Response as a body and default to 200. Always return the Response object directly.

If you’re still seeing 200s, check for middleware or route ordering issues—no other middleware should intercept or modify requests before they reach Better Auth (reference).

For more details, see the Elysia integration docs.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3694482914 --> @dosubot[bot] commented on GitHub (Dec 28, 2025): <!-- Answer --> Better Auth is designed to return the correct HTTP status codes for errors by throwing APIError instances (like 400, 401, 403) at the endpoint level. There’s no config option to globally toggle this—status codes are set by the framework and surfaced in the Response object returned by auth.handler ([reference](https://github.com/better-auth/better-auth/issues/2165), [reference](https://github.com/better-auth/better-auth/issues/6436)). If you’re seeing HTTP 200 for errors in Elysia.js, it’s likely because of how the Elysia route is consuming or forwarding the Response from auth.handler, not a bug or limitation in Better Auth itself. The most robust pattern is to use `.mount(auth.handler)` directly on your Elysia app, which ensures the Response (including its status code) is forwarded as-is. If you use a custom handler, make sure you return the result of `auth.handler(context.request)` directly, without wrapping or modifying it ([reference](https://github.com/better-auth/better-auth/issues/3238), [reference](https://github.com/better-auth/better-auth/issues/3384)). Here’s a minimal working example: ```typescript import { Elysia } from "elysia"; import { auth } from "./auth"; const app = new Elysia().mount(auth.handler).listen(3000); ``` If you need to restrict methods or add logic, use: ```typescript const betterAuthView = (context) => { if (["POST", "GET"].includes(context.request.method)) { return auth.handler(context.request); } context.status(405); }; const app = new Elysia().all("/api/auth/*", betterAuthView).listen(3000); ``` Avoid using `.post(..., c => auth.handler(c.request))` if you want status codes to propagate, as this can cause Elysia to treat the returned Response as a body and default to 200. Always return the Response object directly. If you’re still seeing 200s, check for middleware or route ordering issues—no other middleware should intercept or modify requests before they reach Better Auth ([reference](https://github.com/better-auth/better-auth/issues/3384)). For more details, see the [Elysia integration docs](https://github.com/better-auth/better-auth/pull/213). <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/d0d892e3-023a-43ff-b51d-b24f96d6f905?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/d0d892e3-023a-43ff-b51d-b24f96d6f905?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/d0d892e3-023a-43ff-b51d-b24f96d6f905?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/d0d892e3-023a-43ff-b51d-b24f96d6f905?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/d0d892e3-023a-43ff-b51d-b24f96d6f905?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/d0d892e3-023a-43ff-b51d-b24f96d6f905?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/d0d892e3-023a-43ff-b51d-b24f96d6f905?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=Better%20Auth%20is%20designed%20to%20return%20the%20correct%20HTTP%20status%20codes%20for%20errors%20by%20throwing%20APIError%20instances%20%28like%20400%2C%20401%2C%20403%29%20at%20the%20endpoint%20level.%20There%E2%80%99s%20no%20config%20option%20to%20globally%20toggle%20this%E2%80%94status%20codes%20are%20set%20by%20the%20framework%20and%20surfaced%20in%20the%20Response%20object%20returned%20by%20auth.handler%C2%A0%28%5Breference%5D%28https%3A//github.com/better-auth/better-auth/issues/2165%29%2C%C2%A0%5Breference%5D%28https%3A//github.com/better-auth/better-auth/issues/6436%29%29.%0A%0AIf%20you%E2%80%99re%20seeing%20HTTP%20200%20for%20errors%20in%20Elysia.js%2C%20it%E2%80%99s%20likely%20because%20of%20how%20the%20Elysia%20route%20is%20consuming%20or%20forwarding%20the%20Response%20from%20auth.handler%2C%20not%20a%20bug%20or%20limitation%20in%20Better%20Auth%20itself.%20The%20most%20robust%20pattern%20is%20to%20use%20%60.mount%28auth.handler%29%60%20directly%20on%20your%20Elysia%20app%2C%20which%20ensures%20the%20Response%20%28including%20its%20status%20code%29%20is%20forwarded%20as-is.%20If%20you%20use%20a%20custom%20handler%2C%20make%20sure%20you%20return%20the%20result%20of%20%60auth.handler%28context.request%29%60%20directly%2C%20without%20wrapping%20or%20modifying%20it%20%28%5Breference%5D%28https%3A//github.com/better-auth/better-auth/issues/3238%29%2C%C2%A0%5Breference%5D%28https%3A//github.com/better-auth/better-auth/issues/3384%29%29.%0A%0AHere%E2%80%99s%20a%20minimal%20working%20example%3A%0A%0A%60%60%60typescript%0Aimport%20%7B%20Elysia%20%7D%20from%20%22elysia%22%3B%0Aimport%20%7B%20auth%20%7D%20from%20%22./auth%22%3B%0A%0Aconst%20app%20%3D%20new%20Elysia%28%29.mount%28auth.handler%29.listen%283000%29%3B%0A%60%60%60%0A%0AIf%20you%20need%20to%20restrict%20methods%20or%20add%20logic%2C%20use%3A%0A%0A%60%60%60typescript%0Aconst%20betterAuthView%20%3D%20%28context%29%20%3D%3E%20%7B%0A%20%20if%20%28%5B%22POST%22%2C%20%22GET%22%5D.includes%28context.request.method%29%29%20%7B%0A%20%20%20%20return%20auth.handler%28context.request%29%3B%0A%20%20%7D%0A%20%20context.status%28405%29%3B%0A%7D%3B%0A%0Aconst%20app%20%3D%20new%20Elysia%28%29.all%28%22/api/auth/%2A%22%2C%20betterAuthView%29.listen%283000%29%3B%0A%60%60%60%0A%0AAvoid%20using%20%60.post%28...%2C%20c%20%3D%3E%20auth.handler%28c.request%29%29%60%20if%20you%20want%20status%20codes%20to%20propagate%2C%20as%20this%20can%20cause%20Elysia%20to%20treat%20the%20returned%20Response%20as%20a%20body%20and%20default%20to%20200.%20Always%20return%20the%20Response%20object%20directly.%0A%0AIf%20you%E2%80%99re%20still%20seeing%20200s%2C%20check%20for%20middleware%20or%20route%20ordering%20issues%E2%80%94no%20other%20middleware%20should%20intercept%20or%20modify%20requests%20before%20they%20reach%20Better%20Auth%20%28%5Breference%5D%28https%3A//github.com/better-auth/better-auth/issues/3384%29%29.%0A%0AFor%20more%20details%2C%20see%20the%20%5BElysia%20integration%20docs%5D%28https%3A//github.com/better-auth/better-auth/pull/213%29.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/7035)
Author
Owner

@dagimkassagebrie commented on GitHub (Dec 28, 2025):

Looking at the tests, I see that auth.handler returns Response objects with correct status codes (see to-auth-endpoints.test.ts:899). The conversion from thrown APIError to Response seems to happen inside better-call's createRouter, which this codebase uses but doesn't contain. I think what is happening is when you wrap auth.handler(c.request) in a route handler like .post(...), Elysia treats the returned Response as a body value and loses the status code. Using .mount(auth.handler) forwards the Response directly and that preserves status codes. @better-auth-agent

<!-- gh-comment-id:3694513474 --> @dagimkassagebrie commented on GitHub (Dec 28, 2025): Looking at the tests, I see that auth.handler returns Response objects with correct status codes (see to-auth-endpoints.test.ts:899). The conversion from thrown APIError to Response seems to happen inside better-call's createRouter, which this codebase uses but doesn't contain. I think what is happening is when you wrap auth.handler(c.request) in a route handler like .post(...), Elysia treats the returned Response as a body value and loses the status code. Using .mount(auth.handler) forwards the Response directly and that preserves status codes. @better-auth-agent
Author
Owner

@better-auth-agent[bot] commented on GitHub (Dec 28, 2025):

It appears that you're experiencing an issue where Better Auth returns HTTP status code 200 for error responses instead of the appropriate error codes like 400 or 401 when used with Elysia.js.

This behavior can be problematic as it leads to confusion in client-side error handling, server logs, and adherence to RESTful API conventions. Based on the context from your query and existing issues, the problem seems to arise from how Elysia handles the returned Response object, which gets interpreted as a regular body value instead of preserving the intended status code when wrapped inside the .post method.

To address this, you can consider using the .mount(auth.handler) method instead, as it is suggested that this approach forwards the Response directly and maintains the correct status code.

If you have tried this and are still encountering issues, it might be a good idea to tag @bekacru and @ping__ for further insights or to confirm whether this behavior is intentional within the framework and if any configuration changes can be made to enable proper HTTP status codes for error responses.

How are you currently handling error responses on the client side? Would love to hear more about your implementation!

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3694517606 --> @better-auth-agent[bot] commented on GitHub (Dec 28, 2025): It appears that you're experiencing an issue where Better Auth returns HTTP status code 200 for error responses instead of the appropriate error codes like 400 or 401 when used with Elysia.js. This behavior can be problematic as it leads to confusion in client-side error handling, server logs, and adherence to RESTful API conventions. Based on the context from your query and existing issues, the problem seems to arise from how Elysia handles the returned `Response` object, which gets interpreted as a regular body value instead of preserving the intended status code when wrapped inside the `.post` method. To address this, you can consider using the `.mount(auth.handler)` method instead, as it is suggested that this approach forwards the `Response` directly and maintains the correct status code. If you have tried this and are still encountering issues, it might be a good idea to tag @bekacru and @ping__ for further insights or to confirm whether this behavior is intentional within the framework and if any configuration changes can be made to enable proper HTTP status codes for error responses. How are you currently handling error responses on the client side? Would love to hear more about your implementation! <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) • [Discord](https://discord.gg/fG2XXEuQX3) • [GitHub](https://github.com/Skyvern-AI/Skyvern) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/fG2XXEuQX3) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/Skyvern-AI/Skyvern)
Author
Owner

@sohamingle commented on GitHub (Jan 5, 2026):

I was able to reproduce this.

It looks like Better Auth correctly returns a Response with status: 400, but Elysia does not propagate that status to its internal set.status, so the request is ultimately logged as 200.

In other words, the error status exists on the returned Response, but Elysia doesn’t apply it to the outgoing HTTP status.

Client-side behavior

Step 1: Sending verification OTP...
  Status: 200
  Body: {
  "success": true
}

Step 2: Attempting sign-in with INVALID OTP...        
  Status: 400
  Body: {"code":"INVALID_OTP","message":"Invalid OTP"}

Server Logs (Elysia)

[200] POST /api/auth/sign-in/email-otp
Response (46 bytes) {
  ok: false,
  status: 400,
  statusText: "BAD_REQUEST"
}

Observation

  • The Response object clearly has status: 400
  • However, Elysia logs with 200
  • This suggests the status is not being transferred from the returned Response to set.status
  • This may require explicitly syncing response.status → set.status in the logs.
<!-- gh-comment-id:3710801500 --> @sohamingle commented on GitHub (Jan 5, 2026): I was able to reproduce this. It looks like Better Auth correctly returns a `Response` with `status: 400`, but Elysia does not propagate that status to its internal `set.status`, so the request is ultimately logged as `200`. In other words, the error status exists on the returned Response, but Elysia doesn’t apply it to the outgoing HTTP status. #### Client-side behavior ``` Step 1: Sending verification OTP... Status: 200 Body: { "success": true } Step 2: Attempting sign-in with INVALID OTP... Status: 400 Body: {"code":"INVALID_OTP","message":"Invalid OTP"} ``` #### Server Logs (Elysia) ``` [200] POST /api/auth/sign-in/email-otp Response (46 bytes) { ok: false, status: 400, statusText: "BAD_REQUEST" } ``` #### Observation - The `Response` object clearly has `status: 400` - However, Elysia logs with `200` - This suggests the status is not being transferred from the returned `Response` to `set.status` - This may require explicitly syncing `response.status → set.status` in the logs.
Author
Owner

@anflext commented on GitHub (Jan 26, 2026):

I have find the base problem at the lib better-call src/adapters/node/request.ts (line 156) use res.writeHead(response.status) to set statusCode and that not write to stream. I hack the npm package use res.statusCode = response.status; That work. The statusCode 400 send to client.

Image

issue7035_better-call.patch

I use express/vite/react. And my find problem is all throw APIError response the statusCode is 200.

@sohamingle Can you review and verify the patch code and merge to lib better-call?

<!-- gh-comment-id:3797990575 --> @anflext commented on GitHub (Jan 26, 2026): I have find the base problem at the lib **better-call** _src/adapters/node/request.ts_ (line 156) use res.writeHead(response.status) to set statusCode and that not write to stream. I hack the npm package use res.statusCode = response.status; That work. The statusCode 400 send to client. <img width="738" height="310" alt="Image" src="https://github.com/user-attachments/assets/52881db8-ec53-4974-af27-776a52953ebb" /> [issue7035_better-call.patch](https://github.com/user-attachments/files/24853730/issue7035_better-call.patch) I use express/vite/react. And my find problem is all throw APIError response the statusCode is 200. @sohamingle Can you review and verify the patch code and merge to lib better-call?
Author
Owner

@bytaesu commented on GitHub (Jan 27, 2026):

@anflext I just opened PR for better-call 🙂

<!-- gh-comment-id:3807294753 --> @bytaesu commented on GitHub (Jan 27, 2026): @anflext I just opened PR for `better-call` 🙂
Author
Owner

@bytaesu commented on GitHub (Jan 27, 2026):

This issue seems to be a timing problem when reading the status code inside the middleware.

I spotted the same behavior in better-call nodeHandler and submitted a PR to fix it.

Unfortunately, since this involves external middleware that we don't control, we can't directly fix this issue.

Feel free to mention me if you have further questions!

<!-- gh-comment-id:3807316624 --> @bytaesu commented on GitHub (Jan 27, 2026): This issue seems to be a timing problem when reading the status code inside the middleware. I spotted the same behavior in `better-call` nodeHandler and submitted a PR to fix it. Unfortunately, since this involves external middleware that we don't control, we can't directly fix this issue. Feel free to mention me if you have further questions!
Author
Owner

@anflext commented on GitHub (Jan 28, 2026):

💯 Excellent! I will not need hack lib better-call code in local.

<!-- gh-comment-id:3814503393 --> @anflext commented on GitHub (Jan 28, 2026): 💯 Excellent! I will not need hack lib better-call code in local.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#28032