From 2ececd641c494614ae8d650f85a48b7721fb6790 Mon Sep 17 00:00:00 2001 From: minhn4 Date: Fri, 10 Jul 2026 13:50:04 +0700 Subject: [PATCH 1/4] fix(ci): stop windows search-endpoints failures and cap the exerciser step The windows unit job has failed every main run since the workflow went green post-rebrand, always on "file HttpApi > serves search endpoints" at ~30s: the file-search index needs ripgrep, whose first use downloads and extracts the binary, and on GitHub-hosted Windows runners that alone can eat the whole 30s poll bound (ripgrep.test.ts already runs with 120s timeouts for the same reason). Give the poll 120s and the test an explicit 150s timeout; the poll still returns the moment the index is ready. Also cap the HttpApi exerciser step at 30 minutes: the three modes normally finish in a few, but a wedged effect-mode run (seen once on main) otherwise holds the job until GitHub's 6-hour ceiling because the step had no timeout. Co-Authored-By: Claude Fable 5 --- .github/workflows/test.yml | 4 ++ DIVERGENCES.md | 4 +- .../opencode/test/server/httpapi-file.test.ts | 60 ++++++++++--------- 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4ac471c2c1..1e00d92ca8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -78,6 +78,10 @@ jobs: - name: Run HttpApi exerciser gates if: runner.os == 'Linux' + # Fork: the three exerciser modes finish in a few minutes; without a + # step timeout a wedged run (seen once in effect mode) holds the job + # until the 6-hour GitHub ceiling. + timeout-minutes: 30 working-directory: packages/opencode run: bun run test:httpapi diff --git a/DIVERGENCES.md b/DIVERGENCES.md index b0ec9815c7..a212719230 100644 --- a/DIVERGENCES.md +++ b/DIVERGENCES.md @@ -49,9 +49,9 @@ upstream maintainers — see README → Syncing with upstream for why and how. | `.github/TEAM_MEMBERS` | Deleted | Upstream's team roster. The five workflows that read it (pr-standards, pr-management, triage, duplicate-issues, compliance-close) fetch it from a `dev` branch this fork doesn't have, so they are already inert here. | Keep deleted. | | `packages/script/src/index.ts` | Guard the `.github/TEAM_MEMBERS` read: fall back to an empty team list when the file is absent instead of throwing at module load | This module is imported by the publish path (`publish.ts`); the deleted `TEAM_MEMBERS` (row above) made `bun run release` crash with ENOENT. `Script.team` has no consumers in the fork. | Keep our `exists()` guard; take upstream's surrounding changes. | | `.github/workflows/` — 22 upstream workflows deleted (beta, close-issues, close-prs, compliance-close, containers, deploy, docs-locale-sync, docs-update, duplicate-issues, generate, nix-hashes, notify-discord, opencode, pr-management, pr-standards, publish, publish-github-action, publish-vscode, release-github-action, review, stats, triage) | Deleted | All inert or upstream-only here: gated on `anomalyco/opencode`, publish/deploy to upstream infra, need upstream secrets (`OPENCODE_APP_SECRET`, `OPENCODE_API_KEY`, marketplace/Discord/PostHog tokens), trigger only on a `dev` branch the fork lacks, or read the deleted `TEAM_MEMBERS`. | Keep deleted; delete new upstream workflows too unless they're generic self-contained CI. | -| `.github/workflows/{typecheck,test,nix-eval,storybook}.yml` | Triggers `dev` → `main` (incl. test.yml's concurrency-group ref check); runners `blacksmith-*` → `ubuntu-latest`/`windows-latest`; test.yml unit-test step `timeout-minutes` 20 → 45 | Fork's default branch is `main`; Blacksmith runners aren't installed here, so jobs queued forever. GitHub-hosted `windows-latest` needs well over 20 minutes for the spawn-heavy opencode suite. | Keep our branches + runners + timeout; take upstream's other changes. | +| `.github/workflows/{typecheck,test,nix-eval,storybook}.yml` | Triggers `dev` → `main` (incl. test.yml's concurrency-group ref check); runners `blacksmith-*` → `ubuntu-latest`/`windows-latest`; test.yml unit-test step `timeout-minutes` 20 → 45; test.yml exerciser step gains `timeout-minutes: 30` | Fork's default branch is `main`; Blacksmith runners aren't installed here, so jobs queued forever. GitHub-hosted `windows-latest` needs well over 20 minutes for the spawn-heavy opencode suite. A wedged exerciser run otherwise holds the job for GitHub's 6-hour ceiling. | Keep our branches + runners + timeouts; take upstream's other changes. | | `packages/core/test/ripgrep.test.ts` | `120_000` timeout on both `it.live` tests | First use downloads + extracts the ripgrep binary; GitHub-hosted Windows runners blow bun's 5s default. | Keep our timeouts; take upstream's test changes. | -| `packages/opencode/test/cli/run/run-process.test.ts`, `packages/opencode/test/cli/acp/lifecycle.test.ts`, `packages/opencode/test/server/httpapi-file.test.ts`, `packages/opencode/test/server/httpapi-sdk.test.ts` | Slow-runner headroom: unknown-model test `timeoutMs`/bound 15s → 30s (test timeout 60s); ACP stdin-EOF exit timeout 5s → 30s; file-search-index polls 5s → 30s | Loaded GitHub-hosted runners spawn processes and build the lazy file index slower than upstream's Blacksmith runners; 5–15s budgets flake. Hang regressions are still caught (a hang exceeds the bound; polls return as soon as ready). | Keep our numbers; take upstream's test changes. | +| `packages/opencode/test/cli/run/run-process.test.ts`, `packages/opencode/test/cli/acp/lifecycle.test.ts`, `packages/opencode/test/server/httpapi-file.test.ts`, `packages/opencode/test/server/httpapi-sdk.test.ts` | Slow-runner headroom: unknown-model test `timeoutMs`/bound 15s → 30s (test timeout 60s); ACP stdin-EOF exit timeout 5s → 30s; file-search-index poll 5s → 120s with a 150s test timeout (the search-endpoints test pays ripgrep's first-use download on Windows runners, like ripgrep.test.ts) | Loaded GitHub-hosted runners spawn processes, fetch ripgrep, and build the lazy file index slower than upstream's Blacksmith runners; 5–30s budgets flake or fail outright. Hang regressions are still caught (a hang exceeds the bound; polls return as soon as ready). | Keep our numbers; take upstream's test changes. | | `packages/opencode/test/tool/external-directory.test.ts`, `packages/opencode/test/tool/read.test.ts` | Windows path-variant tests keep the drive letter in the `alt` form (drop the `.replace(/^[A-Za-z]:/, "")` step) | A drive-less path is drive-relative: it resolves against the CWD's drive (D: on GitHub-hosted Windows) while the temp dir lives on C:, so the file genuinely isn't there. Slash + case variants still exercise normalization. | Keep our `alt` construction; take upstream's test changes. | | `packages/opencode/test/server/httpapi-exercise/environment.ts` | Exercise config/data dirs `config/opencode`,`data/opencode` → `…/codev-code`; seeds `config.json` with `{"share":"manual"}` | Follows the `global.ts` app-dir rename (row above); the fork's `share: "disabled"` default (config.ts row) would 500 the share/unshare scenarios, so the isolated environment opts back in (network stays stubbed by `OPENCODE_DISABLE_SHARE`). | Keep our dir names + seeded config; take upstream's surrounding changes. | | `sdks/vscode/package.json`, `sdks/vscode/README.md` | `description` "opencode for VS Code" → "CoDev Code for VS Code"; README title → "CoDev Code VS Code Extension" | Manifest branding. `name`, `displayName`, command IDs/titles and README body (which names actual UI elements) stay upstream — the fork doesn't publish this extension; revisit before shipping it. | Keep our description + title; take upstream's other fields/body. | diff --git a/packages/opencode/test/server/httpapi-file.test.ts b/packages/opencode/test/server/httpapi-file.test.ts index 31e77534ec..7b31f1e5dd 100644 --- a/packages/opencode/test/server/httpapi-file.test.ts +++ b/packages/opencode/test/server/httpapi-file.test.ts @@ -52,35 +52,41 @@ describe("file HttpApi", () => { expect(await status.json()).toEqual([]) }) - test("serves search endpoints", async () => { - await using tmp = await tmpdir({ git: true }) - await Bun.write(path.join(tmp.path, "hello.txt"), "needle") + test( + "serves search endpoints", + async () => { + await using tmp = await tmpdir({ git: true }) + await Bun.write(path.join(tmp.path, "hello.txt"), "needle") - const [text, symbols] = await Promise.all([ - request(FilePaths.findText, tmp.path, { pattern: "needle" }), - request(FilePaths.findSymbol, tmp.path, { query: "hello" }), - ]) - const files = await Effect.runPromise( - pollWithTimeout( - Effect.promise(async () => { - const response = await request(FilePaths.findFile, tmp.path, { query: "hello", type: "file" }) - const body = await response.json() - return body.includes("hello.txt") ? { response, body } : undefined - }), - "file search index was not ready", - // Lazy indexing on loaded GitHub-hosted Windows runners can exceed - // the 5s default; the poll returns as soon as the index is ready. - "30 seconds", - ), - ) + const [text, symbols] = await Promise.all([ + request(FilePaths.findText, tmp.path, { pattern: "needle" }), + request(FilePaths.findSymbol, tmp.path, { query: "hello" }), + ]) + const files = await Effect.runPromise( + pollWithTimeout( + Effect.promise(async () => { + const response = await request(FilePaths.findFile, tmp.path, { query: "hello", type: "file" }) + const body = await response.json() + return body.includes("hello.txt") ? { response, body } : undefined + }), + "file search index was not ready", + // Lazy indexing needs ripgrep, whose first use downloads + extracts + // the binary — on GitHub-hosted Windows runners that alone can blow + // a 30s budget (see ripgrep.test.ts). The poll returns as soon as + // the index is ready. + "120 seconds", + ), + ) - expect(text.status).toBe(200) - expect(await text.json()).toContainEqual(expect.objectContaining({ line_number: 1 })) + expect(text.status).toBe(200) + expect(await text.json()).toContainEqual(expect.objectContaining({ line_number: 1 })) - expect(files.response.status).toBe(200) - expect(files.body).toContain("hello.txt") + expect(files.response.status).toBe(200) + expect(files.body).toContain("hello.txt") - expect(symbols.status).toBe(200) - expect(await symbols.json()).toEqual([]) - }) + expect(symbols.status).toBe(200) + expect(await symbols.json()).toEqual([]) + }, + 150_000, + ) }) From ad441c5bb40b622677c81ac13026c1221191c3e5 Mon Sep 17 00:00:00 2001 From: minhn4 Date: Fri, 10 Jul 2026 13:57:17 +0700 Subject: [PATCH 2/4] minor changes --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e2208036e5..583a93bb82 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Releases publish the unscoped — therefore **public** — [`codev-code`](https: ```sh npm login # once per machine -bun release # from the repo root: builds all platforms (~15-20 min), then publishes +bun release ``` Variants: From ec4e56803adf2c972ed4ca05fa6752687b0f3a4c Mon Sep 17 00:00:00 2001 From: minhn4 Date: Fri, 10 Jul 2026 13:59:47 +0700 Subject: [PATCH 3/4] minor changes --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 583a93bb82..1810dc53a9 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Releases publish the unscoped — therefore **public** — [`codev-code`](https: ```sh npm login # once per machine -bun release +bun release # builds all platforms, then publishes ``` Variants: From e03e73ba01961b90a7ebe5378f7f8c780dcf78dc Mon Sep 17 00:00:00 2001 From: minhn4 Date: Fri, 10 Jul 2026 14:44:46 +0700 Subject: [PATCH 4/4] fix(ci): pre-install ripgrep on the Windows unit job The 120s poll bump was not enough: the search-endpoints test now fails at ~120.7s, meaning the file-search index never becomes ready at all. Root cause: the index is populated by a background ripgrep --files walk forked with Effect.orDie (core/src/filesystem/search.ts), so when the first-use download+extract of rg.exe fails or stalls on GitHub-hosted Windows runners, the failure is swallowed and findFile returns an empty list forever. Pre-installing ripgrep via choco makes binary.ts's which("rg.exe") short-circuit the download, removing the fragile step from the test path. Co-Authored-By: Claude Fable 5 --- .github/workflows/test.yml | 11 +++++++++++ DIVERGENCES.md | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1e00d92ca8..77523504f6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -63,6 +63,17 @@ jobs: turbo-${{ runner.os }}-${{ hashFiles('turbo.json', '**/package.json') }}- turbo-${{ runner.os }}- + - name: Install ripgrep (Windows) + # Fork: the file-search index is built by a background ripgrep walk + # whose errors are swallowed (core/src/filesystem/search.ts forks it + # with orDie). On GitHub-hosted Windows runners the first-use + # download+extract of rg.exe is slow/fragile, which left the index + # permanently empty and failed httpapi-file's search-endpoints test on + # every run. Pre-installing ripgrep makes binary.ts's which("rg.exe") + # short-circuit the download entirely. + if: runner.os == 'Windows' + run: choco install ripgrep --no-progress -y + - name: Run unit tests # Fork: upstream runs on Blacksmith runners; GitHub-hosted windows-latest # needs well over 20 minutes for the spawn-heavy opencode suite. diff --git a/DIVERGENCES.md b/DIVERGENCES.md index a212719230..eb2babcd7e 100644 --- a/DIVERGENCES.md +++ b/DIVERGENCES.md @@ -49,7 +49,7 @@ upstream maintainers — see README → Syncing with upstream for why and how. | `.github/TEAM_MEMBERS` | Deleted | Upstream's team roster. The five workflows that read it (pr-standards, pr-management, triage, duplicate-issues, compliance-close) fetch it from a `dev` branch this fork doesn't have, so they are already inert here. | Keep deleted. | | `packages/script/src/index.ts` | Guard the `.github/TEAM_MEMBERS` read: fall back to an empty team list when the file is absent instead of throwing at module load | This module is imported by the publish path (`publish.ts`); the deleted `TEAM_MEMBERS` (row above) made `bun run release` crash with ENOENT. `Script.team` has no consumers in the fork. | Keep our `exists()` guard; take upstream's surrounding changes. | | `.github/workflows/` — 22 upstream workflows deleted (beta, close-issues, close-prs, compliance-close, containers, deploy, docs-locale-sync, docs-update, duplicate-issues, generate, nix-hashes, notify-discord, opencode, pr-management, pr-standards, publish, publish-github-action, publish-vscode, release-github-action, review, stats, triage) | Deleted | All inert or upstream-only here: gated on `anomalyco/opencode`, publish/deploy to upstream infra, need upstream secrets (`OPENCODE_APP_SECRET`, `OPENCODE_API_KEY`, marketplace/Discord/PostHog tokens), trigger only on a `dev` branch the fork lacks, or read the deleted `TEAM_MEMBERS`. | Keep deleted; delete new upstream workflows too unless they're generic self-contained CI. | -| `.github/workflows/{typecheck,test,nix-eval,storybook}.yml` | Triggers `dev` → `main` (incl. test.yml's concurrency-group ref check); runners `blacksmith-*` → `ubuntu-latest`/`windows-latest`; test.yml unit-test step `timeout-minutes` 20 → 45; test.yml exerciser step gains `timeout-minutes: 30` | Fork's default branch is `main`; Blacksmith runners aren't installed here, so jobs queued forever. GitHub-hosted `windows-latest` needs well over 20 minutes for the spawn-heavy opencode suite. A wedged exerciser run otherwise holds the job for GitHub's 6-hour ceiling. | Keep our branches + runners + timeouts; take upstream's other changes. | +| `.github/workflows/{typecheck,test,nix-eval,storybook}.yml` | Triggers `dev` → `main` (incl. test.yml's concurrency-group ref check); runners `blacksmith-*` → `ubuntu-latest`/`windows-latest`; test.yml unit-test step `timeout-minutes` 20 → 45; test.yml exerciser step gains `timeout-minutes: 30`; test.yml Windows unit job pre-installs ripgrep via choco | Fork's default branch is `main`; Blacksmith runners aren't installed here, so jobs queued forever. GitHub-hosted `windows-latest` needs well over 20 minutes for the spawn-heavy opencode suite. A wedged exerciser run otherwise holds the job for GitHub's 6-hour ceiling. The file-search index is built by a background ripgrep walk with swallowed errors; Windows's first-use rg.exe download left it permanently empty, failing httpapi-file's search test every run. | Keep our branches + runners + timeouts; take upstream's other changes. | | `packages/core/test/ripgrep.test.ts` | `120_000` timeout on both `it.live` tests | First use downloads + extracts the ripgrep binary; GitHub-hosted Windows runners blow bun's 5s default. | Keep our timeouts; take upstream's test changes. | | `packages/opencode/test/cli/run/run-process.test.ts`, `packages/opencode/test/cli/acp/lifecycle.test.ts`, `packages/opencode/test/server/httpapi-file.test.ts`, `packages/opencode/test/server/httpapi-sdk.test.ts` | Slow-runner headroom: unknown-model test `timeoutMs`/bound 15s → 30s (test timeout 60s); ACP stdin-EOF exit timeout 5s → 30s; file-search-index poll 5s → 120s with a 150s test timeout (the search-endpoints test pays ripgrep's first-use download on Windows runners, like ripgrep.test.ts) | Loaded GitHub-hosted runners spawn processes, fetch ripgrep, and build the lazy file index slower than upstream's Blacksmith runners; 5–30s budgets flake or fail outright. Hang regressions are still caught (a hang exceeds the bound; polls return as soon as ready). | Keep our numbers; take upstream's test changes. | | `packages/opencode/test/tool/external-directory.test.ts`, `packages/opencode/test/tool/read.test.ts` | Windows path-variant tests keep the drive letter in the `alt` form (drop the `.replace(/^[A-Za-z]:/, "")` step) | A drive-less path is drive-relative: it resolves against the CWD's drive (D: on GitHub-hosted Windows) while the temp dir lives on C:, so the file genuinely isn't there. Slash + case variants still exercise normalization. | Keep our `alt` construction; take upstream's test changes. |