Skip to content

fix: Windows grep outage and test-run telemetry pollution - #1074

Open
anandgupta42 wants to merge 1 commit into
mainfrom
fix/windows-grep-and-ci-telemetry
Open

fix: Windows grep outage and test-run telemetry pollution#1074
anandgupta42 wants to merge 1 commit into
mainfrom
fix/windows-grep-and-ci-telemetry

Conversation

@anandgupta42

@anandgupta42 anandgupta42 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #1072
Closes #1073

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Two defects found while reading Azure telemetry for 2026-07-22 → 2026-08-05.

1. grep was broken for 99 Windows machines (#1072).

ripgrep's Windows release is a zip, and we extracted it by shelling out to
powershell.exe -Command Expand-Archive, with a literal "powershell.exe" string as the
fallback when neither powershell.exe nor pwsh.exe resolved. cross-spawn's
parseNonShell() sets needsShell = true whenever resolveCommand() returns undefined and
re-spawns through cmd.exe /d /s /c, so cmd.exe replied '…' is not recognized as an internal or external command. We threw that string verbatim, and since RipgrepBinary.filepath is
Effect.cached, one failed extraction killed grep for the whole session.

This extracts the zip in-process with @zip.js/zip.js, so there is no external shell to
resolve. That is the same approach packages/opencode/src/file/ripgrep.ts already uses — which
is why glob/ls/skill never showed this failure while grep did.

Three things came out of review and are included:

  • checkSignature: true. zip.js defaults CRC verification off, so a corrupt download
    decodes "successfully", gets written to the cache, and is trusted by every later session.
  • Atomic install. filepath trusts the cached binary on existence alone, so an interrupted
    write left a truncated rg.exe that was reused forever. Both paths now stage to rg.exe.tmp
    and rename. CRC cannot catch this — it is verified before the write.
  • Attributed failures. Child stderr was reported verbatim, so a shell-level failure looked
    like a tool bug. A resolve failure is also memoized, so it is re-reported on every later grep.
    Typed FS/HTTP failures are now wrapped as ripgrep binary resolve failed: ….

Upstream has the same fragility open as anomalyco/opencode#24291. Their #23457 fix only changed
how paths were passed to PowerShell (the inlined-and-escaped form we already carry); it did not
remove the dependency. upstream/dev still has the original code.

2. Test runs polluted production telemetry (#1073).

1,020 of 3,135 machine ids were test processes (provider_id="test", cli_version="local"),
which regenerate their machine id every run — inflating install and active-machine counts by
about a third. doInit() now refuses the baked-in connection string under NODE_ENV=test,
BUN_TEST, VITEST or JEST_WORKER_ID.

Keyed on test runners and deliberately not on CI: altimate-code-actions wraps this CLI, so
every run of that shipped product sets CI/GITHUB_ACTIONS, and gating on those would blind a
real product surface. bun test sets NODE_ENV=test, which covers CI and developer machines
with one condition. An explicit APPLICATIONINSIGHTS_CONNECTION_STRING is always honoured, so
suites with their own sink are unaffected; ALTIMATE_TELEMETRY_FORCE=true overrides the
default-sink refusal, and the existing opt-outs still win over both.

How did you verify your code works?

  • 8 ripgrep tests, including a layer-level test that drives filepath through the Windows
    zip path with stubbed HTTP/FS and a spawner that throws if invoked — pinning that the
    Windows path spawns nothing and that the install is staged-then-renamed. The CRC test was
    confirmed to fail without checkSignature: true.
  • 15 telemetry tests, including that CI on its own must still report, and one that stubs no
    env at all so it relies on the real runner's NODE_ENV — the assumption the gate rests on.
  • bun turbo typecheck green; marker guard green.
  • Full suites run against a main baseline worktree: packages/core 1,038 pass,
    packages/opencode 11,014 pass, with zero failures unique to this branch (the remaining
    MCP/plugin failures reproduce on main).

Not verified on a real Windows host — I have no Windows runner. The zip decoding, install and
no-spawn behaviour are covered by tests; the end-to-end "grep works on a machine without
PowerShell on PATH" claim is inferred from the call graph, not executed.

Known follow-ups, deliberately not in this PR: bundling rg so there is no runtime download
(upstream wants this too — anomalyco/opencode#31734), unifying the two ripgrep resolvers, and
making filepath cache only successes so a transient failure stops poisoning the session.

Screenshots / recordings

Not a UI change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Summary by cubic

Fixes Windows grep outages by extracting ripgrep in-process (no PowerShell) and installing it atomically, and stops automated tests from sending production telemetry. Adds a Windows CI E2E that resolves and runs rg.exe with PowerShell removed from PATH (fixes #1072, #1073).

Written for commit 83961ac. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved ripgrep installation on Windows with in-process extraction, CRC validation, atomic staging/rename, and better contextual errors.
    • Enhanced ripgrep process failure reporting to always include normalized stderr and exit codes (or a clear “no output” fallback).
  • Telemetry

    • Automated test runs now avoid the baked-in production App Insights sink; CI remains enabled.
    • Explicit connection strings and ALTIMATE_TELEMETRY_FORCE=true still enable telemetry, with disable overrides taking precedence.
  • Documentation

    • Added documentation for telemetry test-run exclusions and override behavior.
  • Tests / CI

    • Added Windows ripgrep end-to-end coverage and automated telemetry tests.
    • Added a Windows-only CI job for ripgrep resolution E2E.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change adds in-process Windows ripgrep ZIP extraction, staged executable installation, clearer ripgrep errors, and automated test-run telemetry suppression. Explicit telemetry sinks and force overrides remain supported. Tests, CI, and documentation cover both changes.

Changes

Ripgrep binary reliability

Layer / File(s) Summary
In-process ZIP extraction
packages/core/package.json, packages/core/src/ripgrep/binary.ts, packages/core/test/ripgrep-windows.test.ts
The core package decodes ZIP archives in process, validates rg.exe, supports root and nested layouts, and tests invalid archives and executables.
Staged binary installation and resolution
packages/core/src/ripgrep/binary.ts, packages/core/src/ripgrep.ts, packages/core/test/ripgrep-windows.test.ts, script/windows-ripgrep-e2e.ts, .github/workflows/ci.yml
ZIP and tarball downloads use separate paths. Installation stages files before renaming them. Tarball failures and ripgrep exit errors include contextual messages. Windows tests and CI validate extraction without PowerShell, atomic installation, execution, and cache reuse.

Automated-run telemetry controls

Layer / File(s) Summary
Automated-run detection and telemetry gating
packages/opencode/src/altimate/telemetry/index.ts, packages/opencode/test/telemetry/automated-run.test.ts, docs/docs/reference/telemetry.md
Telemetry detects test-runner markers and NODE_ENV=test, skips the default sink for automated runs, and preserves explicit sinks, force overrides, and disabled-setting precedence. Tests and documentation cover the supported environment combinations.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DownloadSource
  participant RipgrepBinary
  participant ZipReader
  participant FileSystem
  DownloadSource->>RipgrepBinary: return ZIP or tarball bytes
  RipgrepBinary->>ZipReader: decode ZIP bytes
  ZipReader-->>RipgrepBinary: return validated rg.exe bytes
  RipgrepBinary->>FileSystem: stage and atomically install executable
Loading
sequenceDiagram
  participant TestRunner
  participant Telemetry
  participant AppInsights
  TestRunner->>Telemetry: initialize with environment markers
  Telemetry->>Telemetry: apply sink, force, and disabled settings
  Telemetry->>AppInsights: send or suppress telemetry
Loading

Poem

I’m a rabbit with a ZIP in my hat,
No PowerShell path can stop me at that.
Test runs sleep; explicit sinks may sing.
Atomic installs guard every ring.
Clear errors guide the binary flight—
Hop, hop, shipped right!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address both linked issues through in-process ZIP extraction, atomic installation, failure attribution, and test-run telemetry suppression with required overrides.
Out of Scope Changes check ✅ Passed The dependency, documentation, tests, Windows E2E script, and CI job directly support the two linked issue objectives.
Title check ✅ Passed The title clearly and concisely identifies both primary fixes: the Windows grep outage and test-run telemetry pollution.
Description check ✅ Passed The description includes the required issue, change type, implementation details, verification results, limitations, screenshots section, and checklist.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/windows-grep-and-ci-telemetry

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/ripgrep.ts`:
- Around line 143-147: Update the ripgrep failure handling around failure() so
the formatted exit-code/stderr message is passed as the Error message string,
while the original ripgrep failure object is supplied as ErrorOptions.cause when
available. Preserve the existing stderr attribution and fallback text in the
formatted message.

In `@packages/core/src/ripgrep/binary.ts`:
- Around line 103-111: Serialize the installation flow in install around each
target using a process-safe per-target lock, covering both ZIP and tar
installation paths. Create a unique staged filename inside the lock instead of
the shared `${target}.tmp`, and keep cleanup, writing, chmod, Windows removal,
and rename within the locked section so concurrent resolvers cannot invalidate
the returned binary.

In `@packages/opencode/test/telemetry/automated-run.test.ts`:
- Around line 71-75: Make the telemetry suite identified by “telemetry:
automated runs never reach the production sink” run serially, using the test
framework’s supported serial describe mechanism. Preserve the existing afterEach
cleanup and test behavior while preventing concurrent access to process.env,
global fetch, and the shared Telemetry instance.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e48ec458-dbbf-4686-af56-826ae01ff4e5

📥 Commits

Reviewing files that changed from the base of the PR and between 03b9459 and bc7ec7a.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • docs/docs/reference/telemetry.md
  • packages/core/package.json
  • packages/core/src/ripgrep.ts
  • packages/core/src/ripgrep/binary.ts
  • packages/core/test/ripgrep-windows.test.ts
  • packages/opencode/src/altimate/telemetry/index.ts
  • packages/opencode/test/telemetry/automated-run.test.ts

Comment thread packages/core/src/ripgrep.ts
Comment thread packages/core/src/ripgrep/binary.ts Outdated
Comment thread packages/opencode/test/telemetry/automated-run.test.ts Outdated
@kilo-code-bot

kilo-code-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Incremental pass over 83961acb (since 334adfe0). One file changed; it corrects a flawed negative control in the Windows ripgrep E2E — no issues on changed code.

  • packages/core/script/windows-ripgrep-e2e.ts — the prior commit added a hard negative control that required the old PowerShell spawn to fail (if (old.status === 0) fail(...)), on the theory that an empty PATH reproduces the affected locked-down machines. That premise is wrong: cross-spawn falls back to cmd.exe /d /s /c, and Windows process creation searches beyond PATH (System32, the App Paths registry key, the caller's directory), so powershell.exe still launches on a stock GitHub runner even though which() cannot see it. The hard assertion would therefore have failed the E2E on the very CI it runs in, while proving nothing about the fix. This commit correctly converts it to an informational control probe: status === 0 logs a note that the affected environment is not reproduced here; otherwise it logs a bonus. Neither path gates the run. The 16-line comment above the probe accurately documents the cross-spawn needsShell/cmd.exe fallback and the Windows search order, and points to test/ripgrep-windows.test.ts as the place the no-spawn guarantee is actually established structurally. The status: null (spawn-error/signal) case falls through to the else branch and renders via the status=${control.status} fallback, which is correct. The PASS message is updated to state precisely what the job does and does not establish. The retained probe is justified CI observability, not redundant — no simplification opportunity worth flagging.
Files Reviewed (1 file)
  • packages/core/script/windows-ripgrep-e2e.ts
Previous Review Summaries (8 snapshots, latest commit 334adfe)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 334adfe)

Status: No Issues Found | Recommendation: Merge

Incremental pass over 334adfe0 (since 8d16513). One file changed; it strengthens the existing Windows ripgrep E2E with a negative control — no issues on changed code.

  • packages/core/script/windows-ripgrep-e2e.ts — adds a negative control after the happy-path assertions: it dynamically imports cross-spawn (a confirmed packages/core dependency, also used by the shipped CrossSpawnSpawner) and spawns powershell.exe with no shell option — exactly the parseNonShell/cmd.exe-fallback path the old extraction took. It then requires that spawn to fail (old.status !== 0), so the run is provably non-vacuous: if the environment could still resolve PowerShell, the fix would prove nothing. The which("powershell.exe") guard earlier checks PATH resolution; this additionally exercises the real spawn mechanism, so it is complementary rather than redundant. old.status === 0 is the only pass condition, so status: null (spawn-error / signal) correctly counts as the expected failure, and old.error?.message is folded into the log line. The two-line console.log PASS message is updated to reflect the new assertion. No redundancy or simplification opportunity worth flagging.
Files Reviewed (1 file)
  • packages/core/script/windows-ripgrep-e2e.ts

Previous review (commit 8d16513)

Status: No Issues Found | Recommendation: Merge

Incremental pass over 8d16513 (since e105f17). Two files changed; both are a clean refactor with no issues.

  • .github/workflows/ci.yml — the Resolve ripgrep with PowerShell unavailable step gains working-directory: packages/core plus an explanatory comment. effect (and the other deps the e2e script imports) are packages/core dependencies, not root deps, so resolving from packages/core is the correct, robust fix. Verified the bun run script/windows-ripgrep-e2e.ts path resolves to packages/core/script/windows-ripgrep-e2e.ts, and the step still sits inside its altimate_change start/end marker block.
  • packages/core/script/windows-ripgrep-e2e.ts (moved from repo-root script/) — relative imports corrected (../packages/core/src/...../src/..., all three resolving under packages/core/src/), and the previously duplicated Effect.gen + Effect.provide + Effect.runPromise block is extracted into a single resolveBinary() helper now reused by both the cold-cache resolve and the cache-hit re-resolve. Behavior-preserving: the first-resolve error path moved from .catch() to an equivalent try/catch around the helper, and fail() (: never) keeps resolved definitely-assigned. The cache lives on disk (Global.Path.bin), so the second call still hits the on-disk cache as the assertion intends.

No redundancy remains to flag; the change itself is the simplification. No new issues on changed code.

Files Reviewed (2 files)
  • .github/workflows/ci.yml
  • packages/core/script/windows-ripgrep-e2e.ts

Previous review (commit e105f17)

Status: No Issues Found | Recommendation: Merge

Incremental pass over commit e105f17 (since 6851f9d). One file changed: the windows-ripgrep-e2e CI job's install step switched from bun install to bun install --ignore-scripts, with a clear comment explaining why.

Verified the rationale is sound:

  • The dependency graph exercised by script/windows-ripgrep-e2e.ts is entirely pure-JS — effect, @effect/platform-node, @zip.js/zip.js, which, cross-spawn, xdg-basedir — none require a lifecycle/build script, so --ignore-scripts does not change what the check exercises.
  • The skipped native build (tree-sitter-powershell) is not on the ripgrep resolution path, so coverage of the Windows: grep broken for 99 machines — ripgrep zip extraction shells out to PowerShell #1072 fix (in-process zip extraction, atomic staged install, no process spawn) is unaffected.
  • No inconsistency with the sibling windows-installer job — it runs Pester via PowerShell and never installs the JS dependency tree.

No new issues on changed code.

Files Reviewed (1 file)
  • .github/workflows/ci.yml

Previous review (commit 6851f9d)

Status: No Issues Found | Recommendation: Merge

Incremental pass over commit 6851f9d (since 59e0482). Two new files add the real-Windows E2E guard for the #1072 fix that the PR description explicitly noted was missing: a windows-ripgrep-e2e CI job and script/windows-ripgrep-e2e.ts.

The CI job mirrors every existing convention in ci.yml — same if: needs.changes.outputs.typescript == 'true' || github.event_name == 'push' gate (the typescript output exists on the changes job), the same pinned actions/checkout@v4 and setup-bun@v2 SHAs, bun-version: "1.3.14", and a sane timeout-minutes: 15.

The E2E script correctly reproduces the outage condition and then asserts each invariant the PR claims:

  • Scenario reproduced. PowerShell is stripped from PATH via a powershell-entry filter that deliberately keeps System32 (so cmd.exe/Windows still work), and a guard asserts which("powershell.exe") / which("pwsh.exe") both return null — otherwise the run is treated as vacuous and fails.
  • Cold cache. XDG_CACHE_HOME/LOCALAPPDATA are redirected to a temp dir before importing Global (which computes Path.bin at module load), and a pre-check asserts rg.exe is absent so download+extract are actually exercised.
  • No spawn / atomic install. After resolve it asserts no .tmp staging files survive (validates the stage→rename install), the binary exists with a plausible size, rg --version executes, and a real search returns matches. The self-referential NEEDLE_MARKER search is robust because the marker also appears in a source comment.
  • Cache hit. A second filepath resolve returns the same path.

The imported surface matches the current code: which (returns string | null, appends Global.Path.bin), Global.Path.bin, and RipgrepBinary.Service / defaultLayer / filepath (the Effect.cached resolver whose Windows branch uses unzipExecutable + atomic install). No new issues on changed code.

Files Reviewed (2 files)
  • .github/workflows/ci.yml
  • script/windows-ripgrep-e2e.ts

Previous review (commit 59e0482)

Status: No Issues Found | Recommendation: Merge

Incremental pass over commit 59e0482 (since 09af0a5). The change converts two describe blocks to describe.serial to prevent test-isolation leaks under bun test --concurrent (REVIEW.md focus area #11). Both are correct and well-documented: asWindows mutates process.platform/process.arch process-wide, and the telemetry suite mutates process.env, global fetch, and the module-level Telemetry singleton. describe.serial is supported by bun:test.

A prior warning on packages/core/src/ripgrep/binary.ts was re-verified against current HEAD: the HTTP download path is now attributed at binary.ts:188-195 (ripgrep download failed from ${url}: ...), so that finding no longer reproduces.

Files Reviewed (2 changed files)
  • packages/core/test/ripgrep-windows.test.ts
  • packages/opencode/test/telemetry/automated-run.test.ts

Previous review (commit 09af0a5)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (4 files)
  • packages/core/src/ripgrep/binary.ts
  • packages/core/test/ripgrep-windows.test.ts
  • packages/opencode/src/altimate/telemetry/index.ts
  • packages/opencode/test/telemetry/automated-run.test.ts

Previous review (commit c7b415c)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/core/src/ripgrep/binary.ts 189 Removing the outer .pipe(Effect.mapError(...)) drops ripgrep attribution from the HTTP download path; memoized by Effect.cached, the failure is re-surfaced on every later grep.
Files Reviewed (1 file)
  • packages/core/src/ripgrep/binary.ts - 1 issue

Fix these issues in Kilo Cloud

Previous review (commit bc7ec7a)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (7 files)
  • packages/core/src/ripgrep/binary.ts
  • packages/core/src/ripgrep.ts
  • packages/opencode/src/altimate/telemetry/index.ts
  • packages/core/test/ripgrep-windows.test.ts
  • packages/opencode/test/telemetry/automated-run.test.ts
  • packages/core/package.json
  • docs/docs/reference/telemetry.md

Notes:

Both fixes are well-engineered and well-tested.

  • Windows grep (ripgrep/binary.ts): in-process @zip.js/zip.js decode replaces the PowerShell Expand-Archive shell-out that broke 99 Windows machines. CRC verification (checkSignature: true) prevents a corrupt download from being cached and trusted; the empty-entry guard rejects zero-byte binaries; atomic install (stage to rg.exe.tmp → rename) prevents truncated-cache reuse; and Effect.mapError attributes typed HTTP/FS failures as ripgrep binary resolve failed: … so a memoized resolve failure no longer reads as a tool bug. No zip-slip risk — entry filenames are only used for the rg.exe regex match, never to build filesystem paths.
  • Telemetry gate (telemetry/index.ts): isAutomatedRun() suppresses the baked-in production sink under NODE_ENV=test/BUN_TEST/VITEST/JEST_WORKER_ID. Precedence is correct and matches the docs: ALTIMATE_TELEMETRY_DISABLED and the config opt-out still win, an explicit APPLICATIONINSIGHTS_CONNECTION_STRING is always honoured, and ALTIMATE_TELEMETRY_FORCE opts back in. CI-alone deliberately still reports.
  • Tests pin the key invariants: the Windows install path spawns nothing and installs atomically; CRC corruption fails without checkSignature; and CI markers alone still ship while test-runner markers suppress.

No blocking findings on changed code.


Reviewed by glm-5.2 · Input: 26.4K · Output: 4.8K · Cached: 201.9K

Review guidance: REVIEW.md from base branch main

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/src/ripgrep/binary.ts">

<violation number="1" location="packages/core/src/ripgrep/binary.ts:110">
P2: A Windows install can delete the only usable cached `rg.exe` and then fail to publish the new binary because `remove(target)` is performed before `rename` and its failure is ignored. A Windows-safe replacement or cross-process install lock would preserve the old target until the new one is committed.</violation>

<violation number="2" location="packages/core/src/ripgrep/binary.ts:186">
P2: Some binary-resolution filesystem failures still escape as defects instead of the new `ripgrep binary resolve failed: …` error because the preceding `Effect.orDie` calls run before this `mapError`. Keeping these operations in the typed error channel (or mapping before converting to defects) would make the resolver's error contract consistent.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/core/src/ripgrep/binary.ts Outdated
// HTTP failures do not — and a resolve failure is memoized by Effect.cached, so it is
// reported on every subsequent grep of the session. An unattributed message there is
// exactly what made the Windows outage read as a tool bug rather than a binary problem.
Effect.mapError((cause) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Some binary-resolution filesystem failures still escape as defects instead of the new ripgrep binary resolve failed: … error because the preceding Effect.orDie calls run before this mapError. Keeping these operations in the typed error channel (or mapping before converting to defects) would make the resolver's error contract consistent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/src/ripgrep/binary.ts, line 186:

<comment>Some binary-resolution filesystem failures still escape as defects instead of the new `ripgrep binary resolve failed: …` error because the preceding `Effect.orDie` calls run before this `mapError`. Keeping these operations in the typed error channel (or mapping before converting to defects) would make the resolver's error contract consistent.</comment>

<file context>
@@ -114,11 +164,31 @@ export namespace RipgrepBinary {
+            // HTTP failures do not — and a resolve failure is memoized by Effect.cached, so it is
+            // reported on every subsequent grep of the session. An unattributed message there is
+            // exactly what made the Windows outage read as a tool bug rather than a binary problem.
+            Effect.mapError((cause) => {
+              const message = cause instanceof Error ? cause.message : String(cause)
+              return /ripgrep/i.test(message) ? cause : new Error(`ripgrep binary resolve failed: ${message}`)
</file context>

Comment thread packages/core/src/ripgrep/binary.ts Outdated
Comment thread packages/core/src/ripgrep/binary.ts Outdated
if (process.platform !== "win32") yield* fs.chmod(staged, 0o755)
// Windows rename fails if the destination exists; the old binary is already unusable
// by the time we are reinstalling, so dropping it first is safe.
if (process.platform === "win32") yield* fs.remove(target, { force: true }).pipe(Effect.ignore)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: A Windows install can delete the only usable cached rg.exe and then fail to publish the new binary because remove(target) is performed before rename and its failure is ignored. A Windows-safe replacement or cross-process install lock would preserve the old target until the new one is committed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/src/ripgrep/binary.ts, line 110:

<comment>A Windows install can delete the only usable cached `rg.exe` and then fail to publish the new binary because `remove(target)` is performed before `rename` and its failure is ignored. A Windows-safe replacement or cross-process install lock would preserve the old target until the new one is committed.</comment>

<file context>
@@ -48,34 +93,40 @@ export namespace RipgrepBinary {
+        if (process.platform !== "win32") yield* fs.chmod(staged, 0o755)
+        // Windows rename fails if the destination exists; the old binary is already unusable
+        // by the time we are reinstalling, so dropping it first is safe.
+        if (process.platform === "win32") yield* fs.remove(target, { force: true }).pipe(Effect.ignore)
+        yield* fs.rename(staged, target)
+      })
</file context>

Comment thread packages/opencode/test/telemetry/automated-run.test.ts
@anandgupta42
anandgupta42 force-pushed the fix/windows-grep-and-ci-telemetry branch from bc7ec7a to c7b415c Compare August 5, 2026 06:48
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c7b415cfae

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1634 to +1638
function isAutomatedRun(): boolean {
if (process.env.ALTIMATE_TELEMETRY_FORCE === "true") return false
if (process.env.NODE_ENV === "test") return true
return Boolean(process.env.BUN_TEST || process.env.VITEST || process.env.JEST_WORKER_ID)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Move the private helper out of the namespace

For this new private helper, keeping it inside Telemetry adds more code to an export namespace; the package guidance says namespace-private helpers should be non-exported top-level declarations so they remain usable by the file without being part of the namespace pattern. Move isAutomatedRun to top level and call it from doInit instead.

AGENTS.md reference: packages/opencode/AGENTS.md:L42-L44

Useful? React with 👍 / 👎.

Comment on lines +1634 to +1638
function isAutomatedRun(): boolean {
if (process.env.ALTIMATE_TELEMETRY_FORCE === "true") return false
if (process.env.NODE_ENV === "test") return true
return Boolean(process.env.BUN_TEST || process.env.VITEST || process.env.JEST_WORKER_ID)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Move the private helper out of the namespace

For this new private helper, keeping it inside Telemetry adds more code to an export namespace; the package guidance says namespace-private helpers should be non-exported top-level declarations so they remain usable by the file without being part of the namespace pattern. Move isAutomatedRun to top level and call it from doInit instead.

Useful? React with 👍 / 👎.

Comment thread packages/core/src/ripgrep/binary.ts Outdated
// Staging next to the target keeps the rename within one filesystem, so it is atomic.
const install = Effect.fnUntraced(
function* (target: string, write: (staged: string) => Effect.Effect<void, Error>) {
const staged = `${target}.tmp`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use a unique staging path for rg installs

When two processes hit a cold cache at the same time, they both use the same rg(.exe).tmp staging path. One installer can delete or rename the other installer’s temp file; on Windows the later installer can also remove the just-installed target before its own rename, so filepath can fail or leave no cached binary. Use a per-process/random temp name or serialize installs before the final rename.

Useful? React with 👍 / 👎.

const exit = await run(Effect.result(RipgrepBinary.unzipExecutable(zip)))

expect(exit._tag).toBe("Failure")
expect(String((exit as { failure: Error }).failure.message)).toContain("did not contain rg.exe")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Inspect the Effect failure cause in the new tests

When unzipExecutable fails, Effect.result(...) returns a Failure exit with the error under exit.cause, not a failure property (the existing tests in this repo check exit.cause). In the missing/empty rg.exe cases this line dereferences undefined.message, so the regression tests fail for the wrong reason instead of asserting the intended diagnostic.

Useful? React with 👍 / 👎.

}
// altimate_change end
return target
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[WARNING]: HTTP download failures lost their ripgrep attribution

The outer .pipe(Effect.mapError(...)) that wrapped this resolve effect was removed, so failures outside the install step are no longer prefixed. In particular the HTTP download (the per-call mapError at line 172 only normalizes to Error — it doesn't add "ripgrep"), so a network error, GitHub 403/404, or proxy block surfaces as a raw HttpClientError/ResponseError. Because filepath is Effect.cached, that failure is memoized and re-surfaced on every later grep of the session — the exact "reads as a tool bug" case this attribution exists for. The install/write path is still covered; the download path is not. The PR description still claims "Typed FS/HTTP failures are now wrapped as ripgrep binary resolve failed: …."


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@anandgupta42

Copy link
Copy Markdown
Contributor Author

Thanks — addressed in the latest push. Triage below, including two findings I'm rejecting with evidence.

Fixed

Concurrent installs shared ${target}.tmp — flagged independently by CodeRabbit, cubic (×2) and Codex. Valid and the most serious of the batch. Staging is now unique per attempt (rg.exe.<pid>.<rand>.tmp), so two cold-cache processes can no longer publish each other's partial download.

Also fixed the related Windows hazard cubic raised: remove(target) ran before rename, with its failure ignored, so a failing rename could leave no binary at all. It now attempts the rename first and only removes-and-retries if that fails — a rename failing for any other reason leaves the existing binary untouched. Staged files are cleaned up on error via Effect.onError.

HTTP download failures lost ripgrep attribution (@kilo-code-bot) — correct, and a real regression from moving attribution into install. A bare HttpClientError is now wrapped as ripgrep download failed from <url>: …. This matters because filepath is Effect.cached, so a proxy 403 gets re-reported on every later grep in the session. The PR description overstated the previous state; it's accurate now.

isAutomatedRun inside the namespace (@chatgpt-codex-connector) — correct per packages/opencode/AGENTS.md:42-44; moved to a non-exported top-level declaration.

Tests wrote to the real home directory (cubic) — correct. Non-suppressed cases reach doInit's machine-id block, which uses os.homedir() directly (telemetry/index.ts:1730), so the suite minted a real ~/.altimate/machine-id and assertions depended on pre-existing state. HOME/USERPROFILE now point at a temp dir per case, removed in finally.

Rejecting — verified against the code

failure() produces [object Object] (CodeRabbit, ripgrep.ts:147) — not correct. Error in that module is not the global Error; ripgrep.ts:43 declares:

export class Error extends Schema.TaggedErrorClass<Error>()("Ripgrep.Error", {
  message: Schema.String,
  cause: Schema.optional(Schema.Defect),
}) {}

It takes a struct, and message is a real string field. The verification script in the review ran new Error({message, cause}) in a bare Node process, which resolves the global Error — hence [object Object]. Constructing the actual class gives message === "ripgrep failed with code 3: boom". Applying the proposed diff would break the typed error.

Effect.result exposes the error on exit.cause, not exit.failure (Codex, ripgrep-windows.test.ts:83) — not correct for this Effect version (4.0.0-beta.74):

Effect.runPromise(Effect.result(Effect.fail(new Error("boom"))))
→ keys: failure | _tag: Failure | failure?.message: "boom" | cause: undefined

The tests pass; had .failure been undefined they'd have thrown on the property access rather than reporting the intended diagnostic.

Effect.orDie before the outer mapError (cubic, binary.ts:186) — moot now. That outer wrapper no longer exists; attribution happens at the download and install sites instead. The two .orDie calls on fs.isFile are pre-existing upstream behaviour and unchanged by this PR.

Not doing here

A cross-process lock (CodeRabbit's stronger suggestion) is deliberately out of scope. Unique staging plus atomic rename removes the corruption window; the remaining race is two processes each installing a valid, identical binary and one rename winning — which is harmless. A real lock needs a cross-platform advisory-lock primitive we don't have, and this PR is a hotfix for a live Windows outage.

Verification: turbo typecheck green, marker guard green, packages/core 1,038 pass, packages/opencode suite unchanged — no failures beyond the pre-existing set on main.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create an environment for this repo.

@anandgupta42
anandgupta42 force-pushed the fix/windows-grep-and-ci-telemetry branch from c7b415c to 09af0a5 Compare August 5, 2026 07:51
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@anandgupta42
anandgupta42 force-pushed the fix/windows-grep-and-ci-telemetry branch from 09af0a5 to 59e0482 Compare August 5, 2026 08:52
@anandgupta42

Copy link
Copy Markdown
Contributor Author

One more addressed.

Serialize the shared-state test suites (CodeRabbit) — valid, applied. Both new suites mutate process-wide state: the telemetry one touches process.env, the global fetch and the module-level Telemetry singleton (which afterEach shuts down); the ripgrep layer test redefines process.platform/process.arch. Both describe blocks are now describe.serial (confirmed available in the pinned Bun 1.3.14).

To be precise about the risk: Bun runs a file's tests sequentially by default and this repo does not pass --concurrent, so nothing was actually racing today. The value is that the constraint is now encoded rather than implicit — adding --concurrent later would otherwise have turned these into intermittent failures with a confusing signature.

Verification after this change: turbo typecheck green, marker guard green, packages/core 1,038 pass, packages/opencode 11,014 pass — no failures beyond the pre-existing set on main.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@anandgupta42
anandgupta42 force-pushed the fix/windows-grep-and-ci-telemetry branch from 59e0482 to 6851f9d Compare August 5, 2026 09:37
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Line 392: Update the changes filter configuration used by the TypeScript job
condition to include script/windows-ripgrep-e2e.ts in
changes.outputs.typescript, ensuring the job runs when that E2E script changes
while preserving the existing push behavior.

In `@script/windows-ripgrep-e2e.ts`:
- Around line 22-25: Ensure the test body and any failing execFileSync calls in
the Windows ripgrep E2E flow are wrapped in try/finally so the temporary cache
directory is always removed. Move the cleanup currently at the post-test path
into finally, and update fail() to report failure without directly calling
process.exit before cleanup executes.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9fc22fc1-90ce-4a54-99c8-594069523143

📥 Commits

Reviewing files that changed from the base of the PR and between 03b9459 and 6851f9d.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • .github/workflows/ci.yml
  • docs/docs/reference/telemetry.md
  • packages/core/package.json
  • packages/core/src/ripgrep.ts
  • packages/core/src/ripgrep/binary.ts
  • packages/core/test/ripgrep-windows.test.ts
  • packages/opencode/src/altimate/telemetry/index.ts
  • packages/opencode/test/telemetry/automated-run.test.ts
  • script/windows-ripgrep-e2e.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • docs/docs/reference/telemetry.md
  • packages/core/package.json
  • packages/core/test/ripgrep-windows.test.ts
  • packages/core/src/ripgrep.ts
  • packages/opencode/test/telemetry/automated-run.test.ts
  • packages/opencode/src/altimate/telemetry/index.ts
  • packages/core/src/ripgrep/binary.ts

Comment thread .github/workflows/ci.yml
windows-ripgrep-e2e:
name: Windows ripgrep E2E
needs: changes
if: needs.changes.outputs.typescript == 'true' || github.event_name == 'push'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Run this job when its E2E script changes.

changes.outputs.typescript does not include script/windows-ripgrep-e2e.ts. A pull request that changes only this script skips the job. Add this script to the typescript filter, or add a dedicated output for it.

Proposed fix
             typescript:
+              - 'script/windows-ripgrep-e2e.ts'
               # altimate_change start — upstream_fix: typecheck every declared TypeScript workspace
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml at line 392, Update the changes filter
configuration used by the TypeScript job condition to include
script/windows-ripgrep-e2e.ts in changes.outputs.typescript, ensuring the job
runs when that E2E script changes while preserving the existing push behavior.

Comment on lines +22 to +25
function fail(message: string): never {
console.error(`FAIL: ${message}`)
process.exit(1)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Clean up the temporary cache on failure.

fail() calls process.exit(1), so it bypasses line 110. A thrown execFileSync error also bypasses line 110. Wrap the test body in try/finally, and remove the directory in finally. Avoid direct process.exit() before cleanup runs.

Also applies to: 110-110

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@script/windows-ripgrep-e2e.ts` around lines 22 - 25, Ensure the test body and
any failing execFileSync calls in the Windows ripgrep E2E flow are wrapped in
try/finally so the temporary cache directory is always removed. Move the cleanup
currently at the post-test path into finally, and update fail() to report
failure without directly calling process.exit before cleanup executes.

Source: Coding guidelines

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6851f9de94

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +61 to +62
if (process.env.NODE_ENV === "test") return true
return Boolean(process.env.BUN_TEST || process.env.VITEST || process.env.JEST_WORKER_ID)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Detect bun tests when NODE_ENV is pre-set

This still sends to the production sink when Bun tests are launched with NODE_ENV already set, e.g. NODE_ENV=production bun test: Bun documents that it sets NODE_ENV to test unless it is already set, and in that case BUN_TEST is undefined, so this function returns false and doInit() falls through to DEFAULT_CONNECTION_STRING. Add a repo-owned test marker around the bun test invocations or another Bun-specific guard so pre-set environments do not keep polluting production telemetry.

Useful? React with 👍 / 👎.

@anandgupta42
anandgupta42 force-pushed the fix/windows-grep-and-ci-telemetry branch from 6851f9d to e105f17 Compare August 5, 2026 09:50
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
- - - - - - - - - - - - - - - - - - - - - - - - -
                    AIRECEIPTS                    
             1 session behind this PR             

claude-opus-5...................175,830,684 tokens
  SUBAGENTS (15).........................≥ $3.4889
--------------------------------------------------
TOTAL priced.............................≥ $3.4889
TOTAL unpriced..................175,830,684 tokens
  standard API-equivalent floor; not an invoice
  counted: 1 session + 15 subagents
  cache served 99% of input tokens
  full receipts + session ids: section below
- - - - - - - - - - - - - - - - - - - - - - - - -
                npx aireceipts-cli                
         github.com/anandgupta42/receipts         
- - - - - - - - - - - - - - - - - - - - - - - - -
full receipts (1 session)
session id scope turns time tokens in / out cached
orchestrator 683a08fa entire session (slice unavailable) 457 10h 40m 850 / 314k >99%

orchestrator · 683a08fa

- - - - - - - - - - - - - - - - - - - - - - - - -
                    AIRECEIPTS                    
         “Analyze Azure telemetry issues”         
  Claude Code · Aug 04 2026 23:40 UTC · 10h 40m   
                claude-opus-5 100%                
        cache served >99% of input tokens         

pre-edit: 2% of tokens (36/457 turns)
  (share before the first named edit tool)

Bash..................114,341,588 tok  (364 calls)
Edit....................28,998,511 tok  (76 calls)
TaskOutput..............16,830,250 tok  (34 calls)
(thinking/reply).........4,798,736 tok  (11 turns)
Write....................4,435,818 tok  (12 calls)
Read......................3,619,356 tok  (8 calls)
Agent....................1,241,550 tok  (15 calls)
ToolSearch..................737,303 tok  (3 calls)
TaskList.....................435,963 tok  (1 call)
TaskStop....................391,610 tok  (9 calls)
--------------------------------------------------
TOTAL..............................175,830,684 tok
no price table matched
- - - - - - - - - - - - - - - - - - - - - - - - -
                npx aireceipts-cli                
         github.com/anandgupta42/receipts         
- - - - - - - - - - - - - - - - - - - - - - - - -
subagents (15)
subagent cost
agent-aglm-reviewer-702f70ccae59e3e8 · claude-sonnet-5 ≥ $0.5405
agent-agemini-reviewer-3cce9c3ba5e51cbe · claude-sonnet-5 ≥ $0.3922
agent-agpt-reviewer-71fb203724bc62c1 · claude-sonnet-5 ≥ $0.3835
agent-agrok-converge-26aa059aa135e5b7 · claude-sonnet-5 ≥ $0.3021
agent-amimo-reviewer-3c3215e6d7373c0d · claude-sonnet-5 ≥ $0.2653
agent-aglm-converge-0afa307f0a530a99 · claude-sonnet-5 ≥ $0.2264
agent-aqwen-reviewer-11144dff06f113d3 · claude-sonnet-5 ≥ $0.2228
agent-akimi-reviewer-7c4c7e2168076ff5 · claude-sonnet-5 ≥ $0.1852
agent-agrok-reviewer-f684f16a0a212d21 · claude-sonnet-5 ≥ $0.1676
agent-aqwen-reviewer-c72d3ec92b364a4b · claude-sonnet-5 ≥ $0.1607
agent-agrok-reviewer-747a9bd7129a2672 · claude-sonnet-5 ≥ $0.1421
agent-akimi-reviewer-96624d90c821abe9 · claude-sonnet-5 ≥ $0.1394
agent-adeepseek-reviewer-44ca6585ba35900a · claude-sonnet-5 ≥ $0.1222
agent-aminimax-reviewer-c8346e3fa415d7ce · claude-sonnet-5 ≥ $0.1202
agent-aglm5-reviewer-113ee1caa7f489cd · claude-sonnet-5 ≥ $0.1180

Generated by aireceipts

@anandgupta42
anandgupta42 force-pushed the fix/windows-grep-and-ci-telemetry branch 2 times, most recently from 8d16513 to 334adfe Compare August 5, 2026 10:21

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 334adfe0f9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +119 to +120
yield* fs.remove(target, { force: true }).pipe(Effect.ignore)
yield* fs.rename(staged, target)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid deleting a peer-installed rg binary

When two Windows processes resolve a cold cache concurrently, the first one can publish target after this process already passed the fs.isFile(target) check; if this rename then fails because the destination appeared or is locked by the first process, this retry deletes that valid binary before attempting its own rename. Although the new staging names are unique, the current code still removes a concurrently published target, so a peer can hit ENOENT while spawning rg.exe or this process can memoize a resolve failure when the existing binary was actually usable; prefer treating an existing target as success or serializing the install instead of removing it.

Useful? React with 👍 / 👎.

Two production defects found in Azure telemetry over 2026-07-22 → 2026-08-05.

**Windows `grep` broken for 99 machines**

`core_failure` showed 328 events across 99 distinct Windows machines (of 617
total) carrying `? is not recognized as an internal or external command,
operable program or batch file.` and its German, French, Spanish and Portuguese
translations. Present on released 0.9.2, 0.9.3 and 0.9.4.

Root cause: ripgrep's Windows release is a zip, and `RipgrepBinary` extracted it
via `powershell.exe -Command Expand-Archive`, falling back to the literal string
`"powershell.exe"` when neither `powershell.exe` nor `pwsh.exe` resolved.
`cross-spawn`'s `parseNonShell()` sets `needsShell = true` when `resolveCommand()`
returns undefined and re-spawns through `cmd.exe /d /s /c`, so cmd.exe produced
that message. `throw new Error(result.stderr.trim())` made it the error verbatim,
and since `RipgrepBinary.filepath` is `Effect.cached`, one failed extraction broke
grep for the whole session.

Upstream carries the same fragility: anomalyco/opencode#24291 is open, reporting
`Expand-Archive` unusable when spawned from the Bun-compiled binary, affecting
`grep`, `glob` and `skill`. Their #23457 fix only corrected how paths were passed
to PowerShell (the `$args` → inlined-and-escaped form we already carry); it did
not remove the dependency on PowerShell being resolvable.

Extract the zip in-process with `@zip.js/zip.js`, converging on the approach the
`packages/opencode/src/file/ripgrep.ts` shim already uses in production.
`unzipExecutable` is exported so archive handling is tested directly, and decodes
with `checkSignature: true` — zip.js defaults it off, and a CRC-corrupt download
would otherwise be written to the cache and trusted by every later session.

Install the binary atomically (stage to `rg.exe.tmp`, then rename). `filepath`
trusts the cached binary on existence alone, so an interrupted write previously
left a truncated `rg.exe` that every later session reused — the same permanent
breakage `checkSignature` guards against, which CRC cannot catch because it is
verified before the write. The tar path installs the same way.

Attribute resolution failures. Child stderr was reported verbatim, so a
shell-level failure was indistinguishable from a tool bug; and because a resolve
failure is memoized, it is re-reported on every later grep in the session. The tar
branch now names ripgrep, and any typed filesystem or HTTP failure is wrapped as
`ripgrep binary resolve failed: …`.

Note the blast radius is wider than the `grep` tool: `@opencode-ai/core/ripgrep`
also backs the HTTP-API file handlers and `cli/cmd/debug/ripgrep.ts`.

**Test runs shipped telemetry to the production resource**

1,020 of 3,135 machine ids in the same window emitted `provider_id="test"` /
`cli_version="local"` — test processes that regenerate their machine id every
run, inflating install and active-machine counts by roughly a third. `doInit()`
gated only on `ALTIMATE_TELEMETRY_DISABLED`.

Refuse the baked-in connection string when `NODE_ENV=test`, `BUN_TEST`, `VITEST`
or `JEST_WORKER_ID` is set. Keyed on test runners, deliberately not on CI:
`altimate-code-actions` wraps this CLI, so every run of that shipped product sets
`CI`/`GITHUB_ACTIONS`, and gating on those would blind a real product surface.
`bun test` sets `NODE_ENV=test`, which covers CI and developer machines alike.
An explicit `APPLICATIONINSIGHTS_CONNECTION_STRING` is always honoured, so suites
with their own sink are unaffected; `ALTIMATE_TELEMETRY_FORCE=true` overrides the
default-sink refusal, and the existing opt-outs still win over both.

Test plan: 8 ripgrep tests — including a layer-level test that drives `filepath`
through the Windows zip path with a spawner that fails if invoked, asserting the
staged-then-renamed install, and a CRC case that fails without `checkSignature` —
plus 15 telemetry-gate tests covering CI-alone-still-reports and one that relies
on the real runner's `NODE_ENV`. `turbo typecheck` green, marker guard green,
`packages/core` and `packages/opencode` suites show no failures beyond the
pre-existing set on `main`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@anandgupta42
anandgupta42 force-pushed the fix/windows-grep-and-ci-telemetry branch from 334adfe to 83961ac Compare August 5, 2026 10:34

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 83961acb83

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/ci.yml
# Run from packages/core so `effect` and the other deps resolve — they are not root deps.
- name: Resolve ripgrep with PowerShell unavailable
working-directory: packages/core
run: bun run script/windows-ripgrep-e2e.ts

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Run the new core regression test in CI

On PRs that change packages/core/**, this Windows job installs the core deps but only runs script/windows-ripgrep-e2e.ts; I checked the workflows with rg and there is no invocation of packages/core/test/ripgrep-windows.test.ts or the core test suite, while the main TypeScript job runs bun test from packages/opencode. That leaves the new no-spawn/CRC/empty-archive regression coverage unexecuted in CI, so a broken in-process extractor can still merge as long as this E2E script passes; add the core regression test, or a focused core test pass, to this job.

Useful? React with 👍 / 👎.

@anandgupta42

Copy link
Copy Markdown
Contributor Author

Now tested on real Windows — with an important caveat

Answering the direct question: until now, no — nothing here had run on Windows, which was the obvious gap given this fixes a Windows-only failure. There is now a Windows ripgrep E2E job (windows-latest) running packages/core/script/windows-ripgrep-e2e.ts. It is not a unit test: it does a real download of the real archive, extracts it, installs it and executes the binary.

Output from the run on this PR:

ok   PowerShell is not resolvable on PATH
ok   cold cache at C:\Users\RUNNER~1\AppData\Local\Temp\rg-e2e-…\altimate-code\bin
     downloading ripgrep … ripgrep-15.1.0-x86_64-pc-windows-msvc.zip
ok   resolved …\bin\rg.exe
ok   binary present, 4266496 bytes
ok   no staging files left behind
ok   executes: ripgrep 15.1.0 (rev af60c2de9d)
ok   search returns matches
ok   second resolve hits the cache

So on real Windows the new path downloads, extracts in-process, installs atomically and produces a working binary. The staging assertion also covers the atomic-install change reviewers asked for.

What this job does not establish

I first wrote it to assert that the old PowerShell approach fails under the same conditions, so the run would be a true counterfactual. That assertion failed — and the failure is the useful part:

FAIL: the old PowerShell approach still succeeds here — this environment does not
      reproduce the bug, so the run above proves nothing

Emptying PATH does not make PowerShell unspawnable on Windows. cross-spawn falls back to cmd.exe /d /s /c, and Windows process creation searches beyond PATH (the caller's directory, the system directories, the App Paths registry key), so powershell.exe still starts on a stock runner even though which() cannot see it.

I have therefore not reproduced the affected machines, and I am not claiming this job proves the fix resolves #1072 for them. The probe is now informational and prints that caveat rather than failing.

What carries that argument instead is structural, and it is platform-independent: test/ripgrep-windows.test.ts drives filepath through the Windows zip branch with a ChildProcessSpawner that fails the test if anything is spawned at all. Since extraction launches no process, PowerShell's availability cannot affect it. The Windows job proves the path genuinely works on Windows; the unit test proves it needs no shell.

Other e2e coverage added this round

The subprocess/e2e pass CI runs separately (test/cli/acp, smokes, serve, run-process, mcp-add, help-snapshots) — run locally at the CI concurrency setting: 37 pass, 0 fail.

One incidental find: bun install cannot complete on windows-latest because tree-sitter-powershell has no Windows prebuild and its fallback compile needs Visual Studio Build Tools (same as anomalyco/opencode#25563). The job uses --ignore-scripts, which is sufficient here. Worth knowing if anyone else adds Windows CI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant