From 4810069ccdcc7b8b8286731a63364854071109f1 Mon Sep 17 00:00:00 2001 From: kimi-agent-bot Date: Thu, 27 Aug 2026 16:10:33 +0000 Subject: [PATCH 1/4] fix(cli): give the interactive update check a longer CDN timeout `kimi update` shared the 3-second CDN fetch budget sized for passive background checks. Every CLI invocation is a fresh process paying full DNS+TCP+TLS setup, so a slow connection to the CDN intermittently aborted the interactive check with a raw "This operation was aborted". Thread a per-request timeout through the CDN fetch helpers and refreshUpdateCache; the interactive upgrade command now passes a 10 second budget (INTERACTIVE_UPDATE_CHECK_TIMEOUT_MS) while all background refresh paths keep the 3-second default. --- .../interactive-update-check-timeout.md | 5 +++ apps/kimi-code/src/cli/sub/upgrade.ts | 6 +++- apps/kimi-code/src/cli/update/cdn.ts | 26 ++++++++++---- apps/kimi-code/src/cli/update/refresh.ts | 7 +++- apps/kimi-code/src/constant/app.ts | 7 ++++ apps/kimi-code/test/cli/update/cdn.test.ts | 31 ++++++++++++++++ .../kimi-code/test/cli/update/refresh.test.ts | 36 +++++++++++++++++++ 7 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 .changeset/interactive-update-check-timeout.md diff --git a/.changeset/interactive-update-check-timeout.md b/.changeset/interactive-update-check-timeout.md new file mode 100644 index 00000000000..4390ccee74a --- /dev/null +++ b/.changeset/interactive-update-check-timeout.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +The interactive `kimi update` version check no longer aborts slow CDN connections after 3 seconds (`error: failed to check for updates: This operation was aborted`); it now waits up to 10 seconds, while background checks keep the 3-second budget. diff --git a/apps/kimi-code/src/cli/sub/upgrade.ts b/apps/kimi-code/src/cli/sub/upgrade.ts index c5471064571..861d6ddf84e 100644 --- a/apps/kimi-code/src/cli/sub/upgrade.ts +++ b/apps/kimi-code/src/cli/sub/upgrade.ts @@ -1,6 +1,8 @@ import { log, type Logger } from '@moonshot-ai/kimi-code-sdk'; import { track as trackTelemetry, type TelemetryProperties } from '@moonshot-ai/kimi-telemetry'; +import { INTERACTIVE_UPDATE_CHECK_TIMEOUT_MS } from '#/constant/app'; + import { refreshUpdateCache } from '#/cli/update/refresh'; import { selectUpdateTarget } from '#/cli/update/select'; import { detectInstallSource } from '#/cli/update/source'; @@ -174,7 +176,9 @@ export async function handleUpgrade( function createDefaultUpgradeDeps(overrides: Partial): UpgradeDeps { return { - refreshUpdateCache: overrides.refreshUpdateCache ?? (() => refreshUpdateCache()), + refreshUpdateCache: + overrides.refreshUpdateCache ?? + (() => refreshUpdateCache({ timeoutMs: INTERACTIVE_UPDATE_CHECK_TIMEOUT_MS })), detectInstallSource: overrides.detectInstallSource ?? (() => detectInstallSource()), installUpdate: overrides.installUpdate ?? installUpdateForeground, promptForInstallChoice: overrides.promptForInstallChoice ?? promptForInstallChoice, diff --git a/apps/kimi-code/src/cli/update/cdn.ts b/apps/kimi-code/src/cli/update/cdn.ts index 6e423cdb0c0..878a5a20587 100644 --- a/apps/kimi-code/src/cli/update/cdn.ts +++ b/apps/kimi-code/src/cli/update/cdn.ts @@ -5,6 +5,9 @@ import { kimiCodeCdnLatestJsonUrl, kimiCodeCdnLatestUrl } from '#/constant/app'; import type { UpdateManifest } from './types'; +// Background budget: passive checks (startup refresh, prompt pre-refresh) +// must never stall the CLI. The interactive `kimi update` command overrides +// this via refreshUpdateCache's timeoutMs (INTERACTIVE_UPDATE_CHECK_TIMEOUT_MS). const CDN_FETCH_TIMEOUT_MS = 3_000; const RolloutBatchSchema = z.object({ @@ -33,11 +36,15 @@ export interface FetchLatestResult { readonly manifest: UpdateManifest | null; } -async function fetchWithTimeout(fetchImpl: typeof fetch, input: string): Promise { +async function fetchWithTimeout( + fetchImpl: typeof fetch, + input: string, + timeoutMs: number, +): Promise { const controller = new AbortController(); const timeout = setTimeout(() => { controller.abort(); - }, CDN_FETCH_TIMEOUT_MS); + }, timeoutMs); try { return await fetchImpl(input, { signal: controller.signal }); } finally { @@ -57,8 +64,9 @@ async function fetchWithTimeout(fetchImpl: typeof fetch, input: string): Promise */ export async function fetchLatestVersionFromCdn( fetchImpl: typeof fetch = fetch, + timeoutMs: number = CDN_FETCH_TIMEOUT_MS, ): Promise { - const response = await fetchWithTimeout(fetchImpl, kimiCodeCdnLatestUrl()); + const response = await fetchWithTimeout(fetchImpl, kimiCodeCdnLatestUrl(), timeoutMs); if (!response.ok) { throw new Error(`CDN /latest returned HTTP ${response.status}`); } @@ -69,8 +77,11 @@ export async function fetchLatestVersionFromCdn( return raw; } -async function fetchUpdateManifestFromCdn(fetchImpl: typeof fetch): Promise { - const response = await fetchWithTimeout(fetchImpl, kimiCodeCdnLatestJsonUrl()); +async function fetchUpdateManifestFromCdn( + fetchImpl: typeof fetch, + timeoutMs: number, +): Promise { + const response = await fetchWithTimeout(fetchImpl, kimiCodeCdnLatestJsonUrl(), timeoutMs); if (!response.ok) { throw new Error(`CDN /latest.json returned HTTP ${response.status}`); } @@ -87,11 +98,12 @@ async function fetchUpdateManifestFromCdn(fetchImpl: typeof fetch): Promise { - const manifest = await fetchUpdateManifestFromCdn(fetchImpl).catch(() => null); + const manifest = await fetchUpdateManifestFromCdn(fetchImpl, timeoutMs).catch(() => null); if (manifest !== null) { return { latest: manifest.version, manifest }; } - const latest = await fetchLatestVersionFromCdn(fetchImpl); + const latest = await fetchLatestVersionFromCdn(fetchImpl, timeoutMs); return { latest, manifest: null }; } diff --git a/apps/kimi-code/src/cli/update/refresh.ts b/apps/kimi-code/src/cli/update/refresh.ts index 938a4a0fac3..8ad8c8ed9d3 100644 --- a/apps/kimi-code/src/cli/update/refresh.ts +++ b/apps/kimi-code/src/cli/update/refresh.ts @@ -11,13 +11,18 @@ export interface RefreshUpdateCacheDeps { readonly fetchLatest: () => Promise; readonly writeCache: (cache: UpdateCache) => Promise; readonly now: () => Date; + /** Per-request CDN timeout for the default fetchLatest; ignored when + * fetchLatest is injected. Defaults to the background budget in cdn.ts — + * the interactive `kimi update` command passes a longer one. */ + readonly timeoutMs?: number; } export async function refreshUpdateCache( overrides: Partial = {}, ): Promise { const resolved: RefreshUpdateCacheDeps = { - fetchLatest: overrides.fetchLatest ?? (() => fetchLatestFromCdn()), + fetchLatest: + overrides.fetchLatest ?? (() => fetchLatestFromCdn(undefined, overrides.timeoutMs)), writeCache: overrides.writeCache ?? writeUpdateCache, now: overrides.now ?? (() => new Date()), }; diff --git a/apps/kimi-code/src/constant/app.ts b/apps/kimi-code/src/constant/app.ts index 328b8190e59..18208af470b 100644 --- a/apps/kimi-code/src/constant/app.ts +++ b/apps/kimi-code/src/constant/app.ts @@ -122,6 +122,13 @@ export function kimiCodePluginMarketplaceUrl(): string { // marketplace versions. Without it a stalled connection to github.com hangs // the version phase for undici's default header timeout (300s). export const MARKETPLACE_VERSION_LOOKUP_TIMEOUT_MS = 5000; +// Bound on each CDN request of a user-initiated `kimi update` version check. +// Deliberately longer than the background budget in cli/update/cdn.ts: every +// CLI invocation is a fresh process that pays full DNS+TCP+TLS setup, and a +// user who explicitly asked to update is waiting on the result anyway — the +// 3s background budget aborts slow-but-working connections with a raw +// "This operation was aborted". +export const INTERACTIVE_UPDATE_CHECK_TIMEOUT_MS = 10_000; // Official plugins whose usage bills against the user's plan quota. Installing // one of these shows a quota note after the install result. export const QUOTA_CONSUMING_PLUGIN_IDS: readonly string[] = ['kimi-datasource']; diff --git a/apps/kimi-code/test/cli/update/cdn.test.ts b/apps/kimi-code/test/cli/update/cdn.test.ts index bbfaf965b11..59ddbe30ed4 100644 --- a/apps/kimi-code/test/cli/update/cdn.test.ts +++ b/apps/kimi-code/test/cli/update/cdn.test.ts @@ -230,4 +230,35 @@ describe('fetchLatestFromCdn', () => { vi.useRealTimers(); } }); + + it('honors a custom request timeout instead of the background budget', async () => { + vi.useFakeTimers(); + try { + const f = vi.fn(async (_input: string | URL, init?: RequestInit) => { + return new Promise((_resolve, reject) => { + init?.signal?.addEventListener('abort', () => { + reject(new Error('aborted')); + }, { once: true }); + }); + }) as unknown as typeof fetch; + + const result = fetchLatestFromCdn(f, 10_000); + let rejected = false; + void result.catch(() => { + rejected = true; + }); + const expectation = expect(result).rejects.toThrow(/aborted/); + // Past the 3s background budget (manifest 3s + fallback 3s) the default + // would already have rejected; the custom budget must still be waiting. + await vi.advanceTimersByTimeAsync(6_000); + expect(rejected).toBe(false); + // Manifest fetch aborts at 10s, the /latest fallback at 20s. + await vi.advanceTimersByTimeAsync(14_000); + + await expectation; + expect(rejected).toBe(true); + } finally { + vi.useRealTimers(); + } + }); }); diff --git a/apps/kimi-code/test/cli/update/refresh.test.ts b/apps/kimi-code/test/cli/update/refresh.test.ts index ceb1306f77e..e97112d073d 100644 --- a/apps/kimi-code/test/cli/update/refresh.test.ts +++ b/apps/kimi-code/test/cli/update/refresh.test.ts @@ -62,4 +62,40 @@ describe('refreshUpdateCache', () => { expect(writeCache).not.toHaveBeenCalled(); }); + + it('threads timeoutMs into the default CDN fetch', async () => { + vi.useFakeTimers(); + vi.stubGlobal( + 'fetch', + vi.fn(async (_input: string | URL, init?: RequestInit) => { + return new Promise((_resolve, reject) => { + init?.signal?.addEventListener('abort', () => { + reject(new Error('aborted')); + }, { once: true }); + }); + }), + ); + try { + const result = refreshUpdateCache({ + timeoutMs: 10_000, + writeCache: async () => {}, + }); + let rejected = false; + void result.catch(() => { + rejected = true; + }); + const expectation = expect(result).rejects.toThrow(/aborted/); + // The 3s background budget would have rejected by now; the custom + // budget must still be waiting. + await vi.advanceTimersByTimeAsync(6_000); + expect(rejected).toBe(false); + await vi.advanceTimersByTimeAsync(14_000); + + await expectation; + expect(rejected).toBe(true); + } finally { + vi.useRealTimers(); + vi.unstubAllGlobals(); + } + }); }); From 1aed6e57d992bf6afdfed3f374f4ec565ea46753 Mon Sep 17 00:00:00 2001 From: kimi-agent-bot Date: Fri, 28 Aug 2026 03:44:05 +0000 Subject: [PATCH 2/4] refactor(cli): drop motivational comments and simplify the update-check changeset --- .changeset/interactive-update-check-timeout.md | 2 +- apps/kimi-code/src/cli/update/cdn.ts | 3 --- apps/kimi-code/src/cli/update/refresh.ts | 3 --- apps/kimi-code/src/constant/app.ts | 6 ------ apps/kimi-code/test/cli/update/cdn.test.ts | 3 --- apps/kimi-code/test/cli/update/refresh.test.ts | 2 -- 6 files changed, 1 insertion(+), 18 deletions(-) diff --git a/.changeset/interactive-update-check-timeout.md b/.changeset/interactive-update-check-timeout.md index 4390ccee74a..f3b34952974 100644 --- a/.changeset/interactive-update-check-timeout.md +++ b/.changeset/interactive-update-check-timeout.md @@ -2,4 +2,4 @@ "@moonshot-ai/kimi-code": patch --- -The interactive `kimi update` version check no longer aborts slow CDN connections after 3 seconds (`error: failed to check for updates: This operation was aborted`); it now waits up to 10 seconds, while background checks keep the 3-second budget. +`kimi update` no longer fails with "This operation was aborted" on slow connections: the interactive version check now waits up to 10 seconds for the CDN instead of 3. diff --git a/apps/kimi-code/src/cli/update/cdn.ts b/apps/kimi-code/src/cli/update/cdn.ts index 878a5a20587..0aca3f33997 100644 --- a/apps/kimi-code/src/cli/update/cdn.ts +++ b/apps/kimi-code/src/cli/update/cdn.ts @@ -5,9 +5,6 @@ import { kimiCodeCdnLatestJsonUrl, kimiCodeCdnLatestUrl } from '#/constant/app'; import type { UpdateManifest } from './types'; -// Background budget: passive checks (startup refresh, prompt pre-refresh) -// must never stall the CLI. The interactive `kimi update` command overrides -// this via refreshUpdateCache's timeoutMs (INTERACTIVE_UPDATE_CHECK_TIMEOUT_MS). const CDN_FETCH_TIMEOUT_MS = 3_000; const RolloutBatchSchema = z.object({ diff --git a/apps/kimi-code/src/cli/update/refresh.ts b/apps/kimi-code/src/cli/update/refresh.ts index 8ad8c8ed9d3..a9ec67cecbf 100644 --- a/apps/kimi-code/src/cli/update/refresh.ts +++ b/apps/kimi-code/src/cli/update/refresh.ts @@ -11,9 +11,6 @@ export interface RefreshUpdateCacheDeps { readonly fetchLatest: () => Promise; readonly writeCache: (cache: UpdateCache) => Promise; readonly now: () => Date; - /** Per-request CDN timeout for the default fetchLatest; ignored when - * fetchLatest is injected. Defaults to the background budget in cdn.ts — - * the interactive `kimi update` command passes a longer one. */ readonly timeoutMs?: number; } diff --git a/apps/kimi-code/src/constant/app.ts b/apps/kimi-code/src/constant/app.ts index 18208af470b..e585a821ae3 100644 --- a/apps/kimi-code/src/constant/app.ts +++ b/apps/kimi-code/src/constant/app.ts @@ -122,12 +122,6 @@ export function kimiCodePluginMarketplaceUrl(): string { // marketplace versions. Without it a stalled connection to github.com hangs // the version phase for undici's default header timeout (300s). export const MARKETPLACE_VERSION_LOOKUP_TIMEOUT_MS = 5000; -// Bound on each CDN request of a user-initiated `kimi update` version check. -// Deliberately longer than the background budget in cli/update/cdn.ts: every -// CLI invocation is a fresh process that pays full DNS+TCP+TLS setup, and a -// user who explicitly asked to update is waiting on the result anyway — the -// 3s background budget aborts slow-but-working connections with a raw -// "This operation was aborted". export const INTERACTIVE_UPDATE_CHECK_TIMEOUT_MS = 10_000; // Official plugins whose usage bills against the user's plan quota. Installing // one of these shows a quota note after the install result. diff --git a/apps/kimi-code/test/cli/update/cdn.test.ts b/apps/kimi-code/test/cli/update/cdn.test.ts index 59ddbe30ed4..dae77449f09 100644 --- a/apps/kimi-code/test/cli/update/cdn.test.ts +++ b/apps/kimi-code/test/cli/update/cdn.test.ts @@ -248,11 +248,8 @@ describe('fetchLatestFromCdn', () => { rejected = true; }); const expectation = expect(result).rejects.toThrow(/aborted/); - // Past the 3s background budget (manifest 3s + fallback 3s) the default - // would already have rejected; the custom budget must still be waiting. await vi.advanceTimersByTimeAsync(6_000); expect(rejected).toBe(false); - // Manifest fetch aborts at 10s, the /latest fallback at 20s. await vi.advanceTimersByTimeAsync(14_000); await expectation; diff --git a/apps/kimi-code/test/cli/update/refresh.test.ts b/apps/kimi-code/test/cli/update/refresh.test.ts index e97112d073d..ff5a340de51 100644 --- a/apps/kimi-code/test/cli/update/refresh.test.ts +++ b/apps/kimi-code/test/cli/update/refresh.test.ts @@ -85,8 +85,6 @@ describe('refreshUpdateCache', () => { rejected = true; }); const expectation = expect(result).rejects.toThrow(/aborted/); - // The 3s background budget would have rejected by now; the custom - // budget must still be waiting. await vi.advanceTimersByTimeAsync(6_000); expect(rejected).toBe(false); await vi.advanceTimersByTimeAsync(14_000); From 393d3860682edc75ad708ab0d743ef876f7447c9 Mon Sep 17 00:00:00 2001 From: kimi-agent-bot Date: Fri, 28 Aug 2026 03:48:14 +0000 Subject: [PATCH 3/4] docs(cli): reword the update-check changeset --- .changeset/interactive-update-check-timeout.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/interactive-update-check-timeout.md b/.changeset/interactive-update-check-timeout.md index f3b34952974..7056d7cbcb8 100644 --- a/.changeset/interactive-update-check-timeout.md +++ b/.changeset/interactive-update-check-timeout.md @@ -2,4 +2,4 @@ "@moonshot-ai/kimi-code": patch --- -`kimi update` no longer fails with "This operation was aborted" on slow connections: the interactive version check now waits up to 10 seconds for the CDN instead of 3. +kimi update 增加请求超时时间。 From be7bc7b9d3eae0ab3d6c57b92e4f1535a3647f39 Mon Sep 17 00:00:00 2001 From: kimi-agent-bot Date: Fri, 28 Aug 2026 03:51:34 +0000 Subject: [PATCH 4/4] docs(cli): English changeset for the update-check timeout --- .changeset/interactive-update-check-timeout.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/interactive-update-check-timeout.md b/.changeset/interactive-update-check-timeout.md index 7056d7cbcb8..947e2533092 100644 --- a/.changeset/interactive-update-check-timeout.md +++ b/.changeset/interactive-update-check-timeout.md @@ -2,4 +2,4 @@ "@moonshot-ai/kimi-code": patch --- -kimi update 增加请求超时时间。 +Increase the request timeout for `kimi update`.