From b4f9b8a08b1540daaae68c7eae9c831cf5680de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=F0=9F=A4=A0?= Date: Thu, 11 Dec 2025 09:42:07 +1000 Subject: [PATCH 1/3] Add timeout for file watcher subscribe on Rosetta x86 emulation --- packages/opencode/src/file/watcher.ts | 29 ++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/file/watcher.ts b/packages/opencode/src/file/watcher.ts index 80e524349afd..394474e2c6ca 100644 --- a/packages/opencode/src/file/watcher.ts +++ b/packages/opencode/src/file/watcher.ts @@ -33,6 +33,29 @@ export namespace FileWatcher { return createWrapper(binding) as typeof import("@parcel/watcher") }) + // WORKAROUND: Timeout for watcher subscribe - hangs indefinitely on x86 emulation via Rosetta (parcel-bundler/watcher#159) + // On timeout: no auto-detection of branch switches or external file changes + const SUBSCRIBE_TIMEOUT_MS = 5000 + + async function subscribeWithTimeout( + dir: string, + callback: ParcelWatcher.SubscribeCallback, + options: Parameters[2], + ): Promise { + const timeout = new Promise((resolve) => { + setTimeout(() => { + log.error("watcher subscribe timeout", { dir }) + resolve(null) + }, SUBSCRIBE_TIMEOUT_MS) + }) + try { + return await Promise.race([watcher().subscribe(dir, callback, options), timeout]) + } catch (e) { + log.warn("watcher subscribe failed", { dir, error: e }) + return null + } + } + const state = Instance.state( async () => { if (Instance.project.vcs !== "git") return {} @@ -57,11 +80,11 @@ export namespace FileWatcher { } } - const subs = [] + const subs: (ParcelWatcher.AsyncSubscription | null)[] = [] const cfgIgnores = cfg.watcher?.ignore ?? [] subs.push( - await watcher().subscribe(Instance.directory, subscribe, { + await subscribeWithTimeout(Instance.directory, subscribe, { ignore: [...FileIgnore.PATTERNS, ...cfgIgnores], backend, }), @@ -70,7 +93,7 @@ export namespace FileWatcher { const vcsDir = await $`git rev-parse --git-dir`.quiet().nothrow().cwd(Instance.worktree).text() if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) { subs.push( - await watcher().subscribe(vcsDir, subscribe, { + await subscribeWithTimeout(vcsDir, subscribe, { ignore: ["hooks", "info", "logs", "objects", "refs", "worktrees", "modules", "lfs"], backend, }), From 5138362087fd0458376b94bf72a655c9d3b6a575 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:38:27 -0600 Subject: [PATCH 2/3] Update packages/opencode/src/file/watcher.ts Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- packages/opencode/src/file/watcher.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/opencode/src/file/watcher.ts b/packages/opencode/src/file/watcher.ts index 394474e2c6ca..98537234850e 100644 --- a/packages/opencode/src/file/watcher.ts +++ b/packages/opencode/src/file/watcher.ts @@ -54,6 +54,27 @@ export namespace FileWatcher { log.warn("watcher subscribe failed", { dir, error: e }) return null } + async function subscribeWithTimeout( + dir: string, + callback: ParcelWatcher.SubscribeCallback, + options: Parameters[2], + ): Promise { + let timeoutId: Timer + const timeout = new Promise((resolve) => { + timeoutId = setTimeout(() => { + log.error("watcher subscribe timeout", { dir }) + resolve(null) + }, SUBSCRIBE_TIMEOUT_MS) + }) + return Promise.race([ + watcher() + .subscribe(dir, callback, options) + .finally(() => clearTimeout(timeoutId)), + timeout, + ]).catch((e) => { + log.warn("watcher subscribe failed", { dir, error: e }) + return null + }) } const state = Instance.state( From 55a681dba11d40683d0326407d19a06ada3a7d7e Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 10 Dec 2025 19:48:48 -0600 Subject: [PATCH 3/3] fix --- packages/opencode/src/file/watcher.ts | 70 +++++++++++---------------- 1 file changed, 29 insertions(+), 41 deletions(-) diff --git a/packages/opencode/src/file/watcher.ts b/packages/opencode/src/file/watcher.ts index 98537234850e..ef2d00ab6831 100644 --- a/packages/opencode/src/file/watcher.ts +++ b/packages/opencode/src/file/watcher.ts @@ -42,39 +42,29 @@ export namespace FileWatcher { callback: ParcelWatcher.SubscribeCallback, options: Parameters[2], ): Promise { - const timeout = new Promise((resolve) => { - setTimeout(() => { - log.error("watcher subscribe timeout", { dir }) - resolve(null) - }, SUBSCRIBE_TIMEOUT_MS) - }) - try { - return await Promise.race([watcher().subscribe(dir, callback, options), timeout]) - } catch (e) { - log.warn("watcher subscribe failed", { dir, error: e }) - return null - } - async function subscribeWithTimeout( - dir: string, - callback: ParcelWatcher.SubscribeCallback, - options: Parameters[2], - ): Promise { - let timeoutId: Timer - const timeout = new Promise((resolve) => { - timeoutId = setTimeout(() => { - log.error("watcher subscribe timeout", { dir }) - resolve(null) - }, SUBSCRIBE_TIMEOUT_MS) - }) - return Promise.race([ + let timeoutId: Timer | undefined + let resolved = false + const result = await Promise.race([ watcher() .subscribe(dir, callback, options) - .finally(() => clearTimeout(timeoutId)), - timeout, + .then((sub) => { + resolved = true + return sub + }), + new Promise((resolve) => { + timeoutId = setTimeout(() => { + if (!resolved) { + log.error("watcher subscribe timeout", { dir }) + resolve(null) + } + }, SUBSCRIBE_TIMEOUT_MS) + }), ]).catch((e) => { - log.warn("watcher subscribe failed", { dir, error: e }) + log.error("watcher subscribe failed", { dir, error: e }) return null }) + if (timeoutId) clearTimeout(timeoutId) + return result } const state = Instance.state( @@ -104,21 +94,19 @@ export namespace FileWatcher { const subs: (ParcelWatcher.AsyncSubscription | null)[] = [] const cfgIgnores = cfg.watcher?.ignore ?? [] - subs.push( - await subscribeWithTimeout(Instance.directory, subscribe, { - ignore: [...FileIgnore.PATTERNS, ...cfgIgnores], - backend, - }), - ) + const subDir = await subscribeWithTimeout(Instance.directory, subscribe, { + ignore: [...FileIgnore.PATTERNS, ...cfgIgnores], + backend, + }) + if (subDir) subs.push(subDir) - const vcsDir = await $`git rev-parse --git-dir`.quiet().nothrow().cwd(Instance.worktree).text() + const vcsDir = (await $`git rev-parse --git-dir`.quiet().nothrow().cwd(Instance.worktree).text()).trim() if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) { - subs.push( - await subscribeWithTimeout(vcsDir, subscribe, { - ignore: ["hooks", "info", "logs", "objects", "refs", "worktrees", "modules", "lfs"], - backend, - }), - ) + const subGit = await subscribeWithTimeout(vcsDir, subscribe, { + ignore: ["hooks", "info", "logs", "objects", "refs", "worktrees", "modules", "lfs"], + backend, + }) + if (subGit) subs.push(subGit) } return { subs }