Merge pull request #6560 from continuedev/nate/poll-user-account

Add retry logic with exponential backoff to listOrganizations for user account creation delay
This commit is contained in:
Nate Sesti 2025-07-29 16:47:57 -07:00 committed by GitHub
commit 03079c9fcc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 50 additions and 8 deletions

View File

@ -58,7 +58,7 @@ export class ControlPlaneClient {
}));
}
const resp = await this.request("ide/sync-secrets", {
const resp = await this.requestAndHandleError("ide/sync-secrets", {
method: "POST",
body: JSON.stringify({ fqsns, orgScopeId }),
});
@ -101,6 +101,15 @@ export class ControlPlaneClient {
},
});
return resp;
}
private async requestAndHandleError(
path: string,
init: RequestInit,
): Promise<Response> {
const resp = await this.request(path, init);
if (!resp.ok) {
throw new Error(
`Control plane request failed: ${resp.status} ${await resp.text()}`,
@ -128,7 +137,7 @@ export class ControlPlaneClient {
? `ide/list-assistants?organizationId=${organizationId}`
: "ide/list-assistants";
const resp = await this.request(url, {
const resp = await this.requestAndHandleError(url, {
method: "GET",
});
return (await resp.json()) as any;
@ -142,15 +151,48 @@ export class ControlPlaneClient {
return [];
}
try {
// We try again here because when users sign up with an email domain that is
// captured by an org, we need to wait for the user account creation webhook to
// take effect. Otherwise the organization(s) won't show up.
// This error manifests as a 404 (user not found)
let retries = 0;
const maxRetries = 10;
const maxWaitTime = 20000; // 20 seconds in milliseconds
while (retries < maxRetries) {
const resp = await this.request("ide/list-organizations", {
method: "GET",
});
if (resp.status === 404) {
retries++;
if (retries >= maxRetries) {
console.warn(
`Failed to list organizations after ${maxRetries} retries: user not found`,
);
return [];
}
const waitTime = Math.min(
Math.pow(2, retries) * 100,
maxWaitTime / maxRetries,
);
await new Promise((resolve) => setTimeout(resolve, waitTime));
continue;
} else if (!resp.ok) {
console.warn(
`Failed to list organizations (${resp.status}): ${await resp.text()}`,
);
return [];
}
const { organizations } = (await resp.json()) as any;
return organizations;
} catch (e) {
return [];
}
// This should never be reached due to the while condition, but adding for safety
console.warn(
`Failed to list organizations after ${maxRetries} retries: maximum attempts exceeded`,
);
return [];
}
public async listAssistantFullSlugs(
@ -165,7 +207,7 @@ export class ControlPlaneClient {
: "ide/list-assistant-full-slugs";
try {
const resp = await this.request(url, {
const resp = await this.requestAndHandleError(url, {
method: "GET",
});
const { fullSlugs } = (await resp.json()) as any;
@ -181,7 +223,7 @@ export class ControlPlaneClient {
}
try {
const resp = await this.request("ide/free-trial-status", {
const resp = await this.requestAndHandleError("ide/free-trial-status", {
method: "GET",
});
return (await resp.json()) as FreeTrialStatus;
@ -212,7 +254,7 @@ export class ControlPlaneClient {
params.set("vscode_uri_scheme", vsCodeUriScheme);
}
const resp = await this.request(
const resp = await this.requestAndHandleError(
`ide/get-models-add-on-checkout-url?${params.toString()}`,
{
method: "GET",