fix: invalidate repo tree on repo installation by Flo4604 · Pull Request #5356 · unkeyed/unkey

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In TanStack Query (React Query) v5, when a mutation onSuccesscallback returns a Promise, is that Promise awaited beforemutateAsync() resolves?

💡 Result:

Yes—for the onSuccess provided in useMutation (or mutation defaults / MutationCache).

In TanStack Query v5, the mutation’s internal execute() function does await this.options.onSuccess?.(...) and only then returns the mutation result, so mutateAsync() won’t resolve until that onSuccess Promise settles. [1][2]

Caveat: the per-call callbacks passed to mutate(variables, { onSuccess }) / mutateAsync(variables, { onSuccess }) are invoked by the observer and their return value is ignored (not awaited). [1][3]

Sources
[1] TanStack Query useMutation reference (“If a promise is returned, it will be awaited…”) https://tanstack.com/query/latest/docs/framework/react/reference/useMutation
[2] packages/query-core/src/mutation.ts (v5.67.0) await this.options.onSuccess... before return data https://github.com/TanStack/query/blob/v5.67.0/packages/query-core/src/mutation.ts
[3] packages/query-core/src/mutationObserver.ts (v5.67.0) per-call mutateOptions.onSuccess?.(...) without await https://github.com/TanStack/query/blob/v5.67.0/packages/query-core/src/mutationObserver.ts


🏁 Script executed:

cat -n web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/new/steps/select-repo/index.tsx | sed -n '1,120p'

Repository: unkeyed/unkey

Length of output: 4946


🏁 Script executed:

rg "invalidate\(\)" -A 2 -B 2 web/apps/dashboard --type tsx --max-count 10

Repository: unkeyed/unkey

Length of output: 84


🏁 Script executed:

rg "invalidate\(\)" -A 2 -B 2 web/apps/dashboard --type ts --max-count 10

Repository: unkeyed/unkey

Length of output: 48088


🏁 Script executed:

cat -n web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/build-settings/github-settings/github-no-repo.tsx | sed -n '1,80p'

Repository: unkeyed/unkey

Length of output: 3023


Await invalidations before navigating to the next step.

Lines 37–38 fire invalidations without awaiting them. Because next() runs immediately after mutateAsync() resolves, the wizard advances before the cache refresh completes, causing stale repo-tree state in subsequent steps.

Minimal fix
   const selectRepoMutation = trpc.github.selectRepository.useMutation({
     onSuccess: async (_data, variables) => {
-      trpcUtils.github.getInstallations.invalidate();
-      trpcUtils.github.getRepoTree.invalidate();
+      await Promise.all([
+        trpcUtils.github.getInstallations.invalidate(),
+        trpcUtils.github.getRepoTree.invalidate(),
+      ]);
       const name =
         variables.repositoryFullName.length > 40
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

trpcUtils.github.getInstallations.invalidate();
trpcUtils.github.getRepoTree.invalidate();
await Promise.all([
trpcUtils.github.getInstallations.invalidate(),
trpcUtils.github.getRepoTree.invalidate(),
]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/new/steps/select-repo/index.tsx
around lines 37 - 38, The invalidation calls
trpcUtils.github.getInstallations.invalidate and
trpcUtils.github.getRepoTree.invalidate are fired but not awaited, so the
wizard's next() runs before cache refresh finishes; fix by awaiting those
invalidations (e.g., await
Promise.all([trpcUtils.github.getInstallations.invalidate(),
trpcUtils.github.getRepoTree.invalidate()])) right after mutateAsync() resolves
and before calling next(), ensuring the repo-tree cache is refreshed before
advancing.