After hook is not called when route handler throws redirect since upgrading to 1.4 #2448

Closed
opened 2026-03-13 09:55:24 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @Zollerboy1 on GitHub (Dec 3, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Add social login provider (e.g. GitHub) and after hook for the /callback/:id path:

export const auth = betterAuth({
  // ...
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID,
      clientSecret: process.env.GITHUB_CLIENT_SECRET
    }
  },
  hooks: {
    after: createAuthMiddleware(async (ctx) => {
      if (ctx.path === '/callback/:id' && ctx.params.id === 'github' && ctx.context.newSession) {
        // do something after a social login
        console.log("User logged in with GitHub");
      }
    })
  },
  // ...
});

Call social sign in from client:

const res = await authClient.signIn.social({
  provider: "github",
  callbackURL: "/"
});

Current vs. Expected behavior

The code in the after hook should run even if the route handler throws a redirect error. Since upgrading from 1.3.32 to 1.4.4, that's not happening anymore. After some debugging, it seems that the redirect error isn't catched and returned as a response here, but instead gets rethrown.

What version of Better Auth are you using?

1.4.4

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 24.6.0: Mon Jul 14 11:30:40 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T6041",
    "release": "24.6.0",
    "cpuCount": 12,
    "cpuModel": "Apple M4 Pro",
    "totalMemory": "24.00 GB",
    "freeMemory": "0.08 GB"
  },
  "node": {
    "version": "v24.10.0",
    "env": "development"
  },
  "packageManager": {
    "name": "pnpm",
    "version": "10.17.0"
  },
  "frameworks": [
    {
      "name": "svelte",
      "version": "^5.38.3"
    },
    {
      "name": "@sveltejs/kit",
      "version": "^2.48.5"
    }
  ],
  "databases": [
    {
      "name": "postgres",
      "version": "^3.4.7"
    },
    {
      "name": "drizzle",
      "version": "^0.44.5"
    }
  ],
  "betterAuth": {
    "version": "^1.4.4",
    "config": {
      "trustedOrigins": [
        "https://appleid.apple.com"
      ],
      "telemetry": {
        "enabled": false
      },
      "session": {
        "cookieCache": {
          "enabled": true,
          "maxAge": 300
        }
      },
      "user": {
        "deleteUser": {
          "enabled": true
        }
      },
      "account": {
        "accountLinking": {
          "enabled": true,
          "allowDifferentEmails": true
        }
      },
      "emailAndPassword": {
        "enabled": true,
        "requireEmailVerification": true
      },
      "emailVerification": {
        "sendOnSignUp": true,
        "autoSignInAfterVerification": true
      },
      "socialProviders": {
        "apple": {},
        "github": {},
        "google": {},
        "microsoft": {
          "authority": "https://login.microsoftonline.com"
        }
      },
      "plugins": [
        {
          "name": "admin",
          "config": {
            "id": "admin",
            "hooks": {
              "after": [
                {}
              ]
            },
            "endpoints": {},
            "$ERROR_CODES": {
              "FAILED_TO_CREATE_USER": "Failed to create user",
              "USER_ALREADY_EXISTS": "User already exists.",
              "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL": "User already exists. Use another email.",
              "YOU_CANNOT_BAN_YOURSELF": "You cannot ban yourself",
              "YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE": "You are not allowed to change users role",
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS": "You are not allowed to create users",
              "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS": "You are not allowed to list users",
              "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS": "You are not allowed to list users sessions",
              "YOU_ARE_NOT_ALLOWED_TO_BAN_USERS": "You are not allowed to ban users",
              "YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS": "You are not allowed to impersonate users",
              "YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS": "You are not allowed to revoke users sessions",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS": "You are not allowed to delete users",
              "YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD": "[REDACTED]",
              "BANNED_USER": "You have been banned from this application",
              "YOU_ARE_NOT_ALLOWED_TO_GET_USER": "You are not allowed to get user",
              "NO_DATA_TO_UPDATE": "No data to update",
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS": "You are not allowed to update users",
              "YOU_CANNOT_REMOVE_YOURSELF": "You cannot remove yourself",
              "YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE": "You are not allowed to set a non-existent role value"
            },
            "schema": {
              "user": {
                "fields": {
                  "role": {
                    "type": "string",
                    "required": false,
                    "input": false
                  },
                  "banned": {
                    "type": "boolean",
                    "defaultValue": false,
                    "required": false,
                    "input": false
                  },
                  "banReason": {
                    "type": "string",
                    "required": false,
                    "input": false
                  },
                  "banExpires": {
                    "type": "date",
                    "required": false,
                    "input": false
                  }
                }
              },
              "session": {
                "fields": {
                  "impersonatedBy": {
                    "type": "string",
                    "required": false
                  }
                }
              }
            }
          }
        },
        {
          "name": "passkey",
          "config": {
            "id": "passkey",
            "endpoints": {},
            "schema": {
              "passkey": {
                "fields": {
                  "name": {
                    "type": "string",
                    "required": false
                  },
                  "publicKey": {
                    "type": "string",
                    "required": true
                  },
                  "userId": {
                    "type": "string",
                    "references": {
                      "model": "user",
                      "field": "id"
                    },
                    "required": true,
                    "index": true
                  },
                  "credentialID": {
                    "type": "string",
                    "required": true,
                    "index": true
                  },
                  "counter": {
                    "type": "number",
                    "required": true
                  },
                  "deviceType": {
                    "type": "string",
                    "required": true
                  },
                  "backedUp": {
                    "type": "boolean",
                    "required": true
                  },
                  "transports": {
                    "type": "string",
                    "required": false
                  },
                  "createdAt": {
                    "type": "date",
                    "required": false
                  },
                  "aaguid": {
                    "type": "string",
                    "required": false
                  }
                }
              }
            },
            "$ERROR_CODES": {
              "CHALLENGE_NOT_FOUND": "Challenge not found",
              "YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY": "You are not allowed to register this passkey",
              "FAILED_TO_VERIFY_REGISTRATION": "Failed to verify registration",
              "PASSKEY_NOT_FOUND": "Passkey not found",
              "AUTHENTICATION_FAILED": "Authentication failed",
              "UNABLE_TO_CREATE_SESSION": "Unable to create session",
              "FAILED_TO_UPDATE_PASSKEY": "Failed to update passkey"
            }
          }
        },
        {
          "name": "anonymous",
          "config": {
            "id": "anonymous",
            "endpoints": {},
            "hooks": {
              "after": [
                {}
              ]
            },
            "schema": {
              "user": {
                "fields": {
                  "isAnonymous": {
                    "type": "boolean",
                    "required": false,
                    "input": false,
                    "defaultValue": false
                  }
                }
              }
            },
            "$ERROR_CODES": {
              "INVALID_EMAIL_FORMAT": "Email was not generated in a valid format",
              "FAILED_TO_CREATE_USER": "Failed to create user",
              "COULD_NOT_CREATE_SESSION": "Could not create session",
              "ANONYMOUS_USERS_CANNOT_SIGN_IN_AGAIN_ANONYMOUSLY": "Anonymous users cannot sign in again anonymously"
            }
          }
        },
        {
          "name": "sveltekit-cookies",
          "config": {
            "id": "sveltekit-cookies",
            "hooks": {
              "after": [
                {}
              ]
            }
          }
        }
      ],
      "hooks": {},
      "logger": {},
      "experimental": {
        "joins": true
      }
    }
  }
}

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

Backend

Auth config (if applicable)


Additional context

No response

Originally created by @Zollerboy1 on GitHub (Dec 3, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Add social login provider (e.g. GitHub) and after hook for the `/callback/:id` path: ```ts export const auth = betterAuth({ // ... socialProviders: { github: { clientId: process.env.GITHUB_CLIENT_ID, clientSecret: process.env.GITHUB_CLIENT_SECRET } }, hooks: { after: createAuthMiddleware(async (ctx) => { if (ctx.path === '/callback/:id' && ctx.params.id === 'github' && ctx.context.newSession) { // do something after a social login console.log("User logged in with GitHub"); } }) }, // ... }); ``` Call social sign in from client: ```ts const res = await authClient.signIn.social({ provider: "github", callbackURL: "/" }); ``` ### Current vs. Expected behavior The code in the after hook should run even if the route handler throws a redirect error. Since upgrading from 1.3.32 to 1.4.4, that's not happening anymore. After some debugging, it seems that the redirect error isn't catched and returned as a response [here](https://github.com/better-auth/better-auth/blob/58e886c2029c79ec75d0bdf67934e525925d9767/packages/better-auth/src/api/to-auth-endpoints.ts#L114), but instead gets rethrown. ### What version of Better Auth are you using? 1.4.4 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 24.6.0: Mon Jul 14 11:30:40 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T6041", "release": "24.6.0", "cpuCount": 12, "cpuModel": "Apple M4 Pro", "totalMemory": "24.00 GB", "freeMemory": "0.08 GB" }, "node": { "version": "v24.10.0", "env": "development" }, "packageManager": { "name": "pnpm", "version": "10.17.0" }, "frameworks": [ { "name": "svelte", "version": "^5.38.3" }, { "name": "@sveltejs/kit", "version": "^2.48.5" } ], "databases": [ { "name": "postgres", "version": "^3.4.7" }, { "name": "drizzle", "version": "^0.44.5" } ], "betterAuth": { "version": "^1.4.4", "config": { "trustedOrigins": [ "https://appleid.apple.com" ], "telemetry": { "enabled": false }, "session": { "cookieCache": { "enabled": true, "maxAge": 300 } }, "user": { "deleteUser": { "enabled": true } }, "account": { "accountLinking": { "enabled": true, "allowDifferentEmails": true } }, "emailAndPassword": { "enabled": true, "requireEmailVerification": true }, "emailVerification": { "sendOnSignUp": true, "autoSignInAfterVerification": true }, "socialProviders": { "apple": {}, "github": {}, "google": {}, "microsoft": { "authority": "https://login.microsoftonline.com" } }, "plugins": [ { "name": "admin", "config": { "id": "admin", "hooks": { "after": [ {} ] }, "endpoints": {}, "$ERROR_CODES": { "FAILED_TO_CREATE_USER": "Failed to create user", "USER_ALREADY_EXISTS": "User already exists.", "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL": "User already exists. Use another email.", "YOU_CANNOT_BAN_YOURSELF": "You cannot ban yourself", "YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE": "You are not allowed to change users role", "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS": "You are not allowed to create users", "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS": "You are not allowed to list users", "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS": "You are not allowed to list users sessions", "YOU_ARE_NOT_ALLOWED_TO_BAN_USERS": "You are not allowed to ban users", "YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS": "You are not allowed to impersonate users", "YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS": "You are not allowed to revoke users sessions", "YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS": "You are not allowed to delete users", "YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD": "[REDACTED]", "BANNED_USER": "You have been banned from this application", "YOU_ARE_NOT_ALLOWED_TO_GET_USER": "You are not allowed to get user", "NO_DATA_TO_UPDATE": "No data to update", "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS": "You are not allowed to update users", "YOU_CANNOT_REMOVE_YOURSELF": "You cannot remove yourself", "YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE": "You are not allowed to set a non-existent role value" }, "schema": { "user": { "fields": { "role": { "type": "string", "required": false, "input": false }, "banned": { "type": "boolean", "defaultValue": false, "required": false, "input": false }, "banReason": { "type": "string", "required": false, "input": false }, "banExpires": { "type": "date", "required": false, "input": false } } }, "session": { "fields": { "impersonatedBy": { "type": "string", "required": false } } } } } }, { "name": "passkey", "config": { "id": "passkey", "endpoints": {}, "schema": { "passkey": { "fields": { "name": { "type": "string", "required": false }, "publicKey": { "type": "string", "required": true }, "userId": { "type": "string", "references": { "model": "user", "field": "id" }, "required": true, "index": true }, "credentialID": { "type": "string", "required": true, "index": true }, "counter": { "type": "number", "required": true }, "deviceType": { "type": "string", "required": true }, "backedUp": { "type": "boolean", "required": true }, "transports": { "type": "string", "required": false }, "createdAt": { "type": "date", "required": false }, "aaguid": { "type": "string", "required": false } } } }, "$ERROR_CODES": { "CHALLENGE_NOT_FOUND": "Challenge not found", "YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY": "You are not allowed to register this passkey", "FAILED_TO_VERIFY_REGISTRATION": "Failed to verify registration", "PASSKEY_NOT_FOUND": "Passkey not found", "AUTHENTICATION_FAILED": "Authentication failed", "UNABLE_TO_CREATE_SESSION": "Unable to create session", "FAILED_TO_UPDATE_PASSKEY": "Failed to update passkey" } } }, { "name": "anonymous", "config": { "id": "anonymous", "endpoints": {}, "hooks": { "after": [ {} ] }, "schema": { "user": { "fields": { "isAnonymous": { "type": "boolean", "required": false, "input": false, "defaultValue": false } } } }, "$ERROR_CODES": { "INVALID_EMAIL_FORMAT": "Email was not generated in a valid format", "FAILED_TO_CREATE_USER": "Failed to create user", "COULD_NOT_CREATE_SESSION": "Could not create session", "ANONYMOUS_USERS_CANNOT_SIGN_IN_AGAIN_ANONYMOUSLY": "Anonymous users cannot sign in again anonymously" } } }, { "name": "sveltekit-cookies", "config": { "id": "sveltekit-cookies", "hooks": { "after": [ {} ] } } } ], "hooks": {}, "logger": {}, "experimental": { "joins": true } } } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript ``` ### Additional context _No response_
GiteaMirror added the bug label 2026-03-13 09:55:24 -05:00
Author
Owner

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

It appears that in 1.4.4 the redirect error is no longer caught as before, which stops the after hook from running. One potential next step is to wrap your route handler or hook logic in a try/catch to capture the redirect error explicitly and then execute any post-redirect logic you require. You might also consider testing with 1.3.32 to verify that the behavior changes—this can help pinpoint whether the change was intentional in 1.4.4 or an unintended regression. The linked code indicates that the redirect error now gets rethrown instead of being returned [1], so adjusting your error handling might be necessary. Tag @better-auth-agent if you would like an answer from the Agent. What modifications have you tried so far to work around this issue?

If you need more help, tag @Skyvern in a comment so I can respond.

DiagramDiscordGitHub

Diagram Join Star

@better-auth-agent[bot] commented on GitHub (Dec 3, 2025): It appears that in 1.4.4 the redirect error is no longer caught as before, which stops the after hook from running. One potential next step is to wrap your route handler or hook logic in a try/catch to capture the redirect error explicitly and then execute any post-redirect logic you require. You might also consider testing with 1.3.32 to verify that the behavior changes—this can help pinpoint whether the change was intentional in 1.4.4 or an unintended regression. The linked code indicates that the redirect error now gets rethrown instead of being returned [[1]](https://github.com/better-auth/better-auth/issues/6446), so adjusting your error handling might be necessary. Tag @better-auth-agent if you would like an answer from the Agent. What modifications have you tried so far to work around this issue? _If you need more help, tag @Skyvern 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 3, 2025):

This is a known limitation in Better Auth: after hooks are only called if the endpoint handler completes successfully or throws an APIError. If a redirect error is thrown (as happens in social login callbacks on error), it is not caught as an APIError and the after hooks are skipped. This is by design and not a regression in 1.4.4—previous versions also skipped after hooks on redirect errors, though the error handling code has evolved recently. There is currently no supported workaround to force after hooks to run on redirect errors; logic that must always run should be placed in before hooks or handled earlier in the flow. There is ongoing work to make error handling more flexible (like dynamic error URLs), but after hooks still won't run on redirect errors for now reference reference reference.

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

@dosubot[bot] commented on GitHub (Dec 3, 2025): <!-- Answer --> This is a known limitation in Better Auth: after hooks are only called if the endpoint handler completes successfully or throws an APIError. If a redirect error is thrown (as happens in social login callbacks on error), it is not caught as an APIError and the after hooks are skipped. This is by design and not a regression in 1.4.4—previous versions also skipped after hooks on redirect errors, though the error handling code has evolved recently. There is currently no supported workaround to force after hooks to run on redirect errors; logic that must always run should be placed in before hooks or handled earlier in the flow. There is ongoing work to make error handling more flexible (like dynamic error URLs), but after hooks still won't run on redirect errors for now [reference](https://github.com/better-auth/better-auth/issues/5514#issuecomment-3449865728) [reference](https://github.com/better-auth/better-auth/blob/afd63982fefac566b4cee23b3c00388419a7fdc4/packages/better-auth/src/api/to-auth-endpoints.ts#L40-L292) [reference](https://github.com/better-auth/better-auth/issues/1282). <!-- 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/0f22d4f4-1adc-4ad3-b9ca-d2eebf8cdf29?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/0f22d4f4-1adc-4ad3-b9ca-d2eebf8cdf29?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/0f22d4f4-1adc-4ad3-b9ca-d2eebf8cdf29?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/0f22d4f4-1adc-4ad3-b9ca-d2eebf8cdf29?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/0f22d4f4-1adc-4ad3-b9ca-d2eebf8cdf29?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/0f22d4f4-1adc-4ad3-b9ca-d2eebf8cdf29?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/0f22d4f4-1adc-4ad3-b9ca-d2eebf8cdf29?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=This%20is%20a%20known%20limitation%20in%20Better%20Auth%3A%20after%20hooks%20are%20only%20called%20if%20the%20endpoint%20handler%20completes%20successfully%20or%20throws%20an%20APIError.%20If%20a%20redirect%20error%20is%20thrown%20%28as%20happens%20in%20social%20login%20callbacks%20on%20error%29%2C%20it%20is%20not%20caught%20as%20an%20APIError%20and%20the%20after%20hooks%20are%20skipped.%20This%20is%20by%20design%20and%20not%20a%20regression%20in%201.4.4%E2%80%94previous%20versions%20also%20skipped%20after%20hooks%20on%20redirect%20errors%2C%20though%20the%20error%20handling%20code%20has%20evolved%20recently.%20There%20is%20currently%20no%20supported%20workaround%20to%20force%20after%20hooks%20to%20run%20on%20redirect%20errors%3B%20logic%20that%20must%20always%20run%20should%20be%20placed%20in%20before%20hooks%20or%20handled%20earlier%20in%20the%20flow.%20There%20is%20ongoing%20work%20to%20make%20error%20handling%20more%20flexible%20%28like%20dynamic%20error%20URLs%29%2C%20but%20after%20hooks%20still%20won%27t%20run%20on%20redirect%20errors%20for%20now%C2%A0%5Breference%5D%28https%3A//github.com/better-auth/better-auth/issues/5514%23issuecomment-3449865728%29%C2%A0%5Breference%5D%28https%3A//github.com/better-auth/better-auth/blob/afd63982fefac566b4cee23b3c00388419a7fdc4/packages/better-auth/src/api/to-auth-endpoints.ts%23L40-L292%29%C2%A0%5Breference%5D%28https%3A//github.com/better-auth/better-auth/issues/1282%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/6497)
Author
Owner

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

Hi @Zollerboy1,

This issue seems to be resolved in the latest version.
If the issue persists, feel free to reopen this or mention me!

@bytaesu commented on GitHub (Jan 24, 2026): Hi @Zollerboy1, This issue seems to be resolved in the latest version. If the issue persists, feel free to reopen this or mention me!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2448