diff --git a/.changeset/two-factor-newsession-null.md b/.changeset/two-factor-newsession-null.md
new file mode 100644
index 0000000000..9e9d659174
--- /dev/null
+++ b/.changeset/two-factor-newsession-null.md
@@ -0,0 +1,5 @@
+---
+"better-auth": patch
+---
+
+Document that `ctx.context.newSession` is `null` while a two-factor challenge is in flight. When a 2FA-enabled user signs in through a credential endpoint, the pending session is discarded and `newSession` is reset to `null` (no authenticated session exists until the second factor is verified). Server-side `after` hooks reading `ctx.context.newSession` must null-check it before accessing `newSession.user`. This clarifies the behavior change introduced in #9639.
diff --git a/docs/content/docs/plugins/2fa.mdx b/docs/content/docs/plugins/2fa.mdx
index ce6ed5099b..cb8201083b 100644
--- a/docs/content/docs/plugins/2fa.mdx
+++ b/docs/content/docs/plugins/2fa.mdx
@@ -120,6 +120,10 @@ By default, 2FA sign-in enforcement applies to the credential-based sign-in endp
If your app needs to require 2FA for those sign-in methods, add custom hook handling for those endpoints and redirect users into your 2FA verification flow before treating the sign-in as complete.
+
+ When a 2FA-enabled user signs in via a credential endpoint, the plugin issues a 2FA challenge instead of completing the sign-in. As part of this, the pending session is discarded and `ctx.context.newSession` is reset to `null` — there is no authenticated session until the user verifies the second factor. Server-side hooks (e.g. `after` hooks on the sign-in endpoints) that read `ctx.context.newSession` must null-check it before accessing `newSession.user`, otherwise they will throw while a 2FA challenge is in flight.
+
+
You can handle this in the `onSuccess` callback or by providing a `onTwoFactorRedirect` callback in the plugin config.
```tsx
diff --git a/packages/better-auth/src/plugins/two-factor/index.ts b/packages/better-auth/src/plugins/two-factor/index.ts
index 037db4d21b..0caccf7093 100644
--- a/packages/better-auth/src/plugins/two-factor/index.ts
+++ b/packages/better-auth/src/plugins/two-factor/index.ts
@@ -469,7 +469,15 @@ export const twoFactor = (options?: O) => {
}
/**
- * remove the session cookie. It's set by the sign in credential
+ * Remove the session cookie set by the credential sign-in.
+ *
+ * The credential handler already created a session and set
+ * `ctx.context.newSession`. Since 2FA is still pending, that
+ * session is deleted here and `newSession` is reset to `null`
+ * so downstream hooks don't observe a session that no longer
+ * exists. Hooks that read `ctx.context.newSession` after a
+ * sign-in must therefore null-check it: it is `null` while a
+ * 2FA challenge is in flight (no authenticated session yet).
*/
deleteSessionCookie(ctx, true);
await ctx.context.internalAdapter.deleteSession(data.session.token);