From 45bc61f8f9d2a0a72218fc88b6924ceadcd1d43c Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Mon, 7 Sep 2026 20:37:46 +0300 Subject: [PATCH] fix(network): apply connection timeout in server and extension hosts Complete the runtime entrypoints from #3404 without changing address-family selection. --- packages/vscode/src/DOCUMENTATION.md | 8 +++++++ packages/vscode/src/extension.ts | 2 ++ packages/vscode/src/networkDefaults.test.ts | 24 +++++++++++++++++++ packages/vscode/src/networkDefaults.ts | 15 ++++++++++++ packages/web/server/index.js | 4 ++++ .../web/server/lib/network-defaults.test.js | 16 +++++++++++++ .../web/server/lib/quota/DOCUMENTATION.md | 5 ++++ 7 files changed, 74 insertions(+) create mode 100644 packages/vscode/src/networkDefaults.test.ts create mode 100644 packages/vscode/src/networkDefaults.ts diff --git a/packages/vscode/src/DOCUMENTATION.md b/packages/vscode/src/DOCUMENTATION.md index 0e9c3a3e..89bfa666 100644 --- a/packages/vscode/src/DOCUMENTATION.md +++ b/packages/vscode/src/DOCUMENTATION.md @@ -190,6 +190,14 @@ Reachable filesystem routes: `api:fs:read` (attachments), `api:fs:search` Maintenance: reviews, changelog entries, and parity claims consult this map; whoever mounts or unmounts a surface updates it in the same change. +## Network connections + +Extension activation applies `networkDefaults.ts` before registering handlers. +It gives Node connection attempts 5 seconds, matching the web runtime, so quota +requests to distant providers can connect. This is an extension-host process +default, including other Node connections in that host. Address-family selection +stays unchanged; runtimes without the setter retain their existing behavior. + ## Global OpenCode paths `opencodeConfigPaths.ts` owns the global config directory for config CRUD, diff --git a/packages/vscode/src/extension.ts b/packages/vscode/src/extension.ts index 036f543f..32c8a098 100644 --- a/packages/vscode/src/extension.ts +++ b/packages/vscode/src/extension.ts @@ -7,6 +7,7 @@ import { startGlobalEventWatcher, stopGlobalEventWatcher, setChatViewProvider } import { pathsEqualWithNormalizedDriveLetter } from './pathUtils'; import { resolveWorkspaceFolders } from './workspaceResolver'; import { InlineCommentThreads, SIDEBAR_SURFACE_ID } from './InlineCommentThreads'; +import { applyConnectAttemptTimeout } from './networkDefaults'; let chatViewProvider: ChatViewProvider | undefined; @@ -52,6 +53,7 @@ const formatDurationMs = (value: number | null | undefined) => { }; export async function activate(context: vscode.ExtensionContext) { + applyConnectAttemptTimeout(); outputChannel = vscode.window.createOutputChannel('OpenChamber'); let moveToRightSidebarScheduled = false; diff --git a/packages/vscode/src/networkDefaults.test.ts b/packages/vscode/src/networkDefaults.test.ts new file mode 100644 index 00000000..032033aa --- /dev/null +++ b/packages/vscode/src/networkDefaults.test.ts @@ -0,0 +1,24 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import * as net from 'node:net'; +import { applyConnectAttemptTimeout } from './networkDefaults'; + +test('allows slow connections without changing address-family selection', () => { + const previousTimeout = net.getDefaultAutoSelectFamilyAttemptTimeout(); + const previousFamily = net.getDefaultAutoSelectFamily(); + try { + net.setDefaultAutoSelectFamilyAttemptTimeout(250); + assert.equal(applyConnectAttemptTimeout(), true); + assert.equal(net.getDefaultAutoSelectFamilyAttemptTimeout(), 5_000); + assert.equal(net.getDefaultAutoSelectFamily(), previousFamily); + } finally { + net.setDefaultAutoSelectFamilyAttemptTimeout(previousTimeout); + } +}); + +test('unsupported runtimes retain their existing behavior', () => { + assert.equal(applyConnectAttemptTimeout({}), false); + assert.equal(applyConnectAttemptTimeout({ + setDefaultAutoSelectFamilyAttemptTimeout() { throw new Error('unsupported'); }, + }), false); +}); diff --git a/packages/vscode/src/networkDefaults.ts b/packages/vscode/src/networkDefaults.ts new file mode 100644 index 00000000..293b7214 --- /dev/null +++ b/packages/vscode/src/networkDefaults.ts @@ -0,0 +1,15 @@ +import * as net from 'node:net'; + +// Mirrors the web runtime policy for distant quota endpoints. The extension +// host has its own Node fetch stack and does not inherit server defaults. +export function applyConnectAttemptTimeout( + netModule: Partial> = net, +): boolean { + try { + if (!netModule.setDefaultAutoSelectFamilyAttemptTimeout) return false; + netModule.setDefaultAutoSelectFamilyAttemptTimeout(5_000); + return true; + } catch { + return false; + } +} diff --git a/packages/web/server/index.js b/packages/web/server/index.js index ef2e7d3f..7935f733 100644 --- a/packages/web/server/index.js +++ b/packages/web/server/index.js @@ -118,6 +118,10 @@ import { createScheduledTaskService } from './lib/scheduled-tasks/service.js'; import { createOpenChamberControlService } from './lib/openchamber-control/service.js'; import { OpenChamberControlError } from './lib/openchamber-control/error.js'; import webPush from 'web-push'; +import { applyConnectAttemptTimeout } from './lib/network-defaults.js'; + +// Background CLI launches enter here in a fresh process, without CLI defaults. +applyConnectAttemptTimeout(); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); diff --git a/packages/web/server/lib/network-defaults.test.js b/packages/web/server/lib/network-defaults.test.js index 31ee963b..5be19292 100644 --- a/packages/web/server/lib/network-defaults.test.js +++ b/packages/web/server/lib/network-defaults.test.js @@ -1,9 +1,25 @@ import { describe, expect, it, vi } from 'vitest'; import net from 'node:net'; +import { spawnSync } from 'node:child_process'; import { applyConnectAttemptTimeout, CONNECT_ATTEMPT_TIMEOUT_MS } from './network-defaults.js'; describe('applyConnectAttemptTimeout', () => { + it('initializes the server entrypoint in a fresh Node process', () => { + const serverUrl = new URL('../index.js', import.meta.url).href; + const result = spawnSync('node', ['--input-type=module', '--eval', ` + import net from 'node:net'; + import assert from 'node:assert/strict'; + const family = net.getDefaultAutoSelectFamily(); + net.setDefaultAutoSelectFamilyAttemptTimeout(250); + await import(${JSON.stringify(serverUrl)}); + assert.equal(net.getDefaultAutoSelectFamilyAttemptTimeout(), 5000); + assert.equal(net.getDefaultAutoSelectFamily(), family); + process.exit(0); + `], { encoding: 'utf8', timeout: 15_000, windowsHide: true }); + expect(result.status, result.stderr).toBe(0); + }); + it('raises the per-attempt connect timeout on runtimes that expose the setter', () => { const previous = net.getDefaultAutoSelectFamilyAttemptTimeout(); try { diff --git a/packages/web/server/lib/quota/DOCUMENTATION.md b/packages/web/server/lib/quota/DOCUMENTATION.md index 0a8d10e9..6d12ef2d 100644 --- a/packages/web/server/lib/quota/DOCUMENTATION.md +++ b/packages/web/server/lib/quota/DOCUMENTATION.md @@ -3,6 +3,11 @@ ## Purpose This module fetches quota and usage signals for supported providers in the web server runtime. +Node server entrypoints apply `../network-defaults.js` before serving requests, +including the daemon launched directly through `server/index.js`. Connection +attempts get 5 seconds without changing address-family selection. The VS Code +extension applies the same policy in its own process at activation. + ## Entrypoints and structure - `packages/web/server/lib/quota/index.js`: public entrypoint imported by `packages/web/server/index.js`. - `packages/web/server/lib/quota/routes.js`: Express route registration for quota endpoints.