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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
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

### Fixed

- Apply a 60-second default timeout to REST requests, so requests hung on a
half-open TCP connection don't stall pollers forever.

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

> **Breaking:** API requests now respect `http.proxySupport: off`. Previously
Expand Down
9 changes: 9 additions & 0 deletions src/api/coderApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ import type {

const coderSessionTokenHeader = "Coder-Session-Token";

/**
* Default timeout for REST requests, so requests hung on half-open TCP
* connections (e.g. after system sleep) don't stall pollers forever.
* Streaming responses are only bounded until response headers arrive;
* axios never aborts an in-flight stream body.
*/
export const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;

/**
* Configuration settings that affect WebSocket connections.
* Changes to these settings will trigger WebSocket reconnection.
Expand Down Expand Up @@ -130,6 +138,7 @@ export class CoderApi extends Api implements vscode.Disposable {
httpRequestsTelemetry,
authConfigTracker,
);
client.getAxiosInstance().defaults.timeout = DEFAULT_REQUEST_TIMEOUT_MS;
client.setCredentials(baseUrl, token);

setupInterceptors(client, output, httpRequestsTelemetry, authConfigTracker);
Expand Down
26 changes: 25 additions & 1 deletion test/unit/api/coderApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
getRefreshCommand,
refreshCertificates,
} from "@/api/certificateRefresh";
import { CoderApi } from "@/api/coderApi";
import { CoderApi, DEFAULT_REQUEST_TIMEOUT_MS } from "@/api/coderApi";
import { createHttpAgent } from "@/api/utils";
import { CONFIG_CHANGE_DEBOUNCE_MS } from "@/configWatcher";
import { ClientCertificateError } from "@/error/clientCertificateError";
Expand Down Expand Up @@ -131,6 +131,30 @@ describe("CoderApi", () => {
});

describe("HTTP Interceptors", () => {
it("sets a default request timeout on the axios instance", () => {
api = createApi();

expect(api.getAxiosInstance().defaults.timeout).toBe(
DEFAULT_REQUEST_TIMEOUT_MS,
);
});

it("applies the default timeout to requests", async () => {
api = createApi();
const response = await api.getAxiosInstance().get("/api/v2/users/me");

expect(response.config.timeout).toBe(DEFAULT_REQUEST_TIMEOUT_MS);
});

it("allows per-request timeout overrides", async () => {
api = createApi();
const response = await api
.getAxiosInstance()
.get("/api/v2/users/me", { timeout: 0 });

expect(response.config.timeout).toBe(0);
});

it("adds custom headers and HTTP agent to requests", async () => {
const mockAgent = new ProxyAgent();
vi.mocked(createHttpAgent).mockResolvedValue(mockAgent);
Expand Down
Loading