@bash0816/claude-code 2.1.245 → 2.1.248

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,7 +37,7 @@ npm が `claude` bin link を管理する状態になれば、通常の `npm ins
37
37
  Latest audited version / 最新監査済み版:
38
38
 
39
39
  ```sh
40
- npm install -g @bash0816/claude-code@2.1.241
40
+ npm install -g @bash0816/claude-code@2.1.245
41
41
  ```
42
42
 
43
43
  ## Update / 更新
package/bin/claude CHANGED
@@ -16,6 +16,16 @@ NODE="${MAGI_NODE:-node}"
16
16
  if [ -n "${MAGI_NODE:-}" ]; then
17
17
  export PATH="$(dirname -- "$NODE"):$PATH"
18
18
  fi
19
+
20
+ if ! "$NODE" -e '
21
+ const [major, minor] = process.versions.node.split(".").map(Number);
22
+ const ok = major > 23 || (major === 23 && minor >= 5) || (major === 22 && minor >= 15);
23
+ process.exit(ok ? 0 : 1);
24
+ '; then
25
+ echo "Error: claude-code requires Node.js >=22.15.0 <23.0.0 or >=23.5.0 (module.registerHooks() API). Detected: $("$NODE" --version)" >&2
26
+ exit 1
27
+ fi
28
+
19
29
  CONFIG_FILE="${PACKAGE_DIR}/config/claude-native-audited-versions.json"
20
30
  PACKAGE_VERSION=$("$NODE" -e 'const fs=require("fs"); const path=require("path"); const p=JSON.parse(fs.readFileSync(path.join(process.argv[1],"package.json"),"utf8")); process.stdout.write(p.version)' "${PACKAGE_DIR}")
21
31
  CLAUDE_VERSION="${CLAUDE_TERMUX_CLAUDE_VERSION:-${PACKAGE_VERSION}}"
@@ -777,7 +777,29 @@
777
777
  "entry_format": "esm-chunked",
778
778
  "tarball_integrity": "sha512-Qbn5HnZbYeW4GdifVkDGfcVKqj3/f3U9sfVd3LEaUZh08CcS8D/ptJ76zuBfQUGHJHfToAdNVfak86uJ84b1Dg==",
779
779
  "tarball_sha256": "668662e7b5d91a93cff6c75736e60f3d5d3bed4cbe077ae4feefc63ad1253f4d",
780
- "status": "offset_discovered"
780
+ "cycle_hoists": [],
781
+ "status": "termux_verified"
782
+ },
783
+ "2.1.248": {
784
+ "wrapper_spec": "@anthropic-ai/claude-code@2.1.248",
785
+ "native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.248",
786
+ "entry_format": "esm-chunked",
787
+ "tarball_integrity": "sha512-2qwjgGuzlVfkJC7UWUjl86hiv5Ok5vtVxAZ/5fpdhzQKuBLlxrJJu3z5tVI+VrO/x/hcqx5kJuy9/O9K4eT+Kg==",
788
+ "tarball_sha256": "bd2e2b6ebace34039b073b0f586d521ec09c895fa2a39de8d1c86423ae5884ca",
789
+ "status": "offset_discovered",
790
+ "num_modules": 1946,
791
+ "byte_count": 136124175,
792
+ "cycle_hoists": [
793
+ { "file": "chunk-vmw9kxhv.js", "targetModule": "chunk-y0jj307t.js", "expectedOccurrences": 1, "assertProperties": [] },
794
+ { "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-ppd18hq1.js", "expectedOccurrences": 1, "assertProperties": ["MonitorTool"] },
795
+ { "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-830s9480.js", "expectedOccurrences": 1, "assertProperties": ["EndConversationTool"] },
796
+ { "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-m4hx5z9g.js", "expectedOccurrences": 1, "assertProperties": ["ArtifactTool"] },
797
+ { "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-eg121wbt.js", "expectedOccurrences": 1, "assertProperties": ["WorkflowTool"] },
798
+ { "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-prrm838s.js", "expectedOccurrences": 1, "assertProperties": [] },
799
+ { "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-exvwsc2a.js", "expectedOccurrences": 2, "assertProperties": [] },
800
+ { "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-s61yn21a.js", "expectedOccurrences": 1, "assertProperties": ["default"] },
801
+ { "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-040m9a9s.js", "expectedOccurrences": 3, "assertProperties": ["getWorkflowCommands", "invalidateWorkflowCache", "warmWorkflows"] }
802
+ ]
781
803
  }
782
804
  }
783
805
  }
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "manifest_version": 1,
3
3
  "package_name": "@bash0816/claude-code",
4
- "latest_audited_version": "2.1.241",
5
- "latest_candidate_version": "2.1.245",
6
- "previous_stable_version": "2.1.240",
4
+ "latest_audited_version": "2.1.245",
5
+ "latest_candidate_version": "2.1.248",
6
+ "previous_stable_version": "2.1.241",
7
7
  "stable_pinned_version": "2.1.220-2",
8
8
  "manifest_url": "https://raw.githubusercontent.com/bash0816/ClaudeCode-Termux/main/config/claude-termux-release-manifest.json"
9
9
  }
@@ -9,12 +9,50 @@ let CHILD_PROCESS_GUARD_PATH = null;
9
9
  let VM_GUARD_PATH = null;
10
10
  let WS_STUB_PATH = null;
11
11
 
12
+ let CYCLE_HOISTS = [];
13
+
12
14
  export function initialize(data) {
13
15
  PROCESS_OWNED_DIR = data.processOwnedDir;
14
16
  SOURCE_BIN = data.sourceBin;
15
17
  CHILD_PROCESS_GUARD_PATH = data.childProcessGuardPath;
16
18
  VM_GUARD_PATH = data.vmGuardPath;
17
19
  WS_STUB_PATH = data.wsStubPath;
20
+ CYCLE_HOISTS = Array.isArray(data.cycleHoists) ? data.cycleHoists : [];
21
+ }
22
+
23
+ function tryHoistCycleBreakingImports(filePath, source) {
24
+ const rel = path.relative(PROCESS_OWNED_DIR, filePath);
25
+ const records = CYCLE_HOISTS.filter((r) => r.file === rel);
26
+ if (records.length === 0) return null;
27
+
28
+ let hoistedImportLines = '';
29
+ let newSource = source;
30
+ let varIndex = 0;
31
+ const targetToVar = new Map();
32
+
33
+ for (const record of records) {
34
+ const literal = `import.meta.require("/$bunfs/root/${record.targetModule}")`;
35
+ const occurrences = newSource.split(literal).length - 1;
36
+ if (occurrences !== record.expectedOccurrences) continue;
37
+
38
+ const real = path.resolve(PROCESS_OWNED_DIR, record.targetModule);
39
+ if (path.relative(PROCESS_OWNED_DIR, real).startsWith('..')) continue;
40
+ if (!existsSync(real)) continue;
41
+
42
+ let varName = targetToVar.get(record.targetModule);
43
+ if (!varName) {
44
+ varName = `__bunfsHoisted_${varIndex++}`;
45
+ targetToVar.set(record.targetModule, varName);
46
+ hoistedImportLines += `import * as ${varName} from ${JSON.stringify(pathToFileURL(real).href)};\n`;
47
+ if (record.assertProperties && record.assertProperties.length > 0) {
48
+ hoistedImportLines += `__bunfsAssertHoistedProps(${varName}, ${JSON.stringify(record.targetModule)}, ${JSON.stringify(record.assertProperties)});\n`;
49
+ }
50
+ }
51
+ newSource = newSource.replaceAll(literal, varName);
52
+ }
53
+
54
+ if (!targetToVar.size) return null;
55
+ return { hoistedImportLine: hoistedImportLines, source: newSource };
18
56
  }
19
57
 
20
58
  function buildImportMetaRequirePolyfillPrelude(anchorUrl) {
@@ -22,24 +60,68 @@ function buildImportMetaRequirePolyfillPrelude(anchorUrl) {
22
60
  `import __bunfsGuardedChildProcess from ${JSON.stringify(pathToFileURL(CHILD_PROCESS_GUARD_PATH).href)};\n` +
23
61
  `import __bunfsGuardedVm from ${JSON.stringify(pathToFileURL(VM_GUARD_PATH).href)};\n` +
24
62
  `import { createRequire as __bunfsCreateRequire } from "node:module";\n` +
63
+ `import __bunfsMetaRequirePath from "node:path";\n` +
64
+ `import { existsSync as __bunfsMetaRequireExistsSync } from "node:fs";\n` +
65
+ `import { readFileSync as __bunfsMetaRequireReadFileSync } from "node:fs";\n` +
66
+ `function __bunfsAssertHoistedProps(ns, targetModule, propNames) {\n` +
67
+ ` for (const p of propNames) {\n` +
68
+ ` let v;\n` +
69
+ ` try {\n` +
70
+ ` v = ns[p];\n` +
71
+ ` } catch (e) {\n` +
72
+ ` if (e instanceof ReferenceError) {\n` +
73
+ ` throw new Error("bunfs cycle-hoist: accessing \\"" + p + "\\" on " + targetModule + " threw ReferenceError (TDZ) - evaluation-order regression");\n` +
74
+ ` }\n` +
75
+ ` throw e;\n` +
76
+ ` }\n` +
77
+ ` if (v === undefined) {\n` +
78
+ ` throw new Error("bunfs cycle-hoist: \\"" + p + "\\" is undefined after hoisting from " + targetModule + " (evaluation-order regression?)");\n` +
79
+ ` }\n` +
80
+ ` }\n` +
81
+ `}\n` +
25
82
  `const __bunfsRealRequire = __bunfsCreateRequire(${JSON.stringify(anchorUrl)});\n` +
83
+ `const __bunfsOwnedDir = ${JSON.stringify(PROCESS_OWNED_DIR)};\n` +
26
84
  `const __bunfsMetaRequire = (id) => {\n` +
27
85
  ` if (id === "child_process" || id === "node:child_process") return __bunfsGuardedChildProcess;\n` +
28
86
  ` if (id === "vm" || id === "node:vm") return __bunfsGuardedVm;\n` +
87
+ ` if (typeof id === "string" && id.startsWith("/$bunfs/root/")) {\n` +
88
+ ` const rel = id.slice("/$bunfs/root/".length);\n` +
89
+ ` if (rel.includes("..") || __bunfsMetaRequirePath.isAbsolute(rel)) {\n` +
90
+ ` throw new Error("bunfs meta-require: rejected specifier " + id);\n` +
91
+ ` }\n` +
92
+ ` const real = __bunfsMetaRequirePath.resolve(__bunfsOwnedDir, rel);\n` +
93
+ ` if (__bunfsMetaRequirePath.relative(__bunfsOwnedDir, real).startsWith("..")) {\n` +
94
+ ` throw new Error("bunfs meta-require: path escapes process-owned dir: " + id);\n` +
95
+ ` }\n` +
96
+ ` if (!__bunfsMetaRequireExistsSync(real)) {\n` +
97
+ ` throw new Error("bunfs meta-require: missing extracted module " + id + " -> " + real);\n` +
98
+ ` }\n` +
99
+ ` const ext = __bunfsMetaRequirePath.extname(real);\n` +
100
+ ` if (ext === ".md" || ext === ".txt") {\n` +
101
+ ` return __bunfsMetaRequireReadFileSync(real, "utf8");\n` +
102
+ ` }\n` +
103
+ ` return __bunfsRealRequire(real);\n` +
104
+ ` }\n` +
29
105
  ` return __bunfsRealRequire(id);\n` +
30
106
  `};\n`
31
107
  );
32
108
  }
33
109
 
34
- export async function resolve(specifier, context, nextResolve) {
35
- if (specifier === 'child_process' || specifier === 'node:child_process') {
36
- return { url: pathToFileURL(CHILD_PROCESS_GUARD_PATH).href, shortCircuit: true, format: 'module' };
37
- }
38
- if (specifier === 'vm' || specifier === 'node:vm') {
39
- return { url: pathToFileURL(VM_GUARD_PATH).href, shortCircuit: true, format: 'module' };
40
- }
41
- if (specifier === 'ws') {
42
- return { url: pathToFileURL(WS_STUB_PATH).href, shortCircuit: true, format: 'module' };
110
+ export function resolve(specifier, context, nextResolve) {
111
+ const parentURL = context && context.parentURL;
112
+ const ownedPrefix = pathToFileURL(PROCESS_OWNED_DIR + path.sep).href;
113
+ const fromChunk = typeof parentURL === 'string' && parentURL.startsWith(ownedPrefix);
114
+
115
+ if (fromChunk) {
116
+ if (specifier === 'child_process' || specifier === 'node:child_process') {
117
+ return { url: pathToFileURL(CHILD_PROCESS_GUARD_PATH).href, shortCircuit: true, format: 'module' };
118
+ }
119
+ if (specifier === 'vm' || specifier === 'node:vm') {
120
+ return { url: pathToFileURL(VM_GUARD_PATH).href, shortCircuit: true, format: 'module' };
121
+ }
122
+ if (specifier === 'ws') {
123
+ return { url: pathToFileURL(WS_STUB_PATH).href, shortCircuit: true, format: 'module' };
124
+ }
43
125
  }
44
126
  if (specifier.startsWith('/$bunfs/root/')) {
45
127
  const rel = specifier.slice('/$bunfs/root/'.length);
@@ -58,17 +140,28 @@ export async function resolve(specifier, context, nextResolve) {
58
140
  return nextResolve(specifier, context);
59
141
  }
60
142
 
61
- export async function load(url, context, nextLoad) {
143
+ export function load(url, context, nextLoad) {
62
144
  const ownedPrefix = pathToFileURL(PROCESS_OWNED_DIR + path.sep).href;
63
145
  if (!url.startsWith(ownedPrefix)) {
64
146
  return nextLoad(url, context);
65
147
  }
66
148
  const filePath = fileURLToPath(url);
67
149
  let source = readFileSync(filePath, 'utf8');
150
+
151
+ let hoistedImportLine = '';
152
+ const hoistResult = tryHoistCycleBreakingImports(filePath, source);
153
+ if (hoistResult) {
154
+ hoistedImportLine = hoistResult.hoistedImportLine;
155
+ source = hoistResult.source;
156
+ }
157
+
68
158
  if (source.includes('import.meta.require')) {
69
159
  const anchorUrl = pathToFileURL(SOURCE_BIN).href;
70
- source = buildImportMetaRequirePolyfillPrelude(anchorUrl) +
160
+ source = hoistedImportLine +
161
+ buildImportMetaRequirePolyfillPrelude(anchorUrl) +
71
162
  source.replaceAll('import.meta.require', '__bunfsMetaRequire');
163
+ } else if (hoistedImportLine) {
164
+ source = hoistedImportLine + source;
72
165
  }
73
166
  return { format: 'module', source, shortCircuit: true };
74
167
  }
@@ -33,15 +33,15 @@ test('resolve() handles child_process and node:child_process specifiers', async
33
33
  wsStubPath: path.join(tempDir, 'ws-stub.mjs'),
34
34
  });
35
35
 
36
- const nextResolve = async (spec, ctx) => ({ url: `unresolved:${spec}` });
36
+ const nextResolve = (spec, ctx) => ({ url: `unresolved:${spec}` });
37
37
 
38
38
  // child_process should resolve to childProcessGuardPath
39
- const result1 = await loader.resolve('child_process', {}, nextResolve);
39
+ const result1 = loader.resolve('child_process', { parentURL: pathToFileURL(path.join(tempDir, 'dummy-chunk.js')).href }, nextResolve);
40
40
  assert.ok(result1.url.includes(guardPath));
41
41
  assert.equal(result1.shortCircuit, true);
42
42
 
43
43
  // node:child_process should also resolve to childProcessGuardPath
44
- const result2 = await loader.resolve('node:child_process', {}, nextResolve);
44
+ const result2 = loader.resolve('node:child_process', { parentURL: pathToFileURL(path.join(tempDir, 'dummy-chunk.js')).href }, nextResolve);
45
45
  assert.ok(result2.url.includes(guardPath));
46
46
  assert.equal(result2.shortCircuit, true);
47
47
  } finally {
@@ -65,15 +65,15 @@ test('resolve() handles vm and node:vm specifiers', async () => {
65
65
  wsStubPath: path.join(tempDir, 'ws-stub.mjs'),
66
66
  });
67
67
 
68
- const nextResolve = async (spec, ctx) => ({ url: `unresolved:${spec}` });
68
+ const nextResolve = (spec, ctx) => ({ url: `unresolved:${spec}` });
69
69
 
70
70
  // vm should resolve to vmGuardPath
71
- const result1 = await loader.resolve('vm', {}, nextResolve);
71
+ const result1 = loader.resolve('vm', { parentURL: pathToFileURL(path.join(tempDir, 'dummy-chunk.js')).href }, nextResolve);
72
72
  assert.ok(result1.url.includes(vmGuardPath));
73
73
  assert.equal(result1.shortCircuit, true);
74
74
 
75
75
  // node:vm should also resolve to vmGuardPath
76
- const result2 = await loader.resolve('node:vm', {}, nextResolve);
76
+ const result2 = loader.resolve('node:vm', { parentURL: pathToFileURL(path.join(tempDir, 'dummy-chunk.js')).href }, nextResolve);
77
77
  assert.ok(result2.url.includes(vmGuardPath));
78
78
  assert.equal(result2.shortCircuit, true);
79
79
  } finally {
@@ -97,9 +97,9 @@ test('resolve() handles ws specifier', async () => {
97
97
  wsStubPath: wsStubPath,
98
98
  });
99
99
 
100
- const nextResolve = async (spec, ctx) => ({ url: `unresolved:${spec}` });
100
+ const nextResolve = (spec, ctx) => ({ url: `unresolved:${spec}` });
101
101
 
102
- const result = await loader.resolve('ws', {}, nextResolve);
102
+ const result = loader.resolve('ws', { parentURL: pathToFileURL(path.join(tempDir, 'dummy-chunk.js')).href }, nextResolve);
103
103
  assert.ok(result.url.includes(wsStubPath));
104
104
  assert.equal(result.shortCircuit, true);
105
105
  } finally {
@@ -150,9 +150,9 @@ test('resolve() rejects path traversal with ..', async () => {
150
150
  wsStubPath: path.join(tempDir, 'ws-stub.mjs'),
151
151
  });
152
152
 
153
- const nextResolve = async (spec, ctx) => ({ url: `unresolved:${spec}` });
153
+ const nextResolve = (spec, ctx) => ({ url: `unresolved:${spec}` });
154
154
 
155
- await assert.rejects(
155
+ assert.throws(
156
156
  () => loader.resolve('/$bunfs/root/../../etc/passwd', {}, nextResolve),
157
157
  /rejected specifier|escapes/,
158
158
  );
@@ -175,9 +175,9 @@ test('resolve() rejects absolute paths', async () => {
175
175
  wsStubPath: path.join(tempDir, 'ws-stub.mjs'),
176
176
  });
177
177
 
178
- const nextResolve = async (spec, ctx) => ({ url: `unresolved:${spec}` });
178
+ const nextResolve = (spec, ctx) => ({ url: `unresolved:${spec}` });
179
179
 
180
- await assert.rejects(
180
+ assert.throws(
181
181
  () => loader.resolve('/$bunfs/root//etc/passwd', {}, nextResolve),
182
182
  /rejected specifier|escapes/,
183
183
  );
@@ -200,9 +200,9 @@ test('resolve() throws error for missing extracted module', async () => {
200
200
  wsStubPath: path.join(tempDir, 'ws-stub.mjs'),
201
201
  });
202
202
 
203
- const nextResolve = async (spec, ctx) => ({ url: `unresolved:${spec}` });
203
+ const nextResolve = (spec, ctx) => ({ url: `unresolved:${spec}` });
204
204
 
205
- await assert.rejects(
205
+ assert.throws(
206
206
  () => loader.resolve('/$bunfs/root/nonexistent.js', {}, nextResolve),
207
207
  /missing extracted module/,
208
208
  );
@@ -327,3 +327,162 @@ test('load() calls nextLoad for URLs outside processOwnedDir', async () => {
327
327
  fs.rmSync(tempDir, { recursive: true, force: true });
328
328
  }
329
329
  });
330
+
331
+ test('load() hoists the cycle-breaking import.meta.require call in chunk-vmw9kxhv.js', async () => {
332
+ const loader = await import('./bunfs-esm-loader.mjs');
333
+ const tempDir = path.join(os.tmpdir(), `bunfs-esm-loader-hoist-test-${process.pid}-${Date.now()}`);
334
+ fs.mkdirSync(tempDir, { recursive: true });
335
+
336
+ fs.writeFileSync(path.join(tempDir, 'chunk-y0jj307t.js'), 'export const daemonColdStartGbDefault = () => "fixture";\n');
337
+ const targetFile = path.join(tempDir, 'chunk-vmw9kxhv.js');
338
+ const sourceCode = 'var O9=import.meta.require("/$bunfs/root/chunk-y0jj307t.js");\nexport const value = O9.daemonColdStartGbDefault();\n';
339
+ fs.writeFileSync(targetFile, sourceCode);
340
+
341
+ try {
342
+ loader.initialize({
343
+ processOwnedDir: tempDir,
344
+ sourceBin: '/dummy/bin',
345
+ childProcessGuardPath: path.join(tempDir, 'guard.mjs'),
346
+ vmGuardPath: path.join(tempDir, 'vm-guard.mjs'),
347
+ wsStubPath: path.join(tempDir, 'ws-stub.mjs'),
348
+ cycleHoists: [{ file: 'chunk-vmw9kxhv.js', targetModule: 'chunk-y0jj307t.js', expectedOccurrences: 1, assertProperties: [] }],
349
+ });
350
+
351
+ const fileUrl = pathToFileURL(targetFile).href;
352
+ const result = await loader.load(fileUrl, {}, async () => ({ source: 'fallback' }));
353
+
354
+ assert.equal(result.format, 'module');
355
+ assert.ok(result.source.includes('import * as __bunfsHoisted_0 from'));
356
+ assert.ok(!result.source.includes('var O9=import.meta.require('));
357
+ assert.ok(result.source.includes('var O9=__bunfsHoisted_0'));
358
+ assert.equal(result.shortCircuit, true);
359
+ } finally {
360
+ fs.rmSync(tempDir, { recursive: true, force: true });
361
+ }
362
+ });
363
+
364
+ test('import.meta.require resolves /$bunfs/root/ specifiers via loader integration', async () => {
365
+ const { registerHooks } = await import('node:module');
366
+ const loader = await import('./bunfs-esm-loader.mjs');
367
+ const tempDir = path.join(os.tmpdir(), `bunfs-esm-loader-integration-${process.pid}-${Date.now()}`);
368
+ fs.mkdirSync(tempDir, { recursive: true });
369
+
370
+ try {
371
+ // Create guard files
372
+ fs.writeFileSync(path.join(tempDir, 'guard.mjs'), 'export default {};');
373
+ fs.writeFileSync(path.join(tempDir, 'vm-guard.mjs'), 'export default {};');
374
+ fs.writeFileSync(path.join(tempDir, 'ws-stub.mjs'), 'export default {};');
375
+
376
+ // Create ESM fixture that will be required via import.meta.require (normal case).
377
+ // PROCESS_OWNED_DIR only ever contains genuine ESM chunk files extracted from the
378
+ // Bun esm-chunked bundle (verified against a real 2.1.248 extraction: 0 of 1768
379
+ // chunk files are CommonJS), so load() always returns format: 'module' for this dir.
380
+ fs.writeFileSync(path.join(tempDir, 'foo.js'), 'export const value = 42;');
381
+
382
+ // Create ESM files for each test scenario
383
+ const okCallerPath = path.join(tempDir, 'ok-caller.mjs');
384
+ fs.writeFileSync(okCallerPath, 'export const result = import.meta.require("/$bunfs/root/foo.js").value;\n');
385
+
386
+ const traversalCallerPath = path.join(tempDir, 'traversal-caller.mjs');
387
+ fs.writeFileSync(traversalCallerPath, 'import.meta.require("/$bunfs/root/../../etc/passwd");\n');
388
+
389
+ const missingCallerPath = path.join(tempDir, 'missing-caller.mjs');
390
+ fs.writeFileSync(missingCallerPath, 'import.meta.require("/$bunfs/root/nonexistent.js");\n');
391
+
392
+ // Register loader (only once) with data
393
+ const sourceBin = path.join(tempDir, 'dummy-bin');
394
+ fs.writeFileSync(sourceBin, '#!/bin/false');
395
+
396
+ loader.initialize({
397
+ processOwnedDir: tempDir,
398
+ sourceBin: sourceBin,
399
+ childProcessGuardPath: path.join(tempDir, 'guard.mjs'),
400
+ vmGuardPath: path.join(tempDir, 'vm-guard.mjs'),
401
+ wsStubPath: path.join(tempDir, 'ws-stub.mjs'),
402
+ cycleHoists: [
403
+ { file: 'chunk-vmw9kxhv.js', targetModule: 'chunk-y0jj307t.js', expectedOccurrences: 1, assertProperties: [] },
404
+ ],
405
+ });
406
+ registerHooks({ resolve: loader.resolve, load: loader.load });
407
+
408
+ // Test 1: Normal case - should load and resolve correctly
409
+ const okModule = await import(pathToFileURL(okCallerPath).href);
410
+ assert.equal(okModule.result, 42);
411
+
412
+ // Test 2: Path traversal rejection - should throw error
413
+ await assert.rejects(
414
+ () => import(pathToFileURL(traversalCallerPath).href),
415
+ /rejected specifier|escapes/,
416
+ );
417
+
418
+ // Test 3: Missing module rejection - should throw error
419
+ await assert.rejects(
420
+ () => import(pathToFileURL(missingCallerPath).href),
421
+ /missing extracted module/,
422
+ );
423
+
424
+ // Cycle regression proof: a generic sync-require-into-in-flight-static-import cycle
425
+ // must throw ERR_REQUIRE_CYCLE_MODULE when NOT hoisted (proves our understanding of
426
+ // the bug mechanism is correct, independent of the real chunk-y0jj307t.js file).
427
+ fs.writeFileSync(
428
+ path.join(tempDir, 'chunk-cycle-demo-target.js'),
429
+ 'import "/$bunfs/root/chunk-vmw9kxhv-a.js";\nexport const daemonColdStartGbDefault = () => "fixture";\n',
430
+ );
431
+ fs.writeFileSync(
432
+ path.join(tempDir, 'chunk-vmw9kxhv-a.js'),
433
+ 'var O9X=import.meta.require("/$bunfs/root/chunk-cycle-demo-target.js");\nexport const value = O9X;\n',
434
+ );
435
+ await assert.rejects(
436
+ () => import(pathToFileURL(path.join(tempDir, 'chunk-vmw9kxhv-a.js')).href),
437
+ (err) => {
438
+ assert.equal(err.code, 'ERR_REQUIRE_CYCLE_MODULE');
439
+ return true;
440
+ },
441
+ );
442
+
443
+ // Cycle fix proof: the real chunk-vmw9kxhv.js / chunk-y0jj307t.js pair (exact filenames
444
+ // and declaration text that tryHoistCycleBreakingImport() targets) must resolve cleanly
445
+ // once hoisting is applied, and the hoisted namespace's property access must work.
446
+ fs.writeFileSync(
447
+ path.join(tempDir, 'chunk-y0jj307t.js'),
448
+ 'import "/$bunfs/root/chunk-vmw9kxhv.js";\nexport const daemonColdStartGbDefault = () => "fixture";\n',
449
+ );
450
+ fs.writeFileSync(
451
+ path.join(tempDir, 'chunk-vmw9kxhv.js'),
452
+ 'var O9=import.meta.require("/$bunfs/root/chunk-y0jj307t.js");\nexport const value = O9.daemonColdStartGbDefault();\n',
453
+ );
454
+ const hoistedModule = await import(pathToFileURL(path.join(tempDir, 'chunk-vmw9kxhv.js')).href);
455
+ assert.equal(hoistedModule.value, 'fixture');
456
+
457
+ fs.writeFileSync(path.join(tempDir, 'doc.md'), '# Hello\nSome markdown text.\n');
458
+ fs.writeFileSync(
459
+ path.join(tempDir, 'md-caller.mjs'),
460
+ 'export const result = import.meta.require("/$bunfs/root/doc.md");\n',
461
+ );
462
+ const mdModule = await import(pathToFileURL(path.join(tempDir, 'md-caller.mjs')).href);
463
+ assert.equal(typeof mdModule.result, 'string');
464
+ assert.equal(mdModule.result, '# Hello\nSome markdown text.\n');
465
+
466
+ fs.writeFileSync(path.join(tempDir, 'note.txt'), 'plain text content');
467
+ fs.writeFileSync(
468
+ path.join(tempDir, 'txt-caller.mjs'),
469
+ 'export const result = import.meta.require("/$bunfs/root/note.txt");\n',
470
+ );
471
+ const txtModule = await import(pathToFileURL(path.join(tempDir, 'txt-caller.mjs')).href);
472
+ assert.equal(typeof txtModule.result, 'string');
473
+ assert.equal(txtModule.result, 'plain text content');
474
+
475
+ fs.writeFileSync(
476
+ path.join(tempDir, 'chunk-alias.js'),
477
+ 'export const ee = import.meta.require;\n',
478
+ );
479
+ fs.writeFileSync(
480
+ path.join(tempDir, 'alias-caller.mjs'),
481
+ 'import { ee } from "/$bunfs/root/chunk-alias.js";\nexport const result = ee("/$bunfs/root/doc.md");\n',
482
+ );
483
+ const aliasModule = await import(pathToFileURL(path.join(tempDir, 'alias-caller.mjs')).href);
484
+ assert.equal(aliasModule.result, '# Hello\nSome markdown text.\n');
485
+ } finally {
486
+ fs.rmSync(tempDir, { recursive: true, force: true });
487
+ }
488
+ });
@@ -0,0 +1,90 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const packageDir = path.resolve(__dirname, '..');
8
+
9
+ function verifyTarball(file, audited, version) {
10
+ const buf = fs.readFileSync(file);
11
+ if (audited.tarball_sha256) {
12
+ const sha256 = crypto.createHash('sha256').update(buf).digest('hex');
13
+ if (sha256 !== audited.tarball_sha256) {
14
+ throw new Error(`tarball sha256 mismatch for ${version}`);
15
+ }
16
+ }
17
+ if (audited.tarball_integrity) {
18
+ const sha512 = `sha512-${crypto.createHash('sha512').update(buf).digest('base64')}`;
19
+ if (sha512 !== audited.tarball_integrity) {
20
+ throw new Error(`tarball integrity mismatch for ${version}`);
21
+ }
22
+ }
23
+ if (audited.tarball_size !== undefined && Number(audited.tarball_size) !== buf.length) {
24
+ throw new Error(`tarball size mismatch for ${version}`);
25
+ }
26
+ }
27
+
28
+ function validateOffsets(file, audited, version) {
29
+ if (audited.entry_format === 'esm-chunked') {
30
+ validateEsmChunkedOffsets(file, audited, version);
31
+ return;
32
+ }
33
+ validateLegacyCjsOffsets(file, audited, version);
34
+ }
35
+
36
+ function validateLegacyCjsOffsets(file, audited, version) {
37
+ const buf = fs.readFileSync(file);
38
+ const start = Number(audited.entry_js_offset);
39
+ const end = Number(audited.entry_end_offset);
40
+ const startMarker = Buffer.from('function(exports, require, module, __filename, __dirname) {// Claude Code is a Beta product');
41
+ const endMarker = Buffer.from('/$bunfs/root/image-processor.js');
42
+
43
+ if (!(start > 0 && end > start && end <= buf.length)) {
44
+ throw new Error(`invalid audited offsets for ${version}`);
45
+ }
46
+ if (!buf.subarray(start, start + startMarker.length).equals(startMarker)) {
47
+ throw new Error(`audited start offset validation failed for ${version}`);
48
+ }
49
+ if (!buf.subarray(end, end + endMarker.length).equals(endMarker)) {
50
+ throw new Error(`audited end offset validation failed for ${version}`);
51
+ }
52
+ }
53
+
54
+ function validateEsmChunkedOffsets(file, audited, version) {
55
+ // 371MB超のバイナリ全体をreadFileSyncしない(実機でOOM確認済み)。
56
+ // discoverModuleGraphは範囲readSyncのみでトレイラー・モジュールテーブルを検証する。
57
+ const { discoverModuleGraph, readEntryContentPrefix } = require(path.join(packageDir, 'lib', 'bunfs-extract.js'));
58
+ const graph = discoverModuleGraph(file);
59
+ try {
60
+ if (!(graph.numModules > 0)) {
61
+ throw new Error(`esm-chunked module graph is empty for ${version}`);
62
+ }
63
+ if (graph.entryName !== '/$bunfs/root/cli') {
64
+ throw new Error(`esm-chunked entry module name mismatch for ${version}: ${graph.entryName}`);
65
+ }
66
+ if (!Object.prototype.hasOwnProperty.call(audited, 'cycle_hoists')) {
67
+ throw new Error(`esm-chunked audited metadata for ${version} is missing cycle_hoists field`);
68
+ }
69
+ if (audited.num_modules !== undefined && graph.numModules !== audited.num_modules) {
70
+ throw new Error(`esm-chunked num_modules mismatch for ${version}: expected ${audited.num_modules}, got ${graph.numModules}`);
71
+ }
72
+ if (audited.byte_count !== undefined && graph.byteCount !== audited.byte_count) {
73
+ throw new Error(`esm-chunked byte_count mismatch for ${version}: expected ${audited.byte_count}, got ${graph.byteCount}`);
74
+ }
75
+ const prefix = readEntryContentPrefix(graph.fd, graph.entryModule, 256).toString('utf8');
76
+ const codeStart = prefix.replace(/^(\s*\/\/[^\n]*\n)+/, '').replace(/^\(/, '');
77
+ if (codeStart.startsWith('function(exports, require, module, __filename, __dirname) {')) {
78
+ throw new Error(`entry module for ${version} is legacy-cjs wrapped, but audited entry_format is esm-chunked`);
79
+ }
80
+ } finally {
81
+ fs.closeSync(graph.fd);
82
+ }
83
+ }
84
+
85
+ module.exports = {
86
+ validateEsmChunkedOffsets,
87
+ validateLegacyCjsOffsets,
88
+ validateOffsets,
89
+ verifyTarball,
90
+ };
@@ -0,0 +1,160 @@
1
+ 'use strict';
2
+
3
+ const test = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+
9
+ const {
10
+ validateEsmChunkedOffsets,
11
+ } = require('./native-validators.js');
12
+
13
+ const {
14
+ discoverModuleGraph,
15
+ } = require('./bunfs-extract.js');
16
+
17
+ const TRAILER = '\n---- Bun! ----\n';
18
+
19
+ // StandaloneModuleGraphの最小合成バイナリを構築する。
20
+ // レイアウト: [preamble padding][module contents][module table][Offsets(32byte)][trailer]
21
+ function buildSyntheticBinary({ modules, entryPointId, corruptTrailer = false, preamblePadding = 64 }) {
22
+ const nameBuffers = modules.map((m) => Buffer.from(m.name, 'utf8'));
23
+ const contentBuffers = modules.map((m) => Buffer.from(m.content ?? '', 'utf8'));
24
+
25
+ const dataParts = [];
26
+ const nameOffsets = [];
27
+ const contOffsets = [];
28
+ let cursor = 0;
29
+ for (let i = 0; i < modules.length; i += 1) {
30
+ nameOffsets.push(cursor);
31
+ dataParts.push(nameBuffers[i]);
32
+ cursor += nameBuffers[i].length;
33
+ }
34
+ for (let i = 0; i < modules.length; i += 1) {
35
+ contOffsets.push(cursor);
36
+ dataParts.push(contentBuffers[i]);
37
+ cursor += contentBuffers[i].length;
38
+ }
39
+ const byteCountBeforeTable = cursor;
40
+
41
+ const MODULE_TABLE_ENTRY_SIZE = 52;
42
+ const modTable = Buffer.alloc(MODULE_TABLE_ENTRY_SIZE * modules.length);
43
+ for (let i = 0; i < modules.length; i += 1) {
44
+ const base = i * MODULE_TABLE_ENTRY_SIZE;
45
+ modTable.writeUInt32LE(nameOffsets[i], base);
46
+ modTable.writeUInt32LE(nameBuffers[i].length, base + 4);
47
+ modTable.writeUInt32LE(contOffsets[i], base + 8);
48
+ modTable.writeUInt32LE(contentBuffers[i].length, base + 12);
49
+ modTable[base + 49] = modules[i].loader ?? 1; // 1 = js
50
+ }
51
+ const modulesOffset = byteCountBeforeTable;
52
+ const modulesLength = modTable.length;
53
+ const byteCount = byteCountBeforeTable + modulesLength;
54
+
55
+ const offsetsBuf = Buffer.alloc(32);
56
+ offsetsBuf.writeBigUInt64LE(BigInt(byteCount), 0);
57
+ offsetsBuf.writeUInt32LE(modulesOffset, 8);
58
+ offsetsBuf.writeUInt32LE(modulesLength, 12);
59
+ offsetsBuf.writeUInt32LE(entryPointId, 16);
60
+
61
+ const trailerBuf = Buffer.from(corruptTrailer ? '\n---- NOT BUN ----\n' : TRAILER, 'utf8');
62
+
63
+ return Buffer.concat([
64
+ Buffer.alloc(preamblePadding),
65
+ ...dataParts,
66
+ modTable,
67
+ offsetsBuf,
68
+ trailerBuf,
69
+ ]);
70
+ }
71
+
72
+ function writeTempBinary(buf) {
73
+ const file = path.join(os.tmpdir(), `native-validators-test-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}.bin`);
74
+ fs.writeFileSync(file, buf);
75
+ return file;
76
+ }
77
+
78
+ test('validateEsmChunkedOffsets accepts matching num_modules and byte_count', () => {
79
+ const buf = buildSyntheticBinary({
80
+ modules: [
81
+ { name: '/$bunfs/root/cli', content: 'export default 1;' },
82
+ ],
83
+ entryPointId: 0,
84
+ });
85
+ const file = writeTempBinary(buf);
86
+ try {
87
+ const graph = discoverModuleGraph(file);
88
+ try {
89
+ const audited = {
90
+ num_modules: graph.numModules,
91
+ byte_count: graph.byteCount,
92
+ entry_format: 'esm-chunked',
93
+ cycle_hoists: [],
94
+ };
95
+ assert.doesNotThrow(() => validateEsmChunkedOffsets(file, audited, '9.9.9'));
96
+ } finally {
97
+ fs.closeSync(graph.fd);
98
+ }
99
+ } finally {
100
+ fs.rmSync(file, { force: true });
101
+ }
102
+ });
103
+
104
+ test('validateEsmChunkedOffsets rejects mismatched num_modules', () => {
105
+ const buf = buildSyntheticBinary({
106
+ modules: [
107
+ { name: '/$bunfs/root/cli', content: 'export default 1;' },
108
+ ],
109
+ entryPointId: 0,
110
+ });
111
+ const file = writeTempBinary(buf);
112
+ try {
113
+ const graph = discoverModuleGraph(file);
114
+ try {
115
+ const audited = {
116
+ num_modules: graph.numModules + 1, // intentionally wrong
117
+ byte_count: graph.byteCount,
118
+ entry_format: 'esm-chunked',
119
+ cycle_hoists: [],
120
+ };
121
+ assert.throws(
122
+ () => validateEsmChunkedOffsets(file, audited, '9.9.9'),
123
+ /num_modules mismatch/,
124
+ );
125
+ } finally {
126
+ fs.closeSync(graph.fd);
127
+ }
128
+ } finally {
129
+ fs.rmSync(file, { force: true });
130
+ }
131
+ });
132
+
133
+ test('validateEsmChunkedOffsets rejects mismatched byte_count', () => {
134
+ const buf = buildSyntheticBinary({
135
+ modules: [
136
+ { name: '/$bunfs/root/cli', content: 'export default 1;' },
137
+ ],
138
+ entryPointId: 0,
139
+ });
140
+ const file = writeTempBinary(buf);
141
+ try {
142
+ const graph = discoverModuleGraph(file);
143
+ try {
144
+ const audited = {
145
+ num_modules: graph.numModules,
146
+ byte_count: graph.byteCount + 1, // intentionally wrong
147
+ entry_format: 'esm-chunked',
148
+ cycle_hoists: [],
149
+ };
150
+ assert.throws(
151
+ () => validateEsmChunkedOffsets(file, audited, '9.9.9'),
152
+ /byte_count mismatch/,
153
+ );
154
+ } finally {
155
+ fs.closeSync(graph.fd);
156
+ }
157
+ } finally {
158
+ fs.rmSync(file, { force: true });
159
+ }
160
+ });
@@ -2,7 +2,6 @@
2
2
  'use strict';
3
3
 
4
4
  const cp = require('child_process');
5
- const crypto = require('crypto');
6
5
  const fs = require('fs');
7
6
  const os = require('os');
8
7
  const path = require('path');
@@ -16,6 +15,8 @@ const curlRetries = process.env.CLAUDE_TERMUX_FETCH_RETRIES || '4';
16
15
  const curlConnectTimeout = process.env.CLAUDE_TERMUX_FETCH_CONNECT_TIMEOUT || '20';
17
16
  const curlMaxTime = process.env.CLAUDE_TERMUX_FETCH_MAX_TIME || '300';
18
17
 
18
+ const { validateOffsets, verifyTarball } = require(path.join(packageDir, 'lib', 'native-validators.js'));
19
+
19
20
  if (!item) {
20
21
  console.error(`Unsupported audited Claude Code version: ${version}`);
21
22
  process.exit(1);
@@ -27,7 +28,7 @@ const nativeDest = path.join(versionDir, 'app', 'node_modules', '@anthropic-ai',
27
28
  const sourceBin = path.join(nativeDest, 'claude');
28
29
 
29
30
  if (fs.existsSync(sourceBin)) {
30
- validateOffsets(sourceBin, item);
31
+ validateOffsets(sourceBin, item, version);
31
32
  process.exit(0);
32
33
  }
33
34
 
@@ -36,7 +37,7 @@ const packDir = fs.mkdtempSync(path.join(os.tmpdir(), `claude-code-${version}-`)
36
37
 
37
38
  try {
38
39
  const tgzPath = fetchNativeTarball(item.native_spec, packDir);
39
- verifyTarball(tgzPath, item);
40
+ verifyTarball(tgzPath, item, version);
40
41
 
41
42
  const extractDir = path.join(packDir, 'native');
42
43
  fs.mkdirSync(extractDir, { recursive: true });
@@ -45,7 +46,7 @@ try {
45
46
  fs.rmSync(nativeDest, { recursive: true, force: true });
46
47
  fs.mkdirSync(path.dirname(nativeDest), { recursive: true });
47
48
  fs.cpSync(path.join(extractDir, 'package'), nativeDest, { recursive: true });
48
- validateOffsets(sourceBin, item);
49
+ validateOffsets(sourceBin, item, version);
49
50
  } finally {
50
51
  fs.rmSync(packDir, { recursive: true, force: true });
51
52
  }
@@ -107,70 +108,3 @@ function fetchNativeTarball(spec, packDir) {
107
108
  }
108
109
  return path.join(packDir, packEntry.filename);
109
110
  }
110
-
111
- function verifyTarball(file, audited) {
112
- const buf = fs.readFileSync(file);
113
- if (audited.tarball_sha256) {
114
- const sha256 = crypto.createHash('sha256').update(buf).digest('hex');
115
- if (sha256 !== audited.tarball_sha256) {
116
- throw new Error(`tarball sha256 mismatch for ${version}`);
117
- }
118
- }
119
- if (audited.tarball_integrity) {
120
- const sha512 = `sha512-${crypto.createHash('sha512').update(buf).digest('base64')}`;
121
- if (sha512 !== audited.tarball_integrity) {
122
- throw new Error(`tarball integrity mismatch for ${version}`);
123
- }
124
- }
125
- if (audited.tarball_size !== undefined && Number(audited.tarball_size) !== buf.length) {
126
- throw new Error(`tarball size mismatch for ${version}`);
127
- }
128
- }
129
-
130
- function validateOffsets(file, audited) {
131
- if (audited.entry_format === 'esm-chunked') {
132
- validateEsmChunkedOffsets(file, audited);
133
- return;
134
- }
135
- validateLegacyCjsOffsets(file, audited);
136
- }
137
-
138
- function validateLegacyCjsOffsets(file, audited) {
139
- const buf = fs.readFileSync(file);
140
- const start = Number(audited.entry_js_offset);
141
- const end = Number(audited.entry_end_offset);
142
- const startMarker = Buffer.from('function(exports, require, module, __filename, __dirname) {// Claude Code is a Beta product');
143
- const endMarker = Buffer.from('/$bunfs/root/image-processor.js');
144
-
145
- if (!(start > 0 && end > start && end <= buf.length)) {
146
- throw new Error(`invalid audited offsets for ${version}`);
147
- }
148
- if (!buf.subarray(start, start + startMarker.length).equals(startMarker)) {
149
- throw new Error(`audited start offset validation failed for ${version}`);
150
- }
151
- if (!buf.subarray(end, end + endMarker.length).equals(endMarker)) {
152
- throw new Error(`audited end offset validation failed for ${version}`);
153
- }
154
- }
155
-
156
- function validateEsmChunkedOffsets(file, audited) {
157
- // 371MB超のバイナリ全体をreadFileSyncしない(実機でOOM確認済み)。
158
- // discoverModuleGraphは範囲readSyncのみでトレイラー・モジュールテーブルを検証する。
159
- const { discoverModuleGraph, readEntryContentPrefix } = require(path.join(packageDir, 'lib', 'bunfs-extract.js'));
160
- const graph = discoverModuleGraph(file);
161
- try {
162
- if (!(graph.numModules > 0)) {
163
- throw new Error(`esm-chunked module graph is empty for ${version}`);
164
- }
165
- if (graph.entryName !== '/$bunfs/root/cli') {
166
- throw new Error(`esm-chunked entry module name mismatch for ${version}: ${graph.entryName}`);
167
- }
168
- const prefix = readEntryContentPrefix(graph.fd, graph.entryModule, 256).toString('utf8');
169
- const codeStart = prefix.replace(/^(\s*\/\/[^\n]*\n)+/, '').replace(/^\(/, '');
170
- if (codeStart.startsWith('function(exports, require, module, __filename, __dirname) {')) {
171
- throw new Error(`entry module for ${version} is legacy-cjs wrapped, but audited entry_format is esm-chunked`);
172
- }
173
- } finally {
174
- fs.closeSync(graph.fd);
175
- }
176
- }
@@ -665,7 +665,7 @@ function rewriteNativeChunkSource(source) {
665
665
 
666
666
  async function esmChunkedMain() {
667
667
  const { prepareProcessOwnedDir } = require(path.join(process.env.CLAUDE_TERMUX_PACKAGE_DIR, 'lib', 'bunfs-extract.js'));
668
- const { register } = require('node:module');
668
+ const { registerHooks } = require('node:module');
669
669
  const { pathToFileURL } = require('node:url');
670
670
 
671
671
  const { ownedDir, entryRelPath } = prepareProcessOwnedDir(sourceBin, workdir);
@@ -690,16 +690,27 @@ async function esmChunkedMain() {
690
690
  globalThis.__claudeBunShim = globalThis.Bun;
691
691
  globalThis.__claudeBun = globalThis.Bun;
692
692
 
693
- register(pathToFileURL(path.join(libDir, 'bunfs-esm-loader.mjs')).href, {
694
- parentURL: pathToFileURL(__filename).href,
695
- data: {
696
- processOwnedDir: ownedDir,
697
- sourceBin,
698
- childProcessGuardPath: path.join(libDir, 'bunfs-child-process-guard.mjs'),
699
- vmGuardPath: path.join(libDir, 'bunfs-vm-guard.mjs'),
700
- wsStubPath: path.join(libDir, 'bunfs-ws-stub.mjs'),
701
- },
693
+ let cycleHoists = [];
694
+ try {
695
+ const auditedVersions = require(path.join(process.env.CLAUDE_TERMUX_PACKAGE_DIR, 'config', 'claude-native-audited-versions.json'));
696
+ const entry = auditedVersions.versions?.[process.env.CURRENT_CLAUDE_VERSION];
697
+ if (entry && Array.isArray(entry.cycle_hoists)) {
698
+ cycleHoists = entry.cycle_hoists;
699
+ }
700
+ } catch {
701
+ // 読み込み失敗時は空配列のまま(fail-closed、既存の同期require経路にフォールバック)
702
+ }
703
+
704
+ const loaderMod = require(path.join(libDir, 'bunfs-esm-loader.mjs'));
705
+ loaderMod.initialize({
706
+ processOwnedDir: ownedDir,
707
+ sourceBin,
708
+ childProcessGuardPath: path.join(libDir, 'bunfs-child-process-guard.mjs'),
709
+ vmGuardPath: path.join(libDir, 'bunfs-vm-guard.mjs'),
710
+ wsStubPath: path.join(libDir, 'bunfs-ws-stub.mjs'),
711
+ cycleHoists,
702
712
  });
713
+ registerHooks({ resolve: loaderMod.resolve, load: loaderMod.load });
703
714
 
704
715
  const entryUrl = pathToFileURL(path.join(ownedDir, entryRelPath)).href;
705
716
 
@@ -1679,7 +1690,7 @@ function rewriteNativeChunkSource(source) {
1679
1690
 
1680
1691
  async function esmChunkedMain() {
1681
1692
  const { prepareProcessOwnedDir } = require(path.join(process.env.CLAUDE_TERMUX_PACKAGE_DIR, 'lib', 'bunfs-extract.js'));
1682
- const { register } = require('node:module');
1693
+ const { registerHooks } = require('node:module');
1683
1694
  const { pathToFileURL } = require('node:url');
1684
1695
 
1685
1696
  const { ownedDir, entryRelPath } = prepareProcessOwnedDir(sourceBin, workdir);
@@ -1704,16 +1715,27 @@ async function esmChunkedMain() {
1704
1715
  globalThis.__claudeBunShim = globalThis.Bun;
1705
1716
  globalThis.__claudeBun = globalThis.Bun;
1706
1717
 
1707
- register(pathToFileURL(path.join(libDir, 'bunfs-esm-loader.mjs')).href, {
1708
- parentURL: pathToFileURL(__filename).href,
1709
- data: {
1710
- processOwnedDir: ownedDir,
1711
- sourceBin,
1712
- childProcessGuardPath: path.join(libDir, 'bunfs-child-process-guard.mjs'),
1713
- vmGuardPath: path.join(libDir, 'bunfs-vm-guard.mjs'),
1714
- wsStubPath: path.join(libDir, 'bunfs-ws-stub.mjs'),
1715
- },
1718
+ let cycleHoists = [];
1719
+ try {
1720
+ const auditedVersions = require(path.join(process.env.CLAUDE_TERMUX_PACKAGE_DIR, 'config', 'claude-native-audited-versions.json'));
1721
+ const entry = auditedVersions.versions?.[process.env.CURRENT_CLAUDE_VERSION];
1722
+ if (entry && Array.isArray(entry.cycle_hoists)) {
1723
+ cycleHoists = entry.cycle_hoists;
1724
+ }
1725
+ } catch {
1726
+ // 読み込み失敗時は空配列のまま(fail-closed、既存の同期require経路にフォールバック)
1727
+ }
1728
+
1729
+ const loaderMod = require(path.join(libDir, 'bunfs-esm-loader.mjs'));
1730
+ loaderMod.initialize({
1731
+ processOwnedDir: ownedDir,
1732
+ sourceBin,
1733
+ childProcessGuardPath: path.join(libDir, 'bunfs-child-process-guard.mjs'),
1734
+ vmGuardPath: path.join(libDir, 'bunfs-vm-guard.mjs'),
1735
+ wsStubPath: path.join(libDir, 'bunfs-ws-stub.mjs'),
1736
+ cycleHoists,
1716
1737
  });
1738
+ registerHooks({ resolve: loaderMod.resolve, load: loaderMod.load });
1717
1739
 
1718
1740
  const entryUrl = pathToFileURL(path.join(ownedDir, entryRelPath)).href;
1719
1741
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bash0816/claude-code",
3
- "version": "2.1.245",
3
+ "version": "2.1.248",
4
4
  "description": "Unofficial Termux-native Claude Code wrapper with audited native replay",
5
5
  "license": "GPL-3.0-only",
6
6
  "bin": {
@@ -15,7 +15,7 @@
15
15
  "LICENSE"
16
16
  ],
17
17
  "engines": {
18
- "node": ">=20.6.0"
18
+ "node": ">=22.15.0 <23.0.0 || >=23.5.0"
19
19
  },
20
20
  "keywords": [
21
21
  "claude-code",