From 7806d16667f16af9c98fb552b1ae7b0bd44dcc32 Mon Sep 17 00:00:00 2001 From: zeekay Date: Fri, 3 Jul 2026 09:32:25 -0700 Subject: [PATCH 1/2] =?UTF-8?q?fix(agents):=20raise=20cloud-agent=20run=20?= =?UTF-8?q?timeout=2030s=E2=86=92180s=20(env=20CLOUD=5FAGENT=5FTIMEOUT)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A cloud agent run is a real chat completion — a zen5-mini answer routinely takes ~25-30s (measured 28s), larger models/prompts longer. The hardcoded 30s client timeout aborted long runs mid-flight, surfacing in the UI as a 502 even though the cloud run finished and was recorded. Bump the default to 180s and make it env-configurable (CLOUD_AGENT_TIMEOUT), matching the existing CLOUD_AGENT_MAX_CONCURRENT knob. List/get are fast; this headroom only affects /run. Co-Authored-By: Claude Opus 4.8 --- api/server/services/CloudAgentsClient.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/api/server/services/CloudAgentsClient.js b/api/server/services/CloudAgentsClient.js index 982e50d43d..bc6a3eef6c 100644 --- a/api/server/services/CloudAgentsClient.js +++ b/api/server/services/CloudAgentsClient.js @@ -50,15 +50,25 @@ const MAX_RESPONSE = 4 * 1024 * 1024; */ const MAX_CONCURRENT = Number(process.env.CLOUD_AGENT_MAX_CONCURRENT) || 50; +/** + * HTTP timeout (ms) for a cloud call. A run is a real chat completion — a + * zen5-mini answer routinely takes ~25-30s and larger models/prompts longer — so + * the old 30s default aborted mid-run, surfacing as an in-UI 502 even though the + * cloud run finished and recorded. 180s gives real runs headroom; override with + * CLOUD_AGENT_TIMEOUT. (List/get are fast; the ceiling only matters for /run.) + */ +const DEFAULT_TIMEOUT = Number(process.env.CLOUD_AGENT_TIMEOUT) || 180000; + class CloudAgentsClient { /** * @param {Object} opts * @param {string} opts.endpoint - Cloud base URL (e.g. https://api.hanzo.ai) - * @param {number} [opts.timeout] - HTTP timeout in ms (default 30000; a run is - * a real chat completion so it needs more headroom than a metadata read) + * @param {number} [opts.timeout] - HTTP timeout in ms (default DEFAULT_TIMEOUT, + * 180s; a run is a real chat completion so it needs far more headroom than a + * metadata read — 30s aborted long runs mid-flight) * @param {number} [opts.maxConcurrent] - process-wide in-flight ceiling */ - constructor({ endpoint, timeout = 30000, maxConcurrent = MAX_CONCURRENT }) { + constructor({ endpoint, timeout = DEFAULT_TIMEOUT, maxConcurrent = MAX_CONCURRENT }) { this.endpoint = endpoint.replace(/\/+$/, ''); this.timeout = timeout; this.maxConcurrent = maxConcurrent; From 968467d2da32e1ef98a5027b5b12d6be68ae3967 Mon Sep 17 00:00:00 2001 From: zeekay Date: Sat, 4 Jul 2026 04:05:03 -0700 Subject: [PATCH 2/2] test(agents): lock cloud-agent run timeout contract (180s default + env override) Adds coverage the timeout bump lacked: asserts the 180s default (the fix for the 30s -> in-UI 502), that an explicit constructor timeout wins, and that CLOUD_AGENT_TIMEOUT overrides the module-load default (isolated re-require). Co-Authored-By: Claude Opus 4.8 --- api/server/services/CloudAgentsClient.spec.js | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/api/server/services/CloudAgentsClient.spec.js b/api/server/services/CloudAgentsClient.spec.js index 539eeb27bd..88effd04a0 100644 --- a/api/server/services/CloudAgentsClient.spec.js +++ b/api/server/services/CloudAgentsClient.spec.js @@ -210,6 +210,36 @@ describe('CloudAgentsClient', () => { }); }); + describe('run timeout (headroom for long completions)', () => { + it('defaults to 180s so a long run is not aborted mid-flight (the 30s -> 502 bug)', () => { + const client = new CloudAgentsClient({ endpoint: 'https://api.hanzo.ai' }); + expect(client.timeout).toBe(180000); + }); + + it('honors an explicit constructor timeout', () => { + const client = new CloudAgentsClient({ endpoint: 'https://api.hanzo.ai', timeout: 5000 }); + expect(client.timeout).toBe(5000); + }); + + it('is overridable via CLOUD_AGENT_TIMEOUT (mirrors CLOUD_AGENT_MAX_CONCURRENT)', () => { + const saved = process.env.CLOUD_AGENT_TIMEOUT; + process.env.CLOUD_AGENT_TIMEOUT = '90000'; + // DEFAULT_TIMEOUT is read at module load, so re-require in isolation. + jest.resetModules(); + const { CloudAgentsClient: Fresh } = require('./CloudAgentsClient'); + try { + expect(new Fresh({ endpoint: 'https://api.hanzo.ai' }).timeout).toBe(90000); + } finally { + if (saved === undefined) { + delete process.env.CLOUD_AGENT_TIMEOUT; + } else { + process.env.CLOUD_AGENT_TIMEOUT = saved; + } + jest.resetModules(); + } + }); + }); + describe('getCloudAgentsClient (env wiring)', () => { const saved = {}; beforeEach(() => {