diff --git a/apps/server/src/sourceControl/AzureDevOpsSourceControlProvider.ts b/apps/server/src/sourceControl/AzureDevOpsSourceControlProvider.ts index bf2ac9829275..2f147452f9ec 100644 --- a/apps/server/src/sourceControl/AzureDevOpsSourceControlProvider.ts +++ b/apps/server/src/sourceControl/AzureDevOpsSourceControlProvider.ts @@ -45,6 +45,10 @@ export const discovery = { executable: "az", versionArgs: ["--version"], authArgs: ["account", "show", "--query", "user.name", "-o", "tsv"], + // `az` boots a fresh Python interpreter on every invocation, so even `az --version` + // takes ~6s on Windows and overruns the default budget, leaving the provider reported + // as missing on machines where it is installed. `gh` and `glab` answer in ~0.3s. + probeTimeoutMs: 20_000, parseAuth: parseAzureAuth, installHint: "Install the Azure command-line tools (`az`), then enable Azure DevOps support with `az extension add --name azure-devops`.", diff --git a/apps/server/src/sourceControl/SourceControlProviderDiscovery.ts b/apps/server/src/sourceControl/SourceControlProviderDiscovery.ts index e3a6bd1fb205..b2b9e4513378 100644 --- a/apps/server/src/sourceControl/SourceControlProviderDiscovery.ts +++ b/apps/server/src/sourceControl/SourceControlProviderDiscovery.ts @@ -33,6 +33,7 @@ export type SourceControlCliDiscoverySpec = SourceControlDiscoverySpecBase & { readonly executable: string; readonly versionArgs: ReadonlyArray; readonly authArgs: ReadonlyArray; + readonly probeTimeoutMs?: number; readonly parseAuth: (input: SourceControlAuthProbeInput) => SourceControlProviderAuth; readonly refineUnknownRemote?: ( input: SourceControlUnknownRemoteRefinementInput, @@ -52,6 +53,14 @@ type SourceControlCliRemoteRefinementSpec = SourceControlCliDiscoverySpec & { readonly refineUnknownRemote: NonNullable; }; +// Most provider CLIs answer `--version` in well under a second, so a short budget keeps +// discovery snappy. Specs whose CLI is known to be slower can raise it via probeTimeoutMs. +const DEFAULT_PROBE_TIMEOUT_MS = 5_000; + +function probeTimeoutMs(spec: SourceControlCliDiscoverySpec): number { + return spec.probeTimeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS; +} + interface DiscoveryProbeResult { readonly kind: SourceControlProviderKind; readonly label: string; @@ -167,7 +176,7 @@ function probeCli(input: { command: input.spec.executable, args: input.spec.versionArgs, cwd: input.cwd, - timeoutMs: 5_000, + timeoutMs: probeTimeoutMs(input.spec), maxOutputBytes: 8_000, appendTruncationMarker: true, }) @@ -244,7 +253,7 @@ export function probeSourceControlProvider(input: { args: spec.authArgs, cwd: input.cwd, allowNonZeroExit: true, - timeoutMs: 5_000, + timeoutMs: probeTimeoutMs(spec), maxOutputBytes: 8_000, appendTruncationMarker: true, }) @@ -287,7 +296,7 @@ export const refineUnknownRemoteProvider = Effect.fn("refineUnknownRemoteProvide args: spec.authArgs, cwd: input.cwd, allowNonZeroExit: true, - timeoutMs: 5_000, + timeoutMs: probeTimeoutMs(spec), maxOutputBytes: 8_000, appendTruncationMarker: true, })