Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@
from published versions since it shows up in the VS Code extension changelog
tab and is confusing to users. Add it back between releases if needed. -->

## Unreleased

> **Breaking:** API requests now respect `http.proxySupport: off`. Previously
> the extension applied VS Code's proxy settings (`http.proxy`, `http.noProxy`,
> `coder.proxyBypass`) to API requests even when `http.proxySupport` was `off`.
> Now `off` ignores those settings and only proxy environment variables are
> used. If you set those settings and relied on them while proxy support was
> `off`, either set `http.proxySupport` to `on` to keep using them, or move the
> values into the `HTTP_PROXY`/`HTTPS_PROXY` (from `http.proxy`) and `NO_PROXY`
> (from `http.noProxy`/`coder.proxyBypass`) environment variables.

### Fixed

- Honor `http.proxySupport: off` when deriving proxy settings for SSH and API
connections, so VS Code's proxy settings are ignored while inherited proxy
environment variables still apply.

## [v1.15.1](https://github.com/coder/vscode-coder/releases/tag/v1.15.1) 2026-06-26

### Added
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@
"scope": "machine"
},
"coder.proxyBypass": {
"markdownDescription": "If not set, will inherit from the `no_proxy` or `NO_PROXY` environment variables. `http.proxySupport` must be set to `on` or `off`, otherwise VS Code will override the proxy agent set by the plugin.",
"markdownDescription": "If not set, will inherit from the `no_proxy` or `NO_PROXY` environment variables. Has no effect when `http.proxySupport` is set to `off`. With values other than `on`, VS Code will override the proxy agent set by the plugin.",
"markdownDeprecationMessage": "Deprecated: prefer `http.noProxy`.",
"type": "string",
"default": "",
"scope": "machine"
Expand Down
1 change: 1 addition & 0 deletions src/api/coderApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ const webSocketConfigSettings = [
"coder.tlsCaFile",
"coder.tlsAltHost",
"http.proxy",
"http.proxySupport",
"coder.proxyBypass",
"http.noProxy",
"http.proxyAuthorization",
Expand Down
16 changes: 12 additions & 4 deletions src/api/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,16 @@ export async function createHttpAgent(
): Promise<ProxyAgent> {
const insecure = cfg.get<boolean>("coder.insecure", false);
const proxyStrictSSL = cfg.get<boolean>("http.proxyStrictSSL", true);
const proxyAuthorization = cfg.get<string | null>("http.proxyAuthorization");
const httpNoProxy = cfg.get<string[]>("http.noProxy");
// "off" ignores VS Code proxy config; inherited env proxies still apply.
const proxyEnabled = cfg.get<string>("http.proxySupport") !== "off";
const proxySetting = <T>(key: string) =>
proxyEnabled ? cfg.get<T>(key) : undefined;
const proxyAuthorization = proxySetting<string | null>(
"http.proxyAuthorization",
);
const httpProxy = proxySetting<string | null>("http.proxy");
const coderProxyBypass = proxySetting<string | null>("coder.proxyBypass");
Comment thread
EhabY marked this conversation as resolved.
const httpNoProxy = proxySetting<string[]>("http.noProxy");

const certFile = expandPath(
String(cfg.get("coder.tlsCertFile") ?? "").trim(),
Expand All @@ -54,8 +62,8 @@ export async function createHttpAgent(
getProxyForUrl: (url: string) => {
return getProxyForUrl(
url,
cfg.get("http.proxy"),
cfg.get("coder.proxyBypass"),
httpProxy,
coderProxyBypass,
joinNoProxy(httpNoProxy),
);
},
Expand Down
5 changes: 5 additions & 0 deletions src/remote/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const SSH_PROXY_SETTINGS: ReadonlyArray<{
title: string;
}> = [
{ setting: "http.proxy", title: "HTTP Proxy" },
{ setting: "http.proxySupport", title: "HTTP Proxy Support" },
{ setting: "http.noProxy", title: "HTTP No Proxy" },
{ setting: "coder.proxyBypass", title: "Proxy Bypass" },
];
Expand Down Expand Up @@ -65,6 +66,10 @@ export function applySshEnvironment(
export function getSshProxyEnvironment(
cfg: Pick<WorkspaceConfiguration, "get">,
): SshEnvironment {
if (cfg.get<string>("http.proxySupport") === "off") {
return {};
}

const httpProxy = trimmed(cfg.get<string | null>("http.proxy"));
const noProxy =
trimmed(cfg.get<string | null>("coder.proxyBypass")) ??
Expand Down
19 changes: 19 additions & 0 deletions test/mocks/testHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,25 @@ export class MockConfigurationProvider {
}
}

export type Settings = Record<string, unknown>;

/** Proxy URL for proxy-config tests. */
export const PROXY_URL = "http://proxy.example.com:8080";

/** A MockConfigurationProvider seeded with the given settings. */
export function config(settings: Settings = {}): MockConfigurationProvider {
const cfg = new MockConfigurationProvider();
for (const [key, value] of Object.entries(settings)) {
cfg.set(key, value);
}
return cfg;
}

/** Settings with http.proxy set. */
export function withProxy(settings: Settings = {}): Settings {
return { "http.proxy": PROXY_URL, ...settings };
}

/**
* Mock progress reporter that integrates with vscode.window.withProgress.
* Use this to control progress reporting behavior and cancellation in tests.
Expand Down
42 changes: 24 additions & 18 deletions test/unit/api/coderApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -923,25 +923,31 @@ describe("CoderApi", () => {
expect(sockets).toHaveLength(1);
});

it("reconnects sockets in DISCONNECTED state when config changes", async () => {
mockConfig.set("coder.insecure", false);
const { sockets, handlers } = setupAutoOpeningWebSocket();
api = createApi(CODER_URL, AXIOS_TOKEN);
await api.watchAgentMetadata(AGENT_ID);
await tick();

// Trigger close with unrecoverable code to put socket in DISCONNECTED
handlers["close"]?.({ code: 1002, reason: "Protocol error" });
await tick();

mockConfig.set("coder.insecure", true);
await new Promise((resolve) =>
setTimeout(resolve, CONFIG_CHANGE_DEBOUNCE_MS + 50),
);
it.each([
["coder.insecure", false, true],
["http.proxySupport", "on", "off"],
])(
"reconnects sockets in DISCONNECTED state when %s changes",
async (setting, before, after) => {
mockConfig.set(setting, before);
const { sockets, handlers } = setupAutoOpeningWebSocket();
api = createApi(CODER_URL, AXIOS_TOKEN);
await api.watchAgentMetadata(AGENT_ID);
await tick();

// Trigger close with unrecoverable code to put socket in DISCONNECTED
handlers["close"]?.({ code: 1002, reason: "Protocol error" });
await tick();

mockConfig.set(setting, after);
await new Promise((resolve) =>
setTimeout(resolve, CONFIG_CHANGE_DEBOUNCE_MS + 50),
);

// Only DISCONNECTED sockets get reconnected by config changes
expect(sockets).toHaveLength(2);
});
// Only DISCONNECTED sockets get reconnected by config changes
expect(sockets).toHaveLength(2);
},
);
});
});

Expand Down
Loading