Skip to content

feat: 将 ripgrep 二进制文件通过 base64 编码嵌入到编译产物#334

Open
realorange1994 wants to merge 1 commit intoclaude-code-best:mainfrom
realorange1994:feature/ripgrep-embedding
Open

feat: 将 ripgrep 二进制文件通过 base64 编码嵌入到编译产物#334
realorange1994 wants to merge 1 commit intoclaude-code-best:mainfrom
realorange1994:feature/ripgrep-embedding

Conversation

@realorange1994
Copy link
Copy Markdown

@realorange1994 realorange1994 commented Apr 23, 2026

  • compile.ts: 构建时将各平台 ripgrep 二进制文件编码为 base64 写入 ripgrepAssetBase64.ts
  • ripgrepAsset.ts: 运行时解码 base64 到临时文件(编译模式),开发模式回退到 SDK 内置路径
  • ripgrep.ts: 重构为异步,ripgrepCommand() 返回 Promise
  • bundledMode.ts: 添加 BUNDLED_MODE 编译时检测标志
  • package.json: 添加 compile 脚本

Summary by CodeRabbit

  • New Features

    • Added support for compiling the application into a single executable using Bun's --compile feature
    • Bundled ripgrep binary now available in compiled builds
  • Chores

    • Added build script for compiled executable generation

- compile.ts: 构建时将各平台 ripgrep 二进制文件编码为 base64 写入 ripgrepAssetBase64.ts
- ripgrepAsset.ts: 运行时解码 base64 到临时文件(编译模式),开发模式回退到 SDK 内置路径
- ripgrep.ts: 重构为异步,ripgrepCommand() 返回 Promise
- bundledMode.ts: 添加 BUNDLED_MODE 编译时检测标志
- package.json: 添加 compile 脚本
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

📝 Walkthrough

Walkthrough

Adds Bun --compile support through a new build driver that generates ripgrep base64 assets, injects compile-time feature flags and macro defines, patches SDK paths for compiled execution, and invokes bun build --compile. Runtime ripgrep execution and bundled mode detection are updated to use embedded assets.

Changes

Cohort / File(s) Summary
Build System
compile.ts, package.json
New build driver for Bun compilation: derives feature flags from environment, generates ripgrep base64 asset module, creates macro --define arguments, patches SDK's cli.js path logic for compiled mode, and invokes bun build --compile with proper environment setup.
Bundled Mode Detection
src/utils/bundledMode.ts
Updated isInBundledMode() to check for compile-time-injected BUNDLED_MODE constant as primary detection method before falling back to runtime Bun.embeddedFiles check.
Ripgrep Integration
src/utils/ripgrepAsset.ts, src/utils/ripgrep.ts
New asset module with platform-aware ripgrep binary path resolver and caching logic; decodes base64 assets in compiled mode, validates via version fingerprint, extracts to temp directory with executable permissions. Ripgrep execution updated to use resolved binary path instead of spawning current runtime.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI Startup
    participant BMD as Bundled Mode<br/>Detector
    participant RAR as Ripgrep Asset<br/>Resolver
    participant Cache as Binary Cache<br/>(Temp/Memory)
    participant RG as Ripgrep<br/>Executor

    CLI->>BMD: Check if bundled mode
    BMD->>BMD: Check BUNDLED_MODE<br/>compile-time constant
    BMD-->>CLI: Return true (compiled)
    
    CLI->>RAR: getRipgrepBinaryPath()
    RAR->>Cache: Check cache by platform/arch
    alt Cache hit
        Cache-->>RAR: Cached binary path
    else Cache miss (compiled mode)
        RAR->>RAR: Load ripgrepAssetBase64
        RAR->>RAR: Compute version fingerprint
        RAR->>Cache: Check version tag file
        alt Version valid & binary exists
            Cache-->>RAR: Return cached binary path
        else Invalid or missing
            RAR->>RAR: Decode base64
            RAR->>Cache: Write binary to temp<br/>with executable perms
            RAR->>Cache: Write version tag
            Cache-->>RAR: Return extracted path
        end
    end
    
    RAR-->>CLI: Binary path
    CLI->>RG: Execute ripgrep
    RG-->>CLI: Results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A Bun compiles with ripgrep sewn,
Binary assets in cache are shown,
Base64 magic, temp dirs so neat,
Embedded paths make execution complete! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is in Chinese and describes embedding ripgrep binary via base64 encoding into compiled artifacts, which matches the core objective of adding base64-encoded ripgrep binaries to build outputs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (3)
src/utils/ripgrep.ts (1)

9-9: Use the src/* alias for the new source import.

This new import is under src, so prefer the project alias instead of another relative import.

-import { getRipgrepBinaryPath } from './ripgrepAsset.js'
+import { getRipgrepBinaryPath } from 'src/utils/ripgrepAsset.js'

As per coding guidelines, “Import from src/* path alias instead of relative paths”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/ripgrep.ts` at line 9, The import in ripgrep.ts is using a relative
path; replace the relative import for getRipgrepBinaryPath with the project
alias import from src/* (e.g., import { getRipgrepBinaryPath } from
'src/utils/ripgrepAsset') so the module uses the src path alias instead of
'./ripgrepAsset.js'; update the import statement referencing the
getRipgrepBinaryPath symbol accordingly.
src/utils/ripgrepAsset.ts (1)

15-15: Use the src/* alias for the platform import.

-import { getPlatform } from './platform.js'
+import { getPlatform } from 'src/utils/platform.js'

As per coding guidelines, “Import from src/* path alias instead of relative paths”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/ripgrepAsset.ts` at line 15, The import in
src/utils/ripgrepAsset.ts should use the project path alias instead of a
relative path: replace the current import of getPlatform from './platform.js'
with the aliased import from 'src/platform' (referencing the getPlatform symbol)
so it follows the "Import from src/*" guideline; update the import statement
accordingly and ensure any build/tsconfig alias expectations are respected.
compile.ts (1)

31-36: Move BUNDLED_MODE into the macro definitions source of truth.

This inline --define bypasses scripts/defines.ts, while the rest of the compile-time defines already flow through getMacroDefines().

Based on learnings, “Place feature flag definitions and MACRO constants in scripts/defines.ts - this is the single source of truth for version numbers and compile-time constants”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compile.ts` around lines 31 - 36, The inline addition of `--define
BUNDLED_MODE:"true"` in `defineArgsWithBundled` should be removed and instead
add `BUNDLED_MODE` to the canonical macro definitions in `scripts/defines.ts`
(or the `getMacroDefines()` source) so it flows through the same codepath as
other compile-time defines; update `getMacroDefines()` (or the defines export in
`scripts/defines.ts`) to include BUNDLED_MODE:"true" when building bundled
artifacts and remove the `defineArgsWithBundled` special-case in the compile
logic (referencing `defineArgsWithBundled`, `defineArgs`, and
`getMacroDefines()`/`scripts/defines.ts` and ensure `ripgrepAsset.ts` continues
to read the macro from the central defines).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@compile.ts`:
- Around line 12-14: The feature collection currently enables any FEATURE_* key
regardless of value; update the pipeline that builds features (the code using
Object.keys(process.env).filter(...).map(...)) to only include keys where
process.env[k] === "1" (i.e., check the env value equals the string "1" in the
filter that also verifies k.startsWith("FEATURE_")), then continue mapping to
strip the "FEATURE_" prefix (e.g., keep using k.replace("FEATURE_", "") or
k.slice(8)) so only truly enabled flags are passed as --feature.
- Around line 134-153: In patchRipgrepPaths, remove the hard-coded SDK version
in sdkCachePath and instead resolve the installed package entry point (e.g., via
require.resolve("@anthropic-ai/claude-agent-sdk/cli.js") or using
path.join(repoRoot, "node_modules", ...resolvePackageDir)) to locate cli.js
dynamically; if the resolved path does not exist or readFile fails throw an
Error; after performing the string replacements on sdkContent, if patchedSdk ===
sdkContent throw an Error (fail-fast) rather than console.warn so the build
stops on a non-matching patch; write the patched content with writeFile only
after a successful replacement.
- Around line 83-115: In generateRipgrepAsset, stop only warning when the
ripgrep binary is missing and instead fail the build: if readFile for the
selected platform (used to populate ripgrepBinaries / RIPGREP_BINARIES written
to ripgrepAssetBase64.ts) throws, rethrow or call process.exit(1) with a clear
error so the build fails rather than emitting an empty asset. Also replace the
hard-coded SDK cache path by resolving the installed SDK at build time using
require.resolve('@anthropic-ai/claude-agent-sdk') (locate the package root from
that result) so rgCache is computed dynamically and not tied to the stale
`@anthropic-ai`+claude-agent-sdk@0.2.87+... string referenced in
generateRipgrepAsset and src/utils/ripgrepAsset.ts.

In `@src/utils/bundledMode.ts`:
- Around line 14-22: The comment and implementation disagree: isInBundledMode
currently treats any defined BUNDLED_MODE as true before checking
Bun.embeddedFiles; either update the comment or change isInBundledMode to honor
the documented precedence. Modify isInBundledMode so it first checks
Bun?.embeddedFiles (truthy -> return true), then falls back to explicitly
checking BUNDLED_MODE's value (e.g., compare to "true" or boolean equivalent) —
reference the isInBundledMode function, the BUNDLED_MODE declaration, and
Bun.embeddedFiles when making the change.

In `@src/utils/ripgrep.ts`:
- Around line 48-52: The code returns { mode: 'builtin', command: rgPath, args:
[] } when isInBundledMode() is true, causing the Doctor UI to mislabel an
embedded/compiled ripgrep as vendor; change the returned mode to a distinct
value (e.g., 'embedded' or 'bundled_compiled') from isInBundledMode() so
downstream code can distinguish it, and update any consumers (e.g., the Doctor
display logic that checks mode) to recognize the new mode name; specifically
modify the return in getRipgrepBinaryPath()/where isInBundledMode() is handled
and ensure Doctor.tsx's ripgrep-mode handling accepts the new mode string.

In `@src/utils/ripgrepAsset.ts`:
- Around line 65-111: The temp-cache is currently trusted without validating
directory ownership/permissions or using atomic writes; update the logic around
tmpDir/versionPath/filePath (used in the compiled-mode branch that reads
RIPGREP_BINARIES and fills extractedPaths) to: ensure tmpDir is created with
mode 0o700 and owned by the current process user (on POSIX check uid/gid),
refuse to trust the cached versionPath/filePath if ownership or permissions are
not correct, and when decoding base64Data write to a unique temp file inside
tmpDir and atomically rename it to filePath (set file mode 0o700/0o755 as
appropriate) before writing versionPath, so cached executables are both
user-private and written atomically.
- Around line 70-75: The current dev-mode branch uses typeof BUNDLED_MODE ===
'undefined' which mismatches callers (like ripgrep.ts) that use
isInBundledMode() or Bun.embeddedFiles to detect bundled mode; change the
condition in the resolver to use the same bundled-mode detection as callers
(e.g., call isInBundledMode() or replicate its Bun.embeddedFiles check) so that
when bundled mode is true you do not return getSdkRipgrepPath() but instead
proceed with extracting the embedded binary; update the branch around
BUNDLED_MODE/getSdkRipgrepPath() to rely on isInBundledMode()/Bun.embeddedFiles
consistent with ripgrep.ts.
- Around line 24-30: getSdkRipgrepPath currently hard-codes the full vendor path
including a locked package hash and uses process.cwd(), which will break across
installs; replace that hard-coded resolution by using require.resolve (or
import.meta.resolve in ESM) to locate the installed
`@anthropic-ai/claude-agent-sdk` package entry point and then derive the
vendor/ripgrep subpath from that resolved location in getSdkRipgrepPath(),
falling back to platform/arch branches only after resolving the package base;
also update the dev-mode fallback logic that tries to access the embedded asset
(the block around the existing dev-mode fallback) to surface a clear error when
the resolved path is unavailable in compiled mode and to avoid relying on
process.cwd() for package resolution.

---

Nitpick comments:
In `@compile.ts`:
- Around line 31-36: The inline addition of `--define BUNDLED_MODE:"true"` in
`defineArgsWithBundled` should be removed and instead add `BUNDLED_MODE` to the
canonical macro definitions in `scripts/defines.ts` (or the `getMacroDefines()`
source) so it flows through the same codepath as other compile-time defines;
update `getMacroDefines()` (or the defines export in `scripts/defines.ts`) to
include BUNDLED_MODE:"true" when building bundled artifacts and remove the
`defineArgsWithBundled` special-case in the compile logic (referencing
`defineArgsWithBundled`, `defineArgs`, and
`getMacroDefines()`/`scripts/defines.ts` and ensure `ripgrepAsset.ts` continues
to read the macro from the central defines).

In `@src/utils/ripgrep.ts`:
- Line 9: The import in ripgrep.ts is using a relative path; replace the
relative import for getRipgrepBinaryPath with the project alias import from
src/* (e.g., import { getRipgrepBinaryPath } from 'src/utils/ripgrepAsset') so
the module uses the src path alias instead of './ripgrepAsset.js'; update the
import statement referencing the getRipgrepBinaryPath symbol accordingly.

In `@src/utils/ripgrepAsset.ts`:
- Line 15: The import in src/utils/ripgrepAsset.ts should use the project path
alias instead of a relative path: replace the current import of getPlatform from
'./platform.js' with the aliased import from 'src/platform' (referencing the
getPlatform symbol) so it follows the "Import from src/*" guideline; update the
import statement accordingly and ensure any build/tsconfig alias expectations
are respected.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 344e2320-4352-4060-88d5-370eb64b5f63

📥 Commits

Reviewing files that changed from the base of the PR and between 1173a62 and eeb34de.

📒 Files selected for processing (6)
  • compile.ts
  • package.json
  • src/utils/bundledMode.ts
  • src/utils/ripgrep.ts
  • src/utils/ripgrepAsset.ts
  • src/utils/ripgrepAssetBase64.ts

Comment thread compile.ts
Comment on lines +12 to +14
const features = Object.keys(process.env)
.filter(k => k.startsWith("FEATURE_"))
.map(k => k.replace("FEATURE_", ""));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Only enable feature flags when their value is 1.

The current code enables FEATURE_FOO=0, FEATURE_FOO=false, or an empty variable. Filter by the required enablement value before passing --feature.

 const features = Object.keys(process.env)
-    .filter(k => k.startsWith("FEATURE_"))
+    .filter(k => k.startsWith("FEATURE_") && process.env[k] === "1")
     .map(k => k.replace("FEATURE_", ""));

As per coding guidelines, “Feature flags in build must use environment variable format FEATURE_<FLAG_NAME>=1 for enablement”.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const features = Object.keys(process.env)
.filter(k => k.startsWith("FEATURE_"))
.map(k => k.replace("FEATURE_", ""));
const features = Object.keys(process.env)
.filter(k => k.startsWith("FEATURE_") && process.env[k] === "1")
.map(k => k.replace("FEATURE_", ""));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compile.ts` around lines 12 - 14, The feature collection currently enables
any FEATURE_* key regardless of value; update the pipeline that builds features
(the code using Object.keys(process.env).filter(...).map(...)) to only include
keys where process.env[k] === "1" (i.e., check the env value equals the string
"1" in the filter that also verifies k.startsWith("FEATURE_")), then continue
mapping to strip the "FEATURE_" prefix (e.g., keep using k.replace("FEATURE_",
"") or k.slice(8)) so only truly enabled flags are passed as --feature.

Comment thread compile.ts
Comment on lines +83 to +115
async function generateRipgrepAsset() {
const rgCache = join(repoRoot,
`node_modules/.bun/@anthropic-ai+claude-agent-sdk@0.2.87+3c5d820c62823f0b/node_modules/@anthropic-ai/claude-agent-sdk/vendor/ripgrep`);

const ripgrepBinaries: Record<string, string> = {}

// Map platform+arch to filename
const allPlatforms: Array<{ key: string; subdir: string; file: string }> = [
{ key: 'windows_x64', subdir: 'x64-win32', file: 'rg.exe' },
{ key: 'darwin_x64', subdir: 'x64-darwin', file: 'rg' },
{ key: 'darwin_arm64', subdir: 'arm64-darwin', file: 'rg' },
{ key: 'linux_x64', subdir: 'x64-linux', file: 'rg' },
{ key: 'linux_arm64', subdir: 'arm64-linux', file: 'rg' },
];

// Only embed the current platform's binary to minimize exe size.
// The other platforms are available in the SDK for dev-mode fallback.
const currentPlatformKey = (() => {
if (process.platform === 'win32') return 'windows_x64'
if (process.platform === 'darwin') return process.arch === 'arm64' ? 'darwin_arm64' : 'darwin_x64'
return process.arch === 'arm64' ? 'linux_arm64' : 'linux_x64'
})()

for (const { key, subdir, file } of allPlatforms) {
if (key !== currentPlatformKey) continue // Skip other platforms
const binPath = join(rgCache, subdir, file);
try {
const data = await readFile(binPath);
ripgrepBinaries[key] = data.toString('base64');
console.log(`Encoded ${key}: ${data.length} bytes -> ${Math.round(data.length * 1.37)} chars`);
} catch (e) {
console.warn(`Warning: could not read ${binPath}: ${e}`);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Declared SDK dependency:"
node -e 'const p=require("./package.json"); console.log((p.dependencies||{})["@anthropic-ai/claude-agent-sdk"] || (p.devDependencies||{})["@anthropic-ai/claude-agent-sdk"])'

echo
echo "Hard-coded SDK cache references:"
rg -n -C2 '@anthropic-ai\+claude-agent-sdk@0\.2\.87|claude-agent-sdk@0\.2\.87|vendor/ripgrep'

echo
echo "Installed ripgrep vendor directories, if present:"
fd -p 'vendor/ripgrep' node_modules --max-depth 8 2>/dev/null || true

Repository: claude-code-best/claude-code

Length of output: 2499


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Full generateRipgrepAsset function in compile.ts ==="
sed -n '83,150p' compile.ts

echo
echo "=== How ripgrepBinaries is used/written ==="
rg -n -A5 "ripgrepBinaries" compile.ts | head -40

echo
echo "=== Check if there are dynamic SDK resolution patterns elsewhere ==="
rg -n "require.resolve|import.meta.resolve|resolve.*sdk" --type ts --type tsx -i | head -20

Repository: claude-code-best/claude-code

Length of output: 4337


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Look for require.resolve patterns in TypeScript files ==="
rg -n "require\.resolve|resolveFrom|createRequire|import\.meta" --type-add 'ts:*.ts' --type-add 'tsx:*.tsx' | head -20

echo
echo "=== Check bun's resolve capabilities ==="
rg -n "bun:.*resolve|Bun\.resolveSync|Bun\.resolve" | head -10

echo
echo "=== Look at package.json to understand dependency management ==="
head -50 package.json

echo
echo "=== Check how other paths reference node_modules packages ==="
rg -n "node_modules.*@anthropic" --type-add 'ts:*.ts' --type-add 'tsx:*.tsx' -C1 | head -30

Repository: claude-code-best/claude-code

Length of output: 2238


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Look for resolve patterns with createRequire ==="
rg -n -A5 "createRequire" compile.ts src/utils/ | head -40

echo
echo "=== Check how bun handles module resolution ==="
cat package.json | grep -A2 -B2 "anthropic"

echo
echo "=== Try to find if there's a way to dynamically resolve the package ==="
node -e "
const { createRequire } = require('module');
const req = createRequire(import.meta.url);
try {
  const pkgPath = req.resolve('@anthropic-ai/claude-agent-sdk/package.json');
  console.log('Resolved package:', pkgPath);
} catch(e) {
  console.log('Could not resolve:', e.message);
}
"

echo
echo "=== Check compile.ts for any dynamic resolution at all ==="
head -30 compile.ts

Repository: claude-code-best/claude-code

Length of output: 124


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Full compile.ts head to see imports ==="
head -40 compile.ts

echo
echo "=== Check if file paths are computed anywhere else ==="
grep -r "resolveSync\|resolve(" compile.ts src/ 2>/dev/null || echo "No resolve patterns found"

echo
echo "=== Look at the actual imports in compile.ts ==="
rg -n "^import\|^from" compile.ts | head -20

echo
echo "=== Check how the SDK is actually installed - bun.lockb or package-lock ==="
ls -la | grep -E "lock|package" 

echo
echo "=== Look for any bun-specific resolution ==="
rg -n "Bun\." compile.ts | head -10

Repository: claude-code-best/claude-code

Length of output: 37574


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check imports in compile.ts ==="
head -20 compile.ts

echo
echo "=== Check if createRequire is used anywhere in compile context ==="
rg -n "createRequire" compile.ts

echo
echo "=== Look at how ripgrep.ts (runtime) resolves the path dynamically ==="
cat -n src/utils/ripgrep.ts | head -30

echo
echo "=== Check the actual generated ripgrepAssetBase64.ts file to see if it exists ==="
ls -la src/utils/ripgrepAssetBase64.ts 2>/dev/null || echo "File does not exist yet"

echo
echo "=== Can we use require.resolve or import.meta.resolve in compile.ts? ==="
rg -n "require\.resolve|import\.meta\.resolve" compile.ts src/

Repository: claude-code-best/claude-code

Length of output: 980


🏁 Script executed:

#!/bin/bash
set -euoripefail

echo "=== Full ripgrep.ts to see dynamic resolution pattern ==="
cat -n src/utils/ripgrep.ts

echo
echo "=== Check if bun provides module resolution in compile scripts ==="
# Look for examples of how dependencies are resolved at build time
rg -n "package.json\|require.*resolve\|fileURLToPath" scripts/ --type ts -A2 | head -40

echo
echo "=== Verify the SDK path mismatch more clearly ==="
echo "Declared in package.json:"
jq '.dependencies["@anthropic-ai/claude-agent-sdk"]' package.json
echo
echo "Hard-coded in compile.ts:"
grep -n "@anthropic-ai" compile.ts | head -3

Repository: claude-code-best/claude-code

Length of output: 746


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Full ripgrep.ts to see dynamic resolution pattern ==="
cat -n src/utils/ripgrep.ts

echo
echo "=== Check if bun provides module resolution in compile scripts ==="
# Look for examples of how dependencies are resolved at build time
rg -n "package.json|require.*resolve|fileURLToPath" scripts/ --type ts -A2 | head -40

echo
echo "=== Verify the SDK path mismatch more clearly ==="
echo "Declared in package.json:"
jq '.dependencies["@anthropic-ai/claude-agent-sdk"]' package.json
echo
echo "Hard-coded in compile.ts:"
grep -n "@anthropic-ai" compile.ts | head -3

Repository: claude-code-best/claude-code

Length of output: 27419


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check actual package.json dependencies section ==="
cat package.json | grep -A50 '"dependencies"'

echo
echo "=== Check src/utils/ripgrepAsset.ts - where getRipgrepBinaryPath comes from ==="
cat -n src/utils/ripgrepAsset.ts

echo
echo "=== Confirm: does generateRipgrepAsset fail compile if ripgrep missing? ==="
grep -n "generateRipgrepAsset" compile.ts | head -5

Repository: claude-code-best/claude-code

Length of output: 8190


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Final verification: Check how to resolve SDK location dynamically ==="
echo "Checking if require.resolve or similar could work in compile.ts context:"
node -e "
try {
  const path = require.resolve('@anthropic-ai/claude-agent-sdk');
  console.log('Resolved SDK path:', path);
} catch (e) {
  console.log('Cannot resolve:', e.message);
}
"

echo
echo "=== Check if bun has native resolve capabilities ==="
grep -n "Bun\|bun:" compile.ts | head -5

Repository: claude-code-best/claude-code

Length of output: 717


Fail the build when the embedded ripgrep asset is missing, and resolve the SDK location dynamically.

When ripgrep cannot be read (line 114), the code warns but continues, writing an empty RIPGREP_BINARIES object to ripgrepAssetBase64.ts. This produces an executable that silently fails at runtime since the embedded binary is unavailable. Additionally, both compile.ts (line 85) and src/utils/ripgrepAsset.ts (lines 27–29) hard-code the SDK cache path @anthropic-ai+claude-agent-sdk@0.2.87+..., which is stale—the declared dependency is ^0.2.114. This mismatch breaks the dev-mode fallback.

Use require.resolve('@anthropic-ai/claude-agent-sdk') to resolve the installed SDK path dynamically at build time, and throw an error if the ripgrep binary cannot be read.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compile.ts` around lines 83 - 115, In generateRipgrepAsset, stop only warning
when the ripgrep binary is missing and instead fail the build: if readFile for
the selected platform (used to populate ripgrepBinaries / RIPGREP_BINARIES
written to ripgrepAssetBase64.ts) throws, rethrow or call process.exit(1) with a
clear error so the build fails rather than emitting an empty asset. Also replace
the hard-coded SDK cache path by resolving the installed SDK at build time using
require.resolve('@anthropic-ai/claude-agent-sdk') (locate the package root from
that result) so rgCache is computed dynamically and not tied to the stale
`@anthropic-ai`+claude-agent-sdk@0.2.87+... string referenced in
generateRipgrepAsset and src/utils/ripgrepAsset.ts.

Comment thread compile.ts
Comment on lines +134 to +153
async function patchRipgrepPaths() {
// --- Patch bun cache SDK cli.js ---
const sdkCachePath = join(repoRoot,
"node_modules/.bun/@anthropic-ai+claude-agent-sdk@0.2.87+3c5d820c62823f0b/node_modules/@anthropic-ai/claude-agent-sdk/cli.js");
const sdkContent = await readFile(sdkCachePath, "utf-8");
const patchedSdk = sdkContent
.replace(
/import\{fileURLToPath as Uy_\}from"url";/,
";",
)
.replace(
/dy_=Uy_\(import\.meta\.url\),dy_=Z16\.join\(dy_,"\.\/"\)/,
"dy_=Z16.dirname(process.execPath)",
);
if (patchedSdk === sdkContent) {
console.warn("Warning: SDK patch did not match");
} else {
await writeFile(sdkCachePath, patchedSdk);
console.log("Patched SDK cli.js (bun cache)");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "SDK patch targets/references:"
rg -n -C3 'patchRipgrepPaths|SDK patch did not match|dy_=|@anthropic-ai\+claude-agent-sdk@0\.2\.87'

echo
echo "Installed SDK cli.js files, if present:"
fd -p 'claude-agent-sdk/cli.js' node_modules --max-depth 8 2>/dev/null || true

Repository: claude-code-best/claude-code

Length of output: 3349


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Checking SDK version pinning ==="
echo "From package.json:"
rg '"@anthropic-ai/claude-agent-sdk"' package.json -A2 -B2

echo
echo "=== Checking for hard-coded paths in compile.ts ==="
rg '@anthropic-ai.*0\.2\.87' compile.ts -n

echo
echo "=== Checking if patchRipgrepPaths handles errors ==="
sed -n '134,153p' compile.ts | cat -n

echo
echo "=== Looking for error handling patterns in similar functions ==="
rg 'throw new Error|process\.exit|throw Error' compile.ts -n

Repository: claude-code-best/claude-code

Length of output: 1768


Make the SDK patch deterministic and fail-fast; remove hard-coded version strings.

The code hard-codes SDK version 0.2.87 in paths (lines 85, 137) while package.json specifies ^0.2.114. When the regex patch fails to match, the build continues with an unpatched SDK—potentially causing runtime failures that go undetected. If the installed SDK version differs from the hard-coded 0.2.87, the paths will not exist.

Throw an error on patch failure instead of silently logging a warning. Dynamically resolve the SDK path from node_modules rather than hard-coding a version string to avoid version mismatches and cache path mutations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compile.ts` around lines 134 - 153, In patchRipgrepPaths, remove the
hard-coded SDK version in sdkCachePath and instead resolve the installed package
entry point (e.g., via require.resolve("@anthropic-ai/claude-agent-sdk/cli.js")
or using path.join(repoRoot, "node_modules", ...resolvePackageDir)) to locate
cli.js dynamically; if the resolved path does not exist or readFile fails throw
an Error; after performing the string replacements on sdkContent, if patchedSdk
=== sdkContent throw an Error (fail-fast) rather than console.warn so the build
stops on a non-matching patch; write the patched content with writeFile only
after a successful replacement.

Comment thread src/utils/bundledMode.ts
Comment on lines +14 to 22
*
* Primary check: Bun.embeddedFiles (present in compiled binaries).
* Fallback: BUNDLED_MODE compile-time constant injected by compile.ts.
*/
// BUNDLED_MODE is injected at compile time by compile.ts --define flag.
declare const BUNDLED_MODE: string | undefined
export function isInBundledMode(): boolean {
if (typeof BUNDLED_MODE !== 'undefined') return true
return (
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Align the bundled-mode check with the documented precedence.

The comment says Bun.embeddedFiles is primary and BUNDLED_MODE is fallback, but the code checks BUNDLED_MODE first and treats any defined value as enabled. Make the comment match the code, or check the actual value explicitly.

- * Primary check: Bun.embeddedFiles (present in compiled binaries).
- * Fallback: BUNDLED_MODE compile-time constant injected by compile.ts.
+ * Primary check: BUNDLED_MODE compile-time constant injected by compile.ts.
+ * Fallback: Bun.embeddedFiles (present in compiled binaries).
...
-  if (typeof BUNDLED_MODE !== 'undefined') return true
+  if (BUNDLED_MODE === 'true') return true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/bundledMode.ts` around lines 14 - 22, The comment and
implementation disagree: isInBundledMode currently treats any defined
BUNDLED_MODE as true before checking Bun.embeddedFiles; either update the
comment or change isInBundledMode to honor the documented precedence. Modify
isInBundledMode so it first checks Bun?.embeddedFiles (truthy -> return true),
then falls back to explicitly checking BUNDLED_MODE's value (e.g., compare to
"true" or boolean equivalent) — reference the isInBundledMode function, the
BUNDLED_MODE declaration, and Bun.embeddedFiles when making the change.

Comment thread src/utils/ripgrep.ts
Comment on lines +48 to +52
// In bundled mode (compiled exe), ripgrep is embedded via base64.
// Extract to temp and execute from there.
if (isInBundledMode()) {
return {
mode: 'embedded',
command: process.execPath,
args: ['--no-config'],
argv0: 'rg',
}
const rgPath = getRipgrepBinaryPath()
return { mode: 'builtin', command: rgPath, args: [] }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Keep compiled ripgrep distinct from vendored ripgrep.

Returning mode: 'builtin' for the extracted base64 binary makes src/screens/Doctor.tsx:269-275 display compiled ripgrep as vendor instead of bundled. Either preserve an embedded/compiled mode here, or update the Doctor display logic to recognize this extracted bundled path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/ripgrep.ts` around lines 48 - 52, The code returns { mode:
'builtin', command: rgPath, args: [] } when isInBundledMode() is true, causing
the Doctor UI to mislabel an embedded/compiled ripgrep as vendor; change the
returned mode to a distinct value (e.g., 'embedded' or 'bundled_compiled') from
isInBundledMode() so downstream code can distinguish it, and update any
consumers (e.g., the Doctor display logic that checks mode) to recognize the new
mode name; specifically modify the return in getRipgrepBinaryPath()/where
isInBundledMode() is handled and ensure Doctor.tsx's ripgrep-mode handling
accepts the new mode string.

Comment thread src/utils/ripgrepAsset.ts
Comment on lines +24 to +30
function getSdkRipgrepPath(): string {
const p = getPlatform()
const arch = process.arch
if (p === 'windows') return join(process.cwd(), 'node_modules/.bun/@anthropic-ai+claude-agent-sdk@0.2.87+3c5d820c62823f0b/node_modules/@anthropic-ai/claude-agent-sdk/vendor/ripgrep/x64-win32/rg.exe')
if (p === 'macos') return join(process.cwd(), 'node_modules/.bun/@anthropic-ai+claude-agent-sdk@0.2.87+3c5d820c62823f0b/node_modules/@anthropic-ai/claude-agent-sdk/vendor/ripgrep', arch === 'arm64' ? 'arm64-darwin/rg' : 'x64-darwin/rg')
return join(process.cwd(), 'node_modules/.bun/@anthropic-ai+claude-agent-sdk@0.2.87+3c5d820c62823f0b/node_modules/@anthropic-ai/claude-agent-sdk/vendor/ripgrep', arch === 'arm64' ? 'arm64-linux/rg' : 'x64-linux/rg')
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Declared SDK dependency:"
node -e 'const p=require("./package.json"); console.log((p.dependencies||{})["@anthropic-ai/claude-agent-sdk"] || (p.devDependencies||{})["@anthropic-ai/claude-agent-sdk"])'

echo
echo "Hard-coded SDK cache references:"
rg -n -C2 '@anthropic-ai\+claude-agent-sdk@0\.2\.87|claude-agent-sdk@0\.2\.87|claude-agent-sdk/vendor/ripgrep'

echo
echo "Installed Bun cache SDK dirs, if present:"
fd -p '@anthropic-ai+claude-agent-sdk' node_modules/.bun --max-depth 2 2>/dev/null || true

Repository: claude-code-best/claude-code

Length of output: 1841


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Content of ripgrepAsset.ts at specified lines ==="
sed -n '20,35p' src/utils/ripgrepAsset.ts | cat -n

echo
echo "=== Content at lines 85-95 (also applies to comment) ==="
sed -n '85,95p' src/utils/ripgrepAsset.ts | cat -n

echo
echo "=== Check if require.resolve or similar is used elsewhere ==="
rg -n 'require\.resolve|import\.meta\.resolve|resolve\(.*@anthropic-ai' src/ --type ts --type tsx | head -20

echo
echo "=== Check what functions are exported from ripgrepAsset.ts ==="
rg -n 'export' src/utils/ripgrepAsset.ts

echo
echo "=== Full ripgrepAsset.ts file size ==="
wc -l src/utils/ripgrepAsset.ts

Repository: claude-code-best/claude-code

Length of output: 1823


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check if require.resolve or similar is used elsewhere ==="
rg -n 'require\.resolve|import\.meta\.resolve' src/ | head -20

echo
echo "=== Check how SDK is resolved in other places ==="
rg -n '@anthropic-ai/claude-agent-sdk' src/ | grep -v 'node_modules'

echo
echo "=== Full ripgrepAsset.ts to understand complete context ==="
wc -l src/utils/ripgrepAsset.ts
cat -n src/utils/ripgrepAsset.ts

Repository: claude-code-best/claude-code

Length of output: 133


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Full ripgrepAsset.ts file ==="
cat -n src/utils/ripgrepAsset.ts

echo
echo "=== Check for process.cwd() usage in the repo ==="
rg 'process\.cwd\(\)' src/ -c

echo
echo "=== Check how SDK packages are typically resolved ==="
rg '@anthropic-ai' src/ -l

Repository: claude-code-best/claude-code

Length of output: 10486


Use dynamic module resolution instead of hard-coded SDK path.

The function hard-codes @anthropic-ai/claude-agent-sdk@0.2.87+3c5d820c62823f0b, while package.json declares ^0.2.114. The hash will break whenever the lock file is regenerated or the SDK version updates. Additionally, process.cwd() points to the user's project directory for a CLI, not the package root where the SDK is actually installed.

Resolve the vendor directory dynamically (e.g., via require.resolve() or import.meta.resolve()) instead, and provide clear error handling for compiled mode if the embedded asset is unavailable.

This affects the dev-mode fallback at lines 88–91 as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/ripgrepAsset.ts` around lines 24 - 30, getSdkRipgrepPath currently
hard-codes the full vendor path including a locked package hash and uses
process.cwd(), which will break across installs; replace that hard-coded
resolution by using require.resolve (or import.meta.resolve in ESM) to locate
the installed `@anthropic-ai/claude-agent-sdk` package entry point and then derive
the vendor/ripgrep subpath from that resolved location in getSdkRipgrepPath(),
falling back to platform/arch branches only after resolving the package base;
also update the dev-mode fallback logic that tries to access the embedded asset
(the block around the existing dev-mode fallback) to surface a clear error when
the resolved path is unavailable in compiled mode and to avoid relying on
process.cwd() for package resolution.

Comment thread src/utils/ripgrepAsset.ts
Comment on lines +65 to +111
const tmpDir = join(tmpdir(), 'claude-code-ripgrep')
const filename = key === 'windows_x64' ? 'rg.exe' : 'rg'
const filePath = join(tmpDir, filename)
const versionPath = join(tmpDir, `${key}.version`)

// Dev mode: use SDK path directly
if (typeof BUNDLED_MODE === 'undefined') {
const sdkPath = getSdkRipgrepPath()
extractedPaths[key] = sdkPath
return sdkPath
}

// Compiled mode: must use base64 decode (synchronous path — loaded eagerly from embedded module)
// In the compiled exe, require() resolves to the embedded ripgrepAssetBase64.js
let base64Data: string | undefined
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const RIPGREP_BINARIES: Record<string, string> = require('./ripgrepAssetBase64.js').RIPGREP_BINARIES
base64Data = RIPGREP_BINARIES[key]
} catch {
// require failed — fall back to SDK path
}

if (!base64Data) {
const sdkPath = getSdkRipgrepPath()
extractedPaths[key] = sdkPath
return sdkPath
}

const versionTag = `b64:${base64Data.length}:${base64Data.slice(0, 16)}:${base64Data.slice(-16)}`

// Fast cache check: read only the version tag (~50 bytes)
try {
const storedTag = readFileSync(versionPath, 'utf8')
if (storedTag === versionTag && readFileSync(filePath)) {
extractedPaths[key] = filePath
return filePath
}
} catch {
// Cache miss or stale
}

// Decode and extract
mkdirSync(tmpDir, { recursive: true })
const buffer = Buffer.from(base64Data, 'base64')
writeFileSync(filePath, buffer, { mode: 0o755 })
writeFileSync(versionPath, versionTag, 'utf8')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Harden the temp executable cache before trusting it.

The cache path is predictable and shared under tmpdir(), and the fast path trusts versionPath plus any readable rg file. Use a user-owned 0700 directory, verify ownership/permissions, and write via an atomic temp-file rename before returning an executable path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/ripgrepAsset.ts` around lines 65 - 111, The temp-cache is currently
trusted without validating directory ownership/permissions or using atomic
writes; update the logic around tmpDir/versionPath/filePath (used in the
compiled-mode branch that reads RIPGREP_BINARIES and fills extractedPaths) to:
ensure tmpDir is created with mode 0o700 and owned by the current process user
(on POSIX check uid/gid), refuse to trust the cached versionPath/filePath if
ownership or permissions are not correct, and when decoding base64Data write to
a unique temp file inside tmpDir and atomically rename it to filePath (set file
mode 0o700/0o755 as appropriate) before writing versionPath, so cached
executables are both user-private and written atomically.

Comment thread src/utils/ripgrepAsset.ts
Comment on lines +70 to +75
// Dev mode: use SDK path directly
if (typeof BUNDLED_MODE === 'undefined') {
const sdkPath = getSdkRipgrepPath()
extractedPaths[key] = sdkPath
return sdkPath
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use the same bundled-mode detection as callers.

ripgrep.ts enters this resolver when isInBundledMode() is true, but this file treats BUNDLED_MODE === undefined as dev mode. If the fallback Bun.embeddedFiles detection is what made bundled mode true, this returns the SDK path instead of extracting the embedded binary.

+import { isInBundledMode } from 'src/utils/bundledMode.js'
...
-  if (typeof BUNDLED_MODE === 'undefined') {
+  if (!isInBundledMode()) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Dev mode: use SDK path directly
if (typeof BUNDLED_MODE === 'undefined') {
const sdkPath = getSdkRipgrepPath()
extractedPaths[key] = sdkPath
return sdkPath
}
import { isInBundledMode } from 'src/utils/bundledMode.js'
// ... (rest of imports and code)
// Dev mode: use SDK path directly
if (!isInBundledMode()) {
const sdkPath = getSdkRipgrepPath()
extractedPaths[key] = sdkPath
return sdkPath
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/ripgrepAsset.ts` around lines 70 - 75, The current dev-mode branch
uses typeof BUNDLED_MODE === 'undefined' which mismatches callers (like
ripgrep.ts) that use isInBundledMode() or Bun.embeddedFiles to detect bundled
mode; change the condition in the resolver to use the same bundled-mode
detection as callers (e.g., call isInBundledMode() or replicate its
Bun.embeddedFiles check) so that when bundled mode is true you do not return
getSdkRipgrepPath() but instead proceed with extracting the embedded binary;
update the branch around BUNDLED_MODE/getSdkRipgrepPath() to rely on
isInBundledMode()/Bun.embeddedFiles consistent with ripgrep.ts.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant