From e548271485bec042fa23ba555fdf729906c42d10 Mon Sep 17 00:00:00 2001 From: Vash0001 Date: Sun, 2 Aug 2026 17:39:30 +0900 Subject: [PATCH 1/4] fix: shim Bun.spawn/Bun.file to fail bg-pty-host cleanly (ENOENT) --- .../lib/termux-run-claude-native.sh | 92 ++++++++++++++- .../lib/termux-run-claude-native.test.js | 109 ++++++++++++++++++ 2 files changed, 200 insertions(+), 1 deletion(-) diff --git a/packages/claude-code/lib/termux-run-claude-native.sh b/packages/claude-code/lib/termux-run-claude-native.sh index 5a7996a..9e16bc5 100755 --- a/packages/claude-code/lib/termux-run-claude-native.sh +++ b/packages/claude-code/lib/termux-run-claude-native.sh @@ -795,9 +795,54 @@ async function main() { gte: (a, b) => _cmp(a, b) >= 0, lt: (a, b) => _cmp(a, b) < 0, lte: (a, b) => _cmp(a, b) <= 0, - }; + }; })(), YAML: globalThis.__claudeYaml, + spawn: (cmd, options) => { + const opts = options || {}; + const stdioArrayRaw = opts.stdio; + const cmdArray = Array.isArray(cmd) ? cmd : [cmd]; + if (cmdArray.some(a => a === '--bg-pty-host')) { + const err = new Error('ENOENT: --bg-pty-host is not supported by the Termux compatibility shim'); + err.code = 'ENOENT'; + err.errno = -2; + err.syscall = 'spawn'; + throw err; + } + const normalizeStdio = (v) => { + if (typeof v === 'number' && Number.isInteger(v) && v >= 0) return v; + return (v === 'ignore' || v === 'pipe' || v === 'inherit') ? v : 'pipe'; + }; + const stdioMapped = Array.isArray(stdioArrayRaw) + ? stdioArrayRaw.map(normalizeStdio) + : [normalizeStdio(opts.stdin), normalizeStdio(opts.stdout), normalizeStdio(opts.stderr)]; + const child = _realChild.spawn(cmdArray[0], cmdArray.slice(1), { + stdio: stdioMapped, + cwd: opts.cwd, + env: opts.env, + detached: !!opts.detached, + argv0: opts.argv0, + }); + const stdoutChunks = []; + if (child.stdout) child.stdout.on('data', d => stdoutChunks.push(d)); + let resolveExited; + const exited = new Promise(resolve => { resolveExited = resolve; }); + child.on('exit', (code, signal) => resolveExited(code !== null ? code : (signal ? 128 : 0))); + child.on('error', () => resolveExited(1)); + return { + pid: child.pid, + exited, + stdout: { text: async () => { await exited; return Buffer.concat(stdoutChunks).toString('utf8'); } }, + unref: () => { try { child.unref(); } catch {} }, + kill: (signal) => { try { child.kill(signal); } catch {} }, + }; + }, + file: (path) => { + const err = new Error(`ENOENT: Bun.file(${String(path)}) is not supported by the Termux compatibility shim`); + err.code = 'ENOENT'; + err.errno = -2; + throw err; + }, }; Object.assign(globalThis.__claudeBunShim, globalThis.Bun); if (typeof globalThis.__claudeBunShim.gc !== 'function') { @@ -1619,6 +1664,51 @@ async function main() { }; })(), YAML: globalThis.__claudeYaml, + spawn: (cmd, options) => { + const opts = options || {}; + const stdioArrayRaw = opts.stdio; + const cmdArray = Array.isArray(cmd) ? cmd : [cmd]; + if (cmdArray.some(a => a === '--bg-pty-host')) { + const err = new Error('ENOENT: --bg-pty-host is not supported by the Termux compatibility shim'); + err.code = 'ENOENT'; + err.errno = -2; + err.syscall = 'spawn'; + throw err; + } + const normalizeStdio = (v) => { + if (typeof v === 'number' && Number.isInteger(v) && v >= 0) return v; + return (v === 'ignore' || v === 'pipe' || v === 'inherit') ? v : 'pipe'; + }; + const stdioMapped = Array.isArray(stdioArrayRaw) + ? stdioArrayRaw.map(normalizeStdio) + : [normalizeStdio(opts.stdin), normalizeStdio(opts.stdout), normalizeStdio(opts.stderr)]; + const child = _realChild.spawn(cmdArray[0], cmdArray.slice(1), { + stdio: stdioMapped, + cwd: opts.cwd, + env: opts.env, + detached: !!opts.detached, + argv0: opts.argv0, + }); + const stdoutChunks = []; + if (child.stdout) child.stdout.on('data', d => stdoutChunks.push(d)); + let resolveExited; + const exited = new Promise(resolve => { resolveExited = resolve; }); + child.on('exit', (code, signal) => resolveExited(code !== null ? code : (signal ? 128 : 0))); + child.on('error', () => resolveExited(1)); + return { + pid: child.pid, + exited, + stdout: { text: async () => { await exited; return Buffer.concat(stdoutChunks).toString('utf8'); } }, + unref: () => { try { child.unref(); } catch {} }, + kill: (signal) => { try { child.kill(signal); } catch {} }, + }; + }, + file: (path) => { + const err = new Error(`ENOENT: Bun.file(${String(path)}) is not supported by the Termux compatibility shim`); + err.code = 'ENOENT'; + err.errno = -2; + throw err; + }, }; Object.assign(globalThis.__claudeBunShim, globalThis.Bun); if (typeof globalThis.__claudeBunShim.gc !== 'function') { diff --git a/packages/claude-code/lib/termux-run-claude-native.test.js b/packages/claude-code/lib/termux-run-claude-native.test.js index fbba7ce..4fb1f00 100644 --- a/packages/claude-code/lib/termux-run-claude-native.test.js +++ b/packages/claude-code/lib/termux-run-claude-native.test.js @@ -1,6 +1,7 @@ 'use strict'; const test = require('node:test'); +const { mock } = require('node:test'); const assert = require('node:assert/strict'); const crypto = require('crypto'); const child_process = require('child_process'); @@ -1338,3 +1339,111 @@ test('installStreamJsonTerminalWatcher waits for write callback before completin watcher.restore(); }); + +function extractShimSource(block) { + const startMarker = 'const _realChild = require(\'child_process\');'; + const endMarker = 'Object.assign(globalThis.__claudeBunShim, globalThis.Bun);'; + const start = block.indexOf(startMarker); + assert.notEqual(start, -1, 'missing Bun shim start'); + const end = block.indexOf(endMarker, start); + assert.notEqual(end, -1, 'missing Bun shim end'); + return block.slice(start, end + endMarker.length); +} + +function loadBunShim(source) { + const context = vm.createContext({ + stringWidth: () => 0, + wrapAnsi: value => value, + stripANSI: value => value, + stableHash: () => 0, + __claudeYaml: {}, + Buffer, + require, + }); + context.__claudeBunShim = {}; + context.__claudeYaml = {}; + context.Bun = {}; + context.module = { exports: {} }; + vm.runInContext(`${source}\nmodule.exports = globalThis.__claudeBunShim;`, context); + return context.module.exports; +} + +test('helper and bootstrap Bun shim source is identical', () => { + const helperBlock = extractBlock('cat <<\'NODE\' > "$_helper"', '\n export ENABLE_CLAUDEAI_MCP_SERVERS='); + const bootstrapBlock = extractBlock('cat <<\'NODE\' > "$_bootstrap"', '\n export ENABLE_CLAUDEAI_MCP_SERVERS='); + assert.equal(extractShimSource(helperBlock), extractShimSource(bootstrapBlock)); +}); + +test('Bun.file always throws ENOENT', () => { + for (const blockMarker of ['cat <<\'NODE\' > "$_helper"', 'cat <<\'NODE\' > "$_bootstrap"']) { + const block = extractBlock(blockMarker, '\n export ENABLE_CLAUDEAI_MCP_SERVERS='); + const Bun = loadBunShim(extractShimSource(block)); + assert.throws(() => Bun.file('/some/path'), error => + error.code === 'ENOENT' && error.errno === -2); + } +}); + +test('Bun.spawn rejects bg-pty-host with spawn ENOENT', () => { + for (const blockMarker of ['cat <<\'NODE\' > "$_helper"', 'cat <<\'NODE\' > "$_bootstrap"']) { + const block = extractBlock(blockMarker, '\n export ENABLE_CLAUDEAI_MCP_SERVERS='); + const Bun = loadBunShim(extractShimSource(block)); + assert.throws( + () => Bun.spawn(['claude', '--bg-pty-host', 'sock', '80', '24'], {}), + error => error.code === 'ENOENT' && error.errno === -2 && error.syscall === 'spawn', + ); + } +}); + +test('Bun.spawn supports top-level and array stdio forms', async () => { + const block = extractBlock('cat <<\'NODE\' > "$_helper"', '\n export ENABLE_CLAUDEAI_MCP_SERVERS='); + const Bun = loadBunShim(extractShimSource(block)); + const topLevel = Bun.spawn(['echo', 'hello'], { stdout: 'pipe', stderr: 'ignore' }); + assert.equal(await topLevel.stdout.text(), 'hello\n'); + assert.equal(await topLevel.exited, 0); + const arrayForm = Bun.spawn(['echo', 'hello'], { stdio: ['ignore', 'pipe', 'ignore'] }); + assert.equal(await arrayForm.stdout.text(), 'hello\n'); + assert.equal(await arrayForm.exited, 0); +}); + +test('Bun.spawn forwards options, preserves numeric fds, and delegates child controls', () => { + const block = extractBlock('cat <<\'NODE\' > "$_helper"', '\n export ENABLE_CLAUDEAI_MCP_SERVERS='); + const calls = []; + const handlers = {}; + const child = { + pid: 123, + stdout: null, + on(event, handler) { handlers[event] = handler; return this; }, + unref() { calls.push(['unref']); }, + kill(signal) { calls.push(['kill', signal]); }, + }; + mock.method(child_process, 'spawn', (...args) => { + calls.push(args); + return child; + }); + try { + const Bun = loadBunShim(extractShimSource(block)); + const result = Bun.spawn(['cmd', 'arg'], { + detached: true, + argv0: 'argv0-value', + cwd: '/tmp/work', + env: { TEST: 'yes' }, + stdio: [0, 1, 2], + }); + assert.deepEqual(JSON.parse(JSON.stringify(calls[0])), [ + 'cmd', + ['arg'], + { + stdio: [0, 1, 2], + cwd: '/tmp/work', + env: { TEST: 'yes' }, + detached: true, + argv0: 'argv0-value', + }, + ]); + result.unref(); + result.kill('SIGTERM'); + assert.deepEqual(JSON.parse(JSON.stringify(calls.slice(1))), [['unref'], ['kill', 'SIGTERM']]); + } finally { + mock.restoreAll(); + } +}); From 7ee98ac84254e8ebb45238e9741d7276d05d4600 Mon Sep 17 00:00:00 2001 From: Vash0001 Date: Sun, 2 Aug 2026 17:46:54 +0900 Subject: [PATCH 2/4] test: cover stdio array vs top-level priority and stdin passthrough --- .../lib/termux-run-claude-native.test.js | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/packages/claude-code/lib/termux-run-claude-native.test.js b/packages/claude-code/lib/termux-run-claude-native.test.js index 4fb1f00..7d10b50 100644 --- a/packages/claude-code/lib/termux-run-claude-native.test.js +++ b/packages/claude-code/lib/termux-run-claude-native.test.js @@ -1447,3 +1447,48 @@ test('Bun.spawn forwards options, preserves numeric fds, and delegates child con mock.restoreAll(); } }); + +test('Bun.spawn prefers stdio array over top-level stdio options', () => { + for (const blockMarker of ['cat <<\'NODE\' > "$_helper"', 'cat <<\'NODE\' > "$_bootstrap"']) { + const block = extractBlock(blockMarker, '\n export ENABLE_CLAUDEAI_MCP_SERVERS='); + const calls = []; + mock.method(child_process, 'spawn', (...args) => { + calls.push(args); + return { pid: 123, stdout: null, on() { return this; } }; + }); + try { + const Bun = loadBunShim(extractShimSource(block)); + Bun.spawn(['cmd'], { + stdio: [0, 1, 2], + stdin: 'pipe', + stdout: 'pipe', + stderr: 'pipe', + }); + assert.deepEqual(JSON.parse(JSON.stringify(calls[0][2])), { + stdio: [0, 1, 2], + detached: false, + }); + } finally { + mock.restoreAll(); + } + } +}); + +test('Bun.spawn forwards top-level stdin in the first stdio position', () => { + for (const blockMarker of ['cat <<\'NODE\' > "$_helper"', 'cat <<\'NODE\' > "$_bootstrap"']) { + const block = extractBlock(blockMarker, '\n export ENABLE_CLAUDEAI_MCP_SERVERS='); + const calls = []; + mock.method(child_process, 'spawn', (...args) => { + calls.push(args); + return { pid: 123, stdout: null, on() { return this; } }; + }); + try { + const Bun = loadBunShim(extractShimSource(block)); + Bun.spawn(['cmd'], { stdin: 'inherit', stdout: 'pipe', stderr: 'ignore' }); + assert.equal(calls[0][2].stdio[0], 'inherit'); + assert.deepEqual(JSON.parse(JSON.stringify(calls[0][2].stdio)), ['inherit', 'pipe', 'ignore']); + } finally { + mock.restoreAll(); + } + } +}); From da607b05ea730bf66bc3733d0b24a103ba4334fb Mon Sep 17 00:00:00 2001 From: Vash0001 Date: Wed, 5 Aug 2026 07:32:39 +0900 Subject: [PATCH 3/4] fix: disable bg-pty-host factory via native chunk source rewrite --- .../lib/termux-run-claude-native.sh | 28 ++++----- .../lib/termux-run-claude-native.test.js | 63 ++++++++++++------- 2 files changed, 54 insertions(+), 37 deletions(-) diff --git a/packages/claude-code/lib/termux-run-claude-native.sh b/packages/claude-code/lib/termux-run-claude-native.sh index 9e16bc5..60af4cf 100755 --- a/packages/claude-code/lib/termux-run-claude-native.sh +++ b/packages/claude-code/lib/termux-run-claude-native.sh @@ -627,6 +627,13 @@ function rewriteNativeChunkSource(source) { 'npmInstallDeprecated flag', _npmInstallDeprecatedExpected, ); + patched = replaceRequired( + patched, + /function ([A-Za-z_$][\w$]*)\(\)\{(return\([A-Za-z_$][\w$]*,[A-Za-z_$][\w$]*,[A-Za-z_$][\w$]*\)=>\{let\{cmd:([A-Za-z_$][\w$]*),prefixArgs:([A-Za-z_$][\w$]*)\}=[A-Za-z_$][\w$]*\(\{pinToCurrentBinary:!0\}\),[A-Za-z_$][\w$]*=\[\3,\.\.\.\4,"--bg-pty-host")/g, + 'function $1(){return undefined;$2', + 'bg-pty-host factory disable', + 1, + ); return patched; } @@ -802,13 +809,6 @@ async function main() { const opts = options || {}; const stdioArrayRaw = opts.stdio; const cmdArray = Array.isArray(cmd) ? cmd : [cmd]; - if (cmdArray.some(a => a === '--bg-pty-host')) { - const err = new Error('ENOENT: --bg-pty-host is not supported by the Termux compatibility shim'); - err.code = 'ENOENT'; - err.errno = -2; - err.syscall = 'spawn'; - throw err; - } const normalizeStdio = (v) => { if (typeof v === 'number' && Number.isInteger(v) && v >= 0) return v; return (v === 'ignore' || v === 'pipe' || v === 'inherit') ? v : 'pipe'; @@ -1492,6 +1492,13 @@ function rewriteNativeChunkSource(source) { 'npmInstallDeprecated flag', _npmInstallDeprecatedExpected, ); + patched = replaceRequired( + patched, + /function ([A-Za-z_$][\w$]*)\(\)\{(return\([A-Za-z_$][\w$]*,[A-Za-z_$][\w$]*,[A-Za-z_$][\w$]*\)=>\{let\{cmd:([A-Za-z_$][\w$]*),prefixArgs:([A-Za-z_$][\w$]*)\}=[A-Za-z_$][\w$]*\(\{pinToCurrentBinary:!0\}\),[A-Za-z_$][\w$]*=\[\3,\.\.\.\4,"--bg-pty-host")/g, + 'function $1(){return undefined;$2', + 'bg-pty-host factory disable', + 1, + ); return patched; } @@ -1668,13 +1675,6 @@ async function main() { const opts = options || {}; const stdioArrayRaw = opts.stdio; const cmdArray = Array.isArray(cmd) ? cmd : [cmd]; - if (cmdArray.some(a => a === '--bg-pty-host')) { - const err = new Error('ENOENT: --bg-pty-host is not supported by the Termux compatibility shim'); - err.code = 'ENOENT'; - err.errno = -2; - err.syscall = 'spawn'; - throw err; - } const normalizeStdio = (v) => { if (typeof v === 'number' && Number.isInteger(v) && v >= 0) return v; return (v === 'ignore' || v === 'pipe' || v === 'inherit') ? v : 'pipe'; diff --git a/packages/claude-code/lib/termux-run-claude-native.test.js b/packages/claude-code/lib/termux-run-claude-native.test.js index 7d10b50..960187c 100644 --- a/packages/claude-code/lib/termux-run-claude-native.test.js +++ b/packages/claude-code/lib/termux-run-claude-native.test.js @@ -301,9 +301,36 @@ test('cleanupStaleEntryFiles removes only stale extracted files for the same off function buildSyntheticBundleSource() { const typeofBun = Array.from({ length: 6 }, () => 'typeof Bun').join('; '); const bunProps = Array.from({ length: 37 }, (_, index) => `Bun.p${index}`).join('; '); - return `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0 }`; + return `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; function dYs(){return(e,t,r)=>{let{cmd:n,prefixArgs:o}=ox({pinToCurrentBinary:!0}),i=[n,...o,"--bg-pty-host",r.ptySock];return i}} }`; } +test('rewriteNativeChunkSource disables the bg-pty-host factory', () => { + process.env.CURRENT_CLAUDE_VERSION = '2.1.198'; + try { + const { rewriteNativeChunkSource } = loadHelperApi(); + const patched = rewriteNativeChunkSource(buildSyntheticBundleSource()); + const start = patched.indexOf('function dYs(){'); + assert.notEqual(start, -1); + let depth = 0; + let end = -1; + for (let index = start; index < patched.length; index += 1) { + if (patched[index] === '{') depth += 1; + if (patched[index] === '}') { + depth -= 1; + if (depth === 0) { + end = index + 1; + break; + } + } + } + assert.notEqual(end, -1); + const rewrittenFactory = eval('(' + patched.slice(start, end) + ')'); + assert.equal(rewrittenFactory(), undefined); + } finally { + delete process.env.CURRENT_CLAUDE_VERSION; + } +}); + test('rewriteNativeChunkSource rewrites the synthetic bundle slice (version 2.1.198)', () => { process.env.CURRENT_CLAUDE_VERSION = '2.1.198'; try { @@ -329,7 +356,7 @@ test('rewriteNativeChunkSource rewrites the synthetic bundle slice (version 2.1. const { rewriteNativeChunkSource } = loadHelperApi(); const typeofBun = Array.from({ length: 7 }, () => 'typeof Bun').join('; '); const bunProps = Array.from({ length: 40 }, (_, index) => `Bun.p${index}`).join('; '); - const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0 }`; + const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; function dYs(){return(e,t,r)=>{let{cmd:n,prefixArgs:o}=ox({pinToCurrentBinary:!0}),i=[n,...o,"--bg-pty-host",r.ptySock];return i}} }`; const patched = rewriteNativeChunkSource(source); assert.match(patched, /var __claudeBun = globalThis\.__claudeBunShim;/); @@ -350,7 +377,7 @@ test('rewriteNativeChunkSource rewrites the synthetic bundle slice (version 2.1. const { rewriteNativeChunkSource } = loadHelperApi(); const typeofBun = Array.from({ length: 7 }, () => 'typeof Bun').join('; '); const bunProps = Array.from({ length: 41 }, (_, index) => `Bun.p${index}`).join('; '); - const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0 }`; + const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; function dYs(){return(e,t,r)=>{let{cmd:n,prefixArgs:o}=ox({pinToCurrentBinary:!0}),i=[n,...o,"--bg-pty-host",r.ptySock];return i}} }`; const patched = rewriteNativeChunkSource(source); assert.match(patched, /var __claudeBun = globalThis\.__claudeBunShim;/); @@ -371,7 +398,7 @@ test('rewriteNativeChunkSource rewrites the synthetic bundle slice (version 2.1. const { rewriteNativeChunkSource } = loadHelperApi(); const typeofBun = Array.from({ length: 7 }, () => 'typeof Bun').join('; '); const bunProps = Array.from({ length: 41 }, (_, index) => `Bun.p${index}`).join('; '); - const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; npmInstallDeprecated:!0 }`; + const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; npmInstallDeprecated:!0; function dYs(){return(e,t,r)=>{let{cmd:n,prefixArgs:o}=ox({pinToCurrentBinary:!0}),i=[n,...o,"--bg-pty-host",r.ptySock];return i}} }`; const patched = rewriteNativeChunkSource(source); assert.match(patched, /var __claudeBun = globalThis\.__claudeBunShim;/); @@ -392,7 +419,7 @@ test('rewriteNativeChunkSource rewrites the synthetic bundle slice (version 2.1. const { rewriteNativeChunkSource } = loadHelperApi(); const typeofBun = Array.from({ length: 7 }, () => 'typeof Bun').join('; '); const bunProps = Array.from({ length: 38 }, (_, index) => `Bun.p${index}`).join('; '); - const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; npmInstallDeprecated:!0 }`; + const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; npmInstallDeprecated:!0; function dYs(){return(e,t,r)=>{let{cmd:n,prefixArgs:o}=ox({pinToCurrentBinary:!0}),i=[n,...o,"--bg-pty-host",r.ptySock];return i}} }`; const patched = rewriteNativeChunkSource(source); assert.match(patched, /var __claudeBun = globalThis\.__claudeBunShim;/); @@ -413,7 +440,7 @@ test('rewriteNativeChunkSource rejects stale Bun property access count for versi const { rewriteNativeChunkSource } = loadHelperApi(); const typeofBun = Array.from({ length: 7 }, () => 'typeof Bun').join('; '); const bunProps = Array.from({ length: 41 }, (_, index) => `Bun.p${index}`).join('; '); - const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; npmInstallDeprecated:!0 }`; + const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; npmInstallDeprecated:!0; function dYs(){return(e,t,r)=>{let{cmd:n,prefixArgs:o}=ox({pinToCurrentBinary:!0}),i=[n,...o,"--bg-pty-host",r.ptySock];return i}} }`; assert.throws( () => rewriteNativeChunkSource(source), @@ -430,7 +457,7 @@ test('rewriteNativeChunkSource rewrites the synthetic bundle slice (version 2.1. const { rewriteNativeChunkSource } = loadHelperApi(); const typeofBun = Array.from({ length: 7 }, () => 'typeof Bun').join('; '); const bunProps = Array.from({ length: 39 }, (_, index) => `Bun.p${index}`).join('; '); - const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; npmInstallDeprecated:!0 }`; + const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; npmInstallDeprecated:!0; function dYs(){return(e,t,r)=>{let{cmd:n,prefixArgs:o}=ox({pinToCurrentBinary:!0}),i=[n,...o,"--bg-pty-host",r.ptySock];return i}} }`; const patched = rewriteNativeChunkSource(source); assert.match(patched, /var __claudeBun = globalThis\.__claudeBunShim;/); @@ -451,7 +478,7 @@ test('rewriteNativeChunkSource rejects stale Bun property access count for versi const { rewriteNativeChunkSource } = loadHelperApi(); const typeofBun = Array.from({ length: 7 }, () => 'typeof Bun').join('; '); const bunProps = Array.from({ length: 38 }, (_, index) => `Bun.p${index}`).join('; '); - const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; npmInstallDeprecated:!0 }`; + const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; npmInstallDeprecated:!0; function dYs(){return(e,t,r)=>{let{cmd:n,prefixArgs:o}=ox({pinToCurrentBinary:!0}),i=[n,...o,"--bg-pty-host",r.ptySock];return i}} }`; assert.throws( () => rewriteNativeChunkSource(source), @@ -468,7 +495,7 @@ test('rewriteNativeChunkSource rewrites the synthetic bundle slice (version 2.1. const { rewriteNativeChunkSource } = loadHelperApi(); const typeofBun = Array.from({ length: 7 }, () => 'typeof Bun').join('; '); const bunProps = Array.from({ length: 40 }, (_, index) => `Bun.p${index}`).join('; '); - const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; npmInstallDeprecated:!0 }`; + const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; npmInstallDeprecated:!0; function dYs(){return(e,t,r)=>{let{cmd:n,prefixArgs:o}=ox({pinToCurrentBinary:!0}),i=[n,...o,"--bg-pty-host",r.ptySock];return i}} }`; const patched = rewriteNativeChunkSource(source); assert.match(patched, /var __claudeBun = globalThis\.__claudeBunShim;/); @@ -488,7 +515,7 @@ test('rewriteNativeChunkSource rejects stale Bun property access count for versi const { rewriteNativeChunkSource } = loadHelperApi(); const typeofBun = Array.from({ length: 7 }, () => 'typeof Bun').join('; '); const bunProps = Array.from({ length: 39 }, (_, index) => `Bun.p${index}`).join('; '); - const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; npmInstallDeprecated:!0 }`; + const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; npmInstallDeprecated:!0; function dYs(){return(e,t,r)=>{let{cmd:n,prefixArgs:o}=ox({pinToCurrentBinary:!0}),i=[n,...o,"--bg-pty-host",r.ptySock];return i}} }`; assert.throws( () => rewriteNativeChunkSource(source), @@ -505,7 +532,7 @@ test('rewriteNativeChunkSource rewrites the synthetic bundle slice (version 2.1. const { rewriteNativeChunkSource } = loadHelperApi(); const typeofBun = Array.from({ length: 7 }, () => 'typeof Bun').join('; '); const bunProps = Array.from({ length: 42 }, (_, index) => `Bun.p${index}`).join('; '); - const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; npmInstallDeprecated:!0 }`; + const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; npmInstallDeprecated:!0; function dYs(){return(e,t,r)=>{let{cmd:n,prefixArgs:o}=ox({pinToCurrentBinary:!0}),i=[n,...o,"--bg-pty-host",r.ptySock];return i}} }`; const patched = rewriteNativeChunkSource(source); assert.match(patched, /var __claudeBun = globalThis\.__claudeBunShim;/); @@ -525,7 +552,7 @@ test('rewriteNativeChunkSource rejects stale Bun property access count for versi const { rewriteNativeChunkSource } = loadHelperApi(); const typeofBun = Array.from({ length: 7 }, () => 'typeof Bun').join('; '); const bunProps = Array.from({ length: 41 }, (_, index) => `Bun.p${index}`).join('; '); - const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; npmInstallDeprecated:!0 }`; + const source = `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; npmInstallDeprecated:!0; function dYs(){return(e,t,r)=>{let{cmd:n,prefixArgs:o}=ox({pinToCurrentBinary:!0}),i=[n,...o,"--bg-pty-host",r.ptySock];return i}} }`; assert.throws( () => rewriteNativeChunkSource(source), @@ -572,6 +599,7 @@ function buildScenarioFixtureSource() { return `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0; npmInstallDeprecated:!0; + function dYs(){return(e,t,r)=>{let{cmd:n,prefixArgs:o}=ox({pinToCurrentBinary:!0}),i=[n,...o,"--bg-pty-host",r.ptySock];return i}} const scenario = process.env.TEST_SCENARIO; if (scenario === 'sync-exit') { process.stdout.write('ok'); process.exit(0); return; } if (scenario === 'async-exit') { @@ -1383,17 +1411,6 @@ test('Bun.file always throws ENOENT', () => { } }); -test('Bun.spawn rejects bg-pty-host with spawn ENOENT', () => { - for (const blockMarker of ['cat <<\'NODE\' > "$_helper"', 'cat <<\'NODE\' > "$_bootstrap"']) { - const block = extractBlock(blockMarker, '\n export ENABLE_CLAUDEAI_MCP_SERVERS='); - const Bun = loadBunShim(extractShimSource(block)); - assert.throws( - () => Bun.spawn(['claude', '--bg-pty-host', 'sock', '80', '24'], {}), - error => error.code === 'ENOENT' && error.errno === -2 && error.syscall === 'spawn', - ); - } -}); - test('Bun.spawn supports top-level and array stdio forms', async () => { const block = extractBlock('cat <<\'NODE\' > "$_helper"', '\n export ENABLE_CLAUDEAI_MCP_SERVERS='); const Bun = loadBunShim(extractShimSource(block)); From 25ef6fb75bfbe8ee983db0004fdcfdd366a3e3ea Mon Sep 17 00:00:00 2001 From: Vash0001 Date: Wed, 5 Aug 2026 19:56:21 +0900 Subject: [PATCH 4/4] chore: retarget bg-pty-host fix from 2.1.220-3 to 2.1.222-1 Upstream released 2.1.222 while the 220-3 fix (bg-pty-host factory disable via native chunk source rewrite) was still awaiting G4. Rather than chase the intermediate 2.1.221, rebase the fix onto the 2.1.222 intake and republish the wrapper as 2.1.222-1. Verified the existing regex-based rewrite still matches exactly once against the real 2.1.222 native bundle (offsets 256895476/279849039) and that the patched factory function returns undefined, using the production rewriteNativeChunkSource() from both helper and bootstrap branches. --- config/claude-native-audited-versions.json | 9 +++++++++ config/claude-termux-release-manifest.json | 2 +- .../config/claude-native-audited-versions.json | 9 +++++++++ .../config/claude-termux-release-manifest.json | 2 +- packages/claude-code/package.json | 2 +- 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/config/claude-native-audited-versions.json b/config/claude-native-audited-versions.json index 30d7b00..7000e01 100644 --- a/config/claude-native-audited-versions.json +++ b/config/claude-native-audited-versions.json @@ -617,6 +617,15 @@ "tarball_integrity": "sha512-EXSediF1ujcqQeElbXdCEe+uW3NUQAOjn1xHHAgvwzY08nzJeePjagn9+YS6bmhHxBgDKwlGu7pL8Kv7EpiFtg==", "tarball_sha256": "1bb3c8364d652dd08a15856ee27c6600ecd1e45360243154fa846f240ce7a1df", "status": "termux_verified" + }, + "2.1.222-1": { + "wrapper_spec": "@anthropic-ai/claude-code@2.1.222", + "native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.222", + "entry_js_offset": 256895476, + "entry_end_offset": 279849039, + "tarball_integrity": "sha512-EXSediF1ujcqQeElbXdCEe+uW3NUQAOjn1xHHAgvwzY08nzJeePjagn9+YS6bmhHxBgDKwlGu7pL8Kv7EpiFtg==", + "tarball_sha256": "1bb3c8364d652dd08a15856ee27c6600ecd1e45360243154fa846f240ce7a1df", + "status": "offset_discovered" } } } diff --git a/config/claude-termux-release-manifest.json b/config/claude-termux-release-manifest.json index af43c35..1d8f045 100644 --- a/config/claude-termux-release-manifest.json +++ b/config/claude-termux-release-manifest.json @@ -2,7 +2,7 @@ "manifest_version": 1, "package_name": "@bash0816/claude-code", "latest_audited_version": "2.1.222", - "latest_candidate_version": "2.1.222", + "latest_candidate_version": "2.1.222-1", "previous_stable_version": "2.1.220-2", "stable_pinned_version": "2.1.193", "manifest_url": "https://raw.githubusercontent.com/bash0816/ClaudeCode-Termux/main/config/claude-termux-release-manifest.json" diff --git a/packages/claude-code/config/claude-native-audited-versions.json b/packages/claude-code/config/claude-native-audited-versions.json index 30d7b00..7000e01 100644 --- a/packages/claude-code/config/claude-native-audited-versions.json +++ b/packages/claude-code/config/claude-native-audited-versions.json @@ -617,6 +617,15 @@ "tarball_integrity": "sha512-EXSediF1ujcqQeElbXdCEe+uW3NUQAOjn1xHHAgvwzY08nzJeePjagn9+YS6bmhHxBgDKwlGu7pL8Kv7EpiFtg==", "tarball_sha256": "1bb3c8364d652dd08a15856ee27c6600ecd1e45360243154fa846f240ce7a1df", "status": "termux_verified" + }, + "2.1.222-1": { + "wrapper_spec": "@anthropic-ai/claude-code@2.1.222", + "native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.222", + "entry_js_offset": 256895476, + "entry_end_offset": 279849039, + "tarball_integrity": "sha512-EXSediF1ujcqQeElbXdCEe+uW3NUQAOjn1xHHAgvwzY08nzJeePjagn9+YS6bmhHxBgDKwlGu7pL8Kv7EpiFtg==", + "tarball_sha256": "1bb3c8364d652dd08a15856ee27c6600ecd1e45360243154fa846f240ce7a1df", + "status": "offset_discovered" } } } diff --git a/packages/claude-code/config/claude-termux-release-manifest.json b/packages/claude-code/config/claude-termux-release-manifest.json index af43c35..1d8f045 100644 --- a/packages/claude-code/config/claude-termux-release-manifest.json +++ b/packages/claude-code/config/claude-termux-release-manifest.json @@ -2,7 +2,7 @@ "manifest_version": 1, "package_name": "@bash0816/claude-code", "latest_audited_version": "2.1.222", - "latest_candidate_version": "2.1.222", + "latest_candidate_version": "2.1.222-1", "previous_stable_version": "2.1.220-2", "stable_pinned_version": "2.1.193", "manifest_url": "https://raw.githubusercontent.com/bash0816/ClaudeCode-Termux/main/config/claude-termux-release-manifest.json" diff --git a/packages/claude-code/package.json b/packages/claude-code/package.json index f68745f..4da1f85 100644 --- a/packages/claude-code/package.json +++ b/packages/claude-code/package.json @@ -1,6 +1,6 @@ { "name": "@bash0816/claude-code", - "version": "2.1.222", + "version": "2.1.222-1", "description": "Unofficial Termux-native Claude Code wrapper with audited native replay", "license": "GPL-3.0-only", "bin": {