fix: preserve nested proxy handles

This commit is contained in:
jesse-merhi
2026-05-04 17:22:42 +10:00
committed by clawsweeper
parent 92e00c8bdd
commit 22fc256c3c
3 changed files with 49 additions and 18 deletions

View File

@@ -6,16 +6,23 @@ export type ActiveManagedProxyRegistration = {
};
let activeProxyUrl: ActiveManagedProxyUrl | undefined;
let activeProxyRegistrationCount = 0;
export function registerActiveManagedProxyUrl(proxyUrl: URL): ActiveManagedProxyRegistration {
const normalizedProxyUrl = new URL(proxyUrl.href);
if (activeProxyUrl !== undefined) {
throw new Error(
"proxy: cannot activate a managed proxy while another proxy is active; " +
"stop the current proxy before changing proxy.proxyUrl.",
);
if (activeProxyUrl.href !== normalizedProxyUrl.href) {
throw new Error(
"proxy: cannot activate a managed proxy while another proxy is active; " +
"stop the current proxy before changing proxy.proxyUrl.",
);
}
activeProxyRegistrationCount += 1;
return { proxyUrl: activeProxyUrl, stopped: false };
}
activeProxyUrl = new URL(proxyUrl.href);
activeProxyUrl = normalizedProxyUrl;
activeProxyRegistrationCount = 1;
return { proxyUrl: activeProxyUrl, stopped: false };
}
@@ -26,7 +33,11 @@ export function stopActiveManagedProxyRegistration(
return;
}
registration.stopped = true;
if (activeProxyUrl?.href === registration.proxyUrl.href) {
if (activeProxyUrl?.href !== registration.proxyUrl.href) {
return;
}
activeProxyRegistrationCount = Math.max(0, activeProxyRegistrationCount - 1);
if (activeProxyRegistrationCount === 0) {
activeProxyUrl = undefined;
}
}
@@ -37,4 +48,5 @@ export function getActiveManagedProxyUrl(): ActiveManagedProxyUrl | undefined {
export function _resetActiveManagedProxyStateForTests(): void {
activeProxyUrl = undefined;
activeProxyRegistrationCount = 0;
}

View File

@@ -291,7 +291,7 @@ describe("startProxy", () => {
expect((global as Record<string, unknown>)["GLOBAL_AGENT"]).toBeUndefined();
});
it("rejects overlapping handles with the same managed proxy URL", async () => {
it("keeps same-url overlapping handles active until the final stop", async () => {
const patchedHttpRequest = vi.fn() as unknown as typeof http.request;
const patchedHttpGet = vi.fn() as unknown as typeof http.get;
const patchedHttpsRequest = vi.fn() as unknown as typeof https.request;
@@ -311,13 +311,19 @@ describe("startProxy", () => {
enabled: true,
proxyUrl: "http://127.0.0.1:3128",
});
const secondHandle = await startProxy({
enabled: true,
proxyUrl: "http://127.0.0.1:3128",
});
await expect(
startProxy({
enabled: true,
proxyUrl: "http://127.0.0.1:3128",
}),
).rejects.toThrow("cannot activate a managed proxy");
expect(mockForceResetGlobalDispatcher).toHaveBeenCalledOnce();
expect(mockBootstrapGlobalAgent).toHaveBeenCalledOnce();
expect(http.request).toBe(patchedHttpRequest);
expect(https.request).toBe(patchedHttpsRequest);
expect(process.env["HTTP_PROXY"]).toBe("http://127.0.0.1:3128");
expect(process.env["OPENCLAW_PROXY_ACTIVE"]).toBe("1");
await stopProxy(secondHandle);
expect(http.request).toBe(patchedHttpRequest);
expect(https.request).toBe(patchedHttpsRequest);

View File

@@ -343,6 +343,9 @@ function stopActiveProxyRegistration(registration: ActiveManagedProxyRegistratio
return;
}
stopActiveManagedProxyRegistration(registration);
if (getActiveManagedProxyUrl()) {
return;
}
const restoreSnapshot = baseProxyEnvSnapshot ?? captureProxyEnv();
baseProxyEnvSnapshot = null;
@@ -390,11 +393,21 @@ export async function startProxy(config: ProxyConfig | undefined): Promise<Proxy
}
const proxyUrl = resolveProxyUrl(config);
if (getActiveManagedProxyUrl()) {
throw new Error(
"proxy: cannot activate a managed proxy while another proxy is active; " +
"stop the current proxy before changing proxy.proxyUrl.",
);
const activeProxyUrl = getActiveManagedProxyUrl();
if (activeProxyUrl) {
const registration = registerActiveManagedProxyUrl(new URL(proxyUrl));
const handle: ProxyHandle = {
proxyUrl,
injectedProxyUrl: proxyUrl,
envSnapshot: baseProxyEnvSnapshot ?? captureProxyEnv(),
stop: async () => {
stopActiveProxyRegistration(registration);
},
kill: () => {
stopActiveProxyRegistration(registration);
},
};
return handle;
}
baseProxyEnvSnapshot ??= captureProxyEnv();
const lifecycleBaseEnvSnapshot = baseProxyEnvSnapshot;