-
Notifications
You must be signed in to change notification settings - Fork 3
feat: support claude-code 2.1.245 esm-chunked native format #336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,2 @@ | ||
| *.tgz | ||
| /.verify-*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -770,6 +770,14 @@ | |
| "tarball_integrity": "sha512-tHsJhjwcoFyKTCQfYCclKjLtbnbbGKvcrzxUhMb6NDlPw9RrawmjSWUAdla7PtEVPaJsGkjGo4O2MQTlYIT+PA==", | ||
| "tarball_sha256": "41de896e672667bc5b5060419d5a1ff5cfe15b77659d1418b10df5095e38419a", | ||
| "status": "termux_verified" | ||
| }, | ||
| "2.1.245": { | ||
| "wrapper_spec": "@anthropic-ai/[email protected]", | ||
| "native_spec": "@anthropic-ai/[email protected]", | ||
| "entry_format": "esm-chunked", | ||
| "tarball_integrity": "sha512-Qbn5HnZbYeW4GdifVkDGfcVKqj3/f3U9sfVd3LEaUZh08CcS8D/ptJ76zuBfQUGHJHfToAdNVfak86uJ84b1Dg==", | ||
| "tarball_sha256": "668662e7b5d91a93cff6c75736e60f3d5d3bed4cbe077ae4feefc63ad1253f4d", | ||
| "status": "offset_discovered" | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -770,6 +770,14 @@ | |
| "tarball_integrity": "sha512-tHsJhjwcoFyKTCQfYCclKjLtbnbbGKvcrzxUhMb6NDlPw9RrawmjSWUAdla7PtEVPaJsGkjGo4O2MQTlYIT+PA==", | ||
| "tarball_sha256": "41de896e672667bc5b5060419d5a1ff5cfe15b77659d1418b10df5095e38419a", | ||
| "status": "termux_verified" | ||
| }, | ||
| "2.1.245": { | ||
| "wrapper_spec": "@anthropic-ai/[email protected]", | ||
| "native_spec": "@anthropic-ai/[email protected]", | ||
| "entry_format": "esm-chunked", | ||
| "tarball_integrity": "sha512-Qbn5HnZbYeW4GdifVkDGfcVKqj3/f3U9sfVd3LEaUZh08CcS8D/ptJ76zuBfQUGHJHfToAdNVfak86uJ84b1Dg==", | ||
| "tarball_sha256": "668662e7b5d91a93cff6c75736e60f3d5d3bed4cbe077ae4feefc63ad1253f4d", | ||
| "status": "offset_discovered" | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { createRequire } from 'node:module'; | ||
|
|
||
| const require = createRequire(import.meta.url); | ||
| const realChildProcess = require('node:child_process'); | ||
| const { createGuardedChildProcess } = require('./native-update-guard.js'); | ||
| const guarded = createGuardedChildProcess(realChildProcess, (v) => process.stderr.write(v)); | ||
|
|
||
| export const spawn = guarded.spawn; | ||
| export const execFile = guarded.execFile; | ||
| export const exec = guarded.exec; | ||
| export const spawnSync = guarded.spawnSync; | ||
| export const execFileSync = guarded.execFileSync; | ||
| export const execSync = guarded.execSync; | ||
| export const ChildProcess = realChildProcess.ChildProcess; | ||
| export const fork = realChildProcess.fork; | ||
| export const _forkChild = realChildProcess._forkChild; | ||
|
|
||
| export default Object.assign({}, realChildProcess, guarded); | ||
113 changes: 113 additions & 0 deletions
113
packages/claude-code/lib/bunfs-child-process-guard.test.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| 'use strict'; | ||
|
|
||
| const test = require('node:test'); | ||
| const assert = require('node:assert/strict'); | ||
|
|
||
| test('bunfs-child-process-guard exports named exports for guarded methods', async () => { | ||
| const guard = await import('./bunfs-child-process-guard.mjs'); | ||
|
|
||
| assert.equal(typeof guard.spawn, 'function'); | ||
| assert.equal(typeof guard.execFile, 'function'); | ||
| assert.equal(typeof guard.exec, 'function'); | ||
| assert.equal(typeof guard.spawnSync, 'function'); | ||
| assert.equal(typeof guard.execFileSync, 'function'); | ||
| assert.equal(typeof guard.execSync, 'function'); | ||
| }); | ||
|
|
||
| test('bunfs-child-process-guard exports ChildProcess, fork, _forkChild', async () => { | ||
| const guard = await import('./bunfs-child-process-guard.mjs'); | ||
|
|
||
| // ChildProcess is a class | ||
| assert.equal(typeof guard.ChildProcess, 'function'); | ||
| // fork and _forkChild are functions | ||
| assert.equal(typeof guard.fork, 'function'); | ||
| assert.equal(typeof guard._forkChild, 'function'); | ||
| }); | ||
|
|
||
| test('bunfs-child-process-guard provides default export with all methods', async () => { | ||
| const guard = await import('./bunfs-child-process-guard.mjs'); | ||
| const defaultExport = guard.default; | ||
|
|
||
| assert.ok(defaultExport); | ||
| assert.equal(typeof defaultExport, 'object'); | ||
| assert.equal(typeof defaultExport.spawn, 'function'); | ||
| assert.equal(typeof defaultExport.execFile, 'function'); | ||
| assert.equal(typeof defaultExport.exec, 'function'); | ||
| assert.equal(typeof defaultExport.spawnSync, 'function'); | ||
| assert.equal(typeof defaultExport.execFileSync, 'function'); | ||
| assert.equal(typeof defaultExport.execSync, 'function'); | ||
| assert.equal(typeof defaultExport.ChildProcess, 'function'); | ||
| assert.equal(typeof defaultExport.fork, 'function'); | ||
| assert.equal(typeof defaultExport._forkChild, 'function'); | ||
| }); | ||
|
|
||
| test('bunfs-child-process-guard blocks official package update via execFileSync', async () => { | ||
| const guard = await import('./bunfs-child-process-guard.mjs'); | ||
|
|
||
| // Try to execute npm install of official package | ||
| try { | ||
| guard.execFileSync('npm', ['install', '-g', '@anthropic-ai/claude-code@latest']); | ||
| // If it doesn't throw, that's an error (should be blocked) | ||
| assert.fail('Expected execFileSync to block official package update'); | ||
| } catch (err) { | ||
| // Should throw with CLAUDE_TERMUX_OFFICIAL_UPDATE_BLOCKED code | ||
| assert.ok( | ||
| err.code === 'CLAUDE_TERMUX_OFFICIAL_UPDATE_BLOCKED' || | ||
| err.message.includes('disabled on Termux'), | ||
| `Expected block error, got: ${err.message}`, | ||
| ); | ||
| } | ||
| }); | ||
|
|
||
| test('bunfs-child-process-guard blocks official package update via execSync', async () => { | ||
| const guard = await import('./bunfs-child-process-guard.mjs'); | ||
|
|
||
| // Try to execute npm install via exec | ||
| try { | ||
| guard.execSync('npm install -g @anthropic-ai/claude-code@latest'); | ||
| // If it doesn't throw, that's an error (should be blocked) | ||
| assert.fail('Expected execSync to block official package update'); | ||
| } catch (err) { | ||
| // Should throw with CLAUDE_TERMUX_OFFICIAL_UPDATE_BLOCKED code | ||
| assert.ok( | ||
| err.code === 'CLAUDE_TERMUX_OFFICIAL_UPDATE_BLOCKED' || | ||
| err.message.includes('disabled on Termux'), | ||
| `Expected block error, got: ${err.message}`, | ||
| ); | ||
| } | ||
| }); | ||
|
|
||
| test('bunfs-child-process-guard allows harmless commands', async () => { | ||
| const guard = await import('./bunfs-child-process-guard.mjs'); | ||
|
|
||
| // echo is a harmless command and should not be blocked | ||
| const result = guard.execFileSync('echo', ['hello']); | ||
| // Verify the command actually executed and produced output | ||
| assert.equal(result.toString().trim(), 'hello'); | ||
| }); | ||
|
|
||
| test('bunfs-child-process-guard spawn blocks official package install', async () => { | ||
| const guard = await import('./bunfs-child-process-guard.mjs'); | ||
|
|
||
| // spawn should return a blocked child process (EventEmitter-like) | ||
| const child = guard.spawn('npm', ['install', '-g', '@anthropic-ai/claude-code']); | ||
|
|
||
| // Blocked spawn should have specific properties | ||
| assert.equal(child.stdout, null); | ||
| assert.equal(child.stderr, null); | ||
| assert.equal(child.stdin, null); | ||
| assert.equal(child.pid, 0); | ||
| assert.equal(child.killed, false); | ||
|
|
||
| // Verify it's event-like (has on method or can be used as event emitter) | ||
| assert.equal(typeof child.kill, 'function'); | ||
| }); | ||
|
|
||
| test('bunfs-child-process-guard does not block other package installs', async () => { | ||
| const guard = await import('./bunfs-child-process-guard.mjs'); | ||
|
|
||
| // Installing a different package should not be blocked | ||
| const result = guard.execFileSync('echo', ['@bash0816/claude-code']); | ||
| // Verify the command actually executed and produced correct output | ||
| assert.equal(result.toString().trim(), '@bash0816/claude-code'); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| import { pathToFileURL, fileURLToPath } from 'node:url'; | ||
| import { existsSync, readFileSync } from 'node:fs'; | ||
| import { createRequire } from 'node:module'; | ||
| import path from 'node:path'; | ||
|
|
||
| let PROCESS_OWNED_DIR = null; | ||
| let SOURCE_BIN = null; | ||
| let CHILD_PROCESS_GUARD_PATH = null; | ||
| let VM_GUARD_PATH = null; | ||
| let WS_STUB_PATH = null; | ||
|
|
||
| export function initialize(data) { | ||
| PROCESS_OWNED_DIR = data.processOwnedDir; | ||
| SOURCE_BIN = data.sourceBin; | ||
| CHILD_PROCESS_GUARD_PATH = data.childProcessGuardPath; | ||
| VM_GUARD_PATH = data.vmGuardPath; | ||
| WS_STUB_PATH = data.wsStubPath; | ||
| } | ||
|
|
||
| function buildImportMetaRequirePolyfillPrelude(anchorUrl) { | ||
| return ( | ||
| `import __bunfsGuardedChildProcess from ${JSON.stringify(pathToFileURL(CHILD_PROCESS_GUARD_PATH).href)};\n` + | ||
| `import __bunfsGuardedVm from ${JSON.stringify(pathToFileURL(VM_GUARD_PATH).href)};\n` + | ||
| `import { createRequire as __bunfsCreateRequire } from "node:module";\n` + | ||
| `const __bunfsRealRequire = __bunfsCreateRequire(${JSON.stringify(anchorUrl)});\n` + | ||
| `const __bunfsMetaRequire = (id) => {\n` + | ||
| ` if (id === "child_process" || id === "node:child_process") return __bunfsGuardedChildProcess;\n` + | ||
| ` if (id === "vm" || id === "node:vm") return __bunfsGuardedVm;\n` + | ||
| ` return __bunfsRealRequire(id);\n` + | ||
| `};\n` | ||
| ); | ||
| } | ||
|
|
||
| export async function resolve(specifier, context, nextResolve) { | ||
| if (specifier === 'child_process' || specifier === 'node:child_process') { | ||
| return { url: pathToFileURL(CHILD_PROCESS_GUARD_PATH).href, shortCircuit: true, format: 'module' }; | ||
| } | ||
| if (specifier === 'vm' || specifier === 'node:vm') { | ||
| return { url: pathToFileURL(VM_GUARD_PATH).href, shortCircuit: true, format: 'module' }; | ||
| } | ||
| if (specifier === 'ws') { | ||
| return { url: pathToFileURL(WS_STUB_PATH).href, shortCircuit: true, format: 'module' }; | ||
| } | ||
| if (specifier.startsWith('/$bunfs/root/')) { | ||
| const rel = specifier.slice('/$bunfs/root/'.length); | ||
| if (rel.includes('..') || path.isAbsolute(rel)) { | ||
| throw new Error(`bunfs resolve: rejected specifier ${specifier}`); | ||
| } | ||
| const real = path.resolve(PROCESS_OWNED_DIR, rel); | ||
| if (path.relative(PROCESS_OWNED_DIR, real).startsWith('..')) { | ||
| throw new Error(`bunfs resolve: path escapes process-owned dir: ${specifier}`); | ||
| } | ||
| if (!existsSync(real)) { | ||
| throw new Error(`bunfs resolve: missing extracted module ${specifier} -> ${real}`); | ||
| } | ||
| return { url: pathToFileURL(real).href, shortCircuit: true, format: 'module' }; | ||
| } | ||
| return nextResolve(specifier, context); | ||
| } | ||
|
|
||
| export async function load(url, context, nextLoad) { | ||
| const ownedPrefix = pathToFileURL(PROCESS_OWNED_DIR + path.sep).href; | ||
| if (!url.startsWith(ownedPrefix)) { | ||
| return nextLoad(url, context); | ||
| } | ||
| const filePath = fileURLToPath(url); | ||
| let source = readFileSync(filePath, 'utf8'); | ||
| if (source.includes('import.meta.require')) { | ||
| const anchorUrl = pathToFileURL(SOURCE_BIN).href; | ||
| source = buildImportMetaRequirePolyfillPrelude(anchorUrl) + | ||
| source.replaceAll('import.meta.require', '__bunfsMetaRequire'); | ||
| } | ||
| return { format: 'module', source, shortCircuit: true }; | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the ESM CLI opens a browser during authentication or another URL-opening flow, this guard forwards
xdg-openunchanged to the real child-process module, but Termux providestermux-open-urlinstead. The legacy path explicitly rewritesxdg-openin bothspawnandspawnSync, and the launcher still requirestermux-open-url; therefore the newesm-chunkedpath regresses those flows unless the same rewrite is applied before creating the guarded methods.Useful? React with 👍 / 👎.