feat: 将 ripgrep 二进制文件通过 base64 编码嵌入到编译产物#334
feat: 将 ripgrep 二进制文件通过 base64 编码嵌入到编译产物#334realorange1994 wants to merge 1 commit intoclaude-code-best:mainfrom
Conversation
- compile.ts: 构建时将各平台 ripgrep 二进制文件编码为 base64 写入 ripgrepAssetBase64.ts - ripgrepAsset.ts: 运行时解码 base64 到临时文件(编译模式),开发模式回退到 SDK 内置路径 - ripgrep.ts: 重构为异步,ripgrepCommand() 返回 Promise - bundledMode.ts: 添加 BUNDLED_MODE 编译时检测标志 - package.json: 添加 compile 脚本
📝 WalkthroughWalkthroughAdds Bun Changes
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (3)
src/utils/ripgrep.ts (1)
9-9: Use thesrc/*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 thesrc/*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: MoveBUNDLED_MODEinto the macro definitions source of truth.This inline
--definebypassesscripts/defines.ts, while the rest of the compile-time defines already flow throughgetMacroDefines().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
📒 Files selected for processing (6)
compile.tspackage.jsonsrc/utils/bundledMode.tssrc/utils/ripgrep.tssrc/utils/ripgrepAsset.tssrc/utils/ripgrepAssetBase64.ts
| const features = Object.keys(process.env) | ||
| .filter(k => k.startsWith("FEATURE_")) | ||
| .map(k => k.replace("FEATURE_", "")); |
There was a problem hiding this comment.
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.
| 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.
| 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}`); | ||
| } |
There was a problem hiding this comment.
🧩 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 || trueRepository: 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 -20Repository: 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 -30Repository: 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.tsRepository: 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 -10Repository: 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 -3Repository: 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 -3Repository: 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 -5Repository: 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 -5Repository: 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.
| 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)"); | ||
| } |
There was a problem hiding this comment.
🧩 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 || trueRepository: 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 -nRepository: 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.
| * | ||
| * 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 ( |
There was a problem hiding this comment.
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.
| // 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: [] } |
There was a problem hiding this comment.
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.
| 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') | ||
| } |
There was a problem hiding this comment.
🧩 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 || trueRepository: 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.tsRepository: 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.tsRepository: 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/ -lRepository: 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.
| 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') |
There was a problem hiding this comment.
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.
| // Dev mode: use SDK path directly | ||
| if (typeof BUNDLED_MODE === 'undefined') { | ||
| const sdkPath = getSdkRipgrepPath() | ||
| extractedPaths[key] = sdkPath | ||
| return sdkPath | ||
| } |
There was a problem hiding this comment.
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.
| // 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.
Summary by CodeRabbit
New Features
--compilefeatureChores