diff --git a/.claude/commands/create-pr.md b/.claude/commands/create-pr.md
index 920d5c770f..f2902057b5 100644
--- a/.claude/commands/create-pr.md
+++ b/.claude/commands/create-pr.md
@@ -58,4 +58,5 @@ gh pr create --title "
" --body ""
gh pr create --title "" --body "" && gh pr comment $(gh pr view --json number -q .number) --body "#skipreview"
```
-Display the returned PR URL as a markdown link on its own line, formatted as: `[PR #]()` so it's clickable.
\ No newline at end of file
+Display the returned PR URL as a markdown link on its own line, formatted as: `[PR #]()` so it's clickable.
+Display the name of the branch you created.
\ No newline at end of file
diff --git a/apps/web/app/api/google/linking/callback/route.ts b/apps/web/app/api/google/linking/callback/route.ts
index 8b9be8f160..ec620cad2c 100644
--- a/apps/web/app/api/google/linking/callback/route.ts
+++ b/apps/web/app/api/google/linking/callback/route.ts
@@ -179,17 +179,20 @@ export const GET = withError("google/linking/callback", async (request) => {
providerAccountId,
},
},
- select: { userId: true },
+ select: { id: true, userId: true },
});
if (accountNow?.userId === targetUserId) {
logger.info(
- "Account was created by concurrent request, continuing",
+ "Account already exists for same user, updating tokens",
{
targetUserId,
providerAccountId,
+ accountId: accountNow.id,
},
);
+
+ await updateGoogleAccountTokens(accountNow.id, tokens);
} else {
throw createError;
}
@@ -215,17 +218,7 @@ export const GET = withError("google/linking/callback", async (request) => {
accountId: linkingResult.existingAccountId,
});
- await prisma.account.update({
- where: { id: linkingResult.existingAccountId },
- data: {
- access_token: tokens.access_token,
- refresh_token: tokens.refresh_token,
- expires_at: tokens.expiry_date ? new Date(tokens.expiry_date) : null,
- scope: tokens.scope,
- token_type: tokens.token_type,
- id_token: tokens.id_token,
- },
- });
+ await updateGoogleAccountTokens(linkingResult.existingAccountId, tokens);
logger.info("Successfully updated tokens for Google account", {
email: providerEmail,
@@ -291,3 +284,32 @@ export const GET = withError("google/linking/callback", async (request) => {
});
}
});
+
+interface GoogleTokens {
+ access_token?: string | null;
+ refresh_token?: string | null;
+ expiry_date?: number | null;
+ scope?: string | null;
+ token_type?: string | null;
+ id_token?: string | null;
+}
+
+async function updateGoogleAccountTokens(
+ accountId: string,
+ tokens: GoogleTokens,
+) {
+ await prisma.account.update({
+ where: { id: accountId },
+ data: {
+ access_token: tokens.access_token,
+ // Only update refresh_token if provider returned one (preserves existing token)
+ ...(tokens.refresh_token != null && {
+ refresh_token: tokens.refresh_token,
+ }),
+ expires_at: tokens.expiry_date ? new Date(tokens.expiry_date) : null,
+ scope: tokens.scope,
+ token_type: tokens.token_type,
+ id_token: tokens.id_token,
+ },
+ });
+}
diff --git a/apps/web/app/api/outlook/linking/callback/route.ts b/apps/web/app/api/outlook/linking/callback/route.ts
index 5607eada9e..0fff63e6e6 100644
--- a/apps/web/app/api/outlook/linking/callback/route.ts
+++ b/apps/web/app/api/outlook/linking/callback/route.ts
@@ -231,17 +231,20 @@ export const GET = withError("outlook/linking/callback", async (request) => {
providerAccountId,
},
},
- select: { userId: true },
+ select: { id: true, userId: true },
});
if (accountNow?.userId === targetUserId) {
logger.info(
- "Account was created by concurrent request, continuing",
+ "Account already exists for same user, updating tokens",
{
targetUserId,
providerAccountId,
+ accountId: accountNow.id,
},
);
+
+ await updateMicrosoftAccountTokens(accountNow.id, tokens);
} else {
throw createError;
}
@@ -267,27 +270,10 @@ export const GET = withError("outlook/linking/callback", async (request) => {
accountId: linkingResult.existingAccountId,
});
- let expiresAt: Date | null = null;
- if (tokens.expires_at) {
- expiresAt = new Date(tokens.expires_at * 1000);
- } else if (tokens.expires_in) {
- const expiresInSeconds =
- typeof tokens.expires_in === "string"
- ? Number.parseInt(tokens.expires_in, 10)
- : tokens.expires_in;
- expiresAt = new Date(Date.now() + expiresInSeconds * 1000);
- }
-
- await prisma.account.update({
- where: { id: linkingResult.existingAccountId },
- data: {
- access_token: tokens.access_token,
- refresh_token: tokens.refresh_token,
- expires_at: expiresAt,
- scope: tokens.scope,
- token_type: tokens.token_type,
- },
- });
+ await updateMicrosoftAccountTokens(
+ linkingResult.existingAccountId,
+ tokens,
+ );
logger.info("Successfully updated tokens for Microsoft account", {
email: providerEmail,
@@ -351,3 +337,45 @@ export const GET = withError("outlook/linking/callback", async (request) => {
});
}
});
+
+interface MicrosoftTokens {
+ access_token: string;
+ refresh_token?: string | null;
+ expires_at?: number;
+ expires_in?: string | number;
+ scope?: string | null;
+ token_type?: string | null;
+}
+
+function parseMicrosoftExpiresAt(tokens: MicrosoftTokens): Date | null {
+ if (tokens.expires_at) {
+ return new Date(tokens.expires_at * 1000);
+ }
+ if (tokens.expires_in) {
+ const expiresInSeconds =
+ typeof tokens.expires_in === "string"
+ ? Number.parseInt(tokens.expires_in, 10)
+ : tokens.expires_in;
+ return new Date(Date.now() + expiresInSeconds * 1000);
+ }
+ return null;
+}
+
+async function updateMicrosoftAccountTokens(
+ accountId: string,
+ tokens: MicrosoftTokens,
+) {
+ await prisma.account.update({
+ where: { id: accountId },
+ data: {
+ access_token: tokens.access_token,
+ // Only update refresh_token if provider returned one (preserves existing token)
+ ...(tokens.refresh_token != null && {
+ refresh_token: tokens.refresh_token,
+ }),
+ expires_at: parseMicrosoftExpiresAt(tokens),
+ scope: tokens.scope,
+ token_type: tokens.token_type,
+ },
+ });
+}