diff --git a/lib/parallel-execution.js b/lib/parallel-execution.js index f4bc0fc..69c443c 100644 --- a/lib/parallel-execution.js +++ b/lib/parallel-execution.js @@ -18,19 +18,39 @@ import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); +/** + * Check if file is a native binary (Mach-O, ELF, etc.) vs JavaScript + * @param {string} filePath - Path to file + * @returns {boolean} true if native binary + */ +function isNativeBinary(filePath) { + try { + const magic = Buffer.alloc(4); + const fd = fs.openSync(filePath, 'r'); + fs.readSync(fd, magic, 0, 4, 0); + fs.closeSync(fd); + const hex = magic.toString('hex'); + return hex === 'cffaedfe' || hex === 'cafebabe' || hex === '7f454c46'; + } catch { + return false; + } +} + /** * Detect if TeammateTool is available in current Claude Code installation * Auto-enables if possible */ export function detectTeammateTool() { try { - // Find Claude Code installation const claudePath = findClaudeCodeBinary(); if (!claudePath) { return { available: false, reason: 'Claude Code binary not found' }; } - // Read CLI content + if (isNativeBinary(claudePath)) { + return { available: false, reason: 'Native binary detected - patching not supported', isNative: true }; + } + const cliContent = fs.readFileSync(claudePath, 'utf8'); // Check for TeammateTool presence @@ -130,6 +150,10 @@ export function enableTeammateTool() { throw new Error('Claude Code binary not found'); } + if (isNativeBinary(claudePath)) { + return { success: false, error: 'Native binary detected - patching not supported. TeammateTool patching only works with npm-installed Claude Code.' }; + } + // Backup original const backupPath = `${claudePath}.backup-${Date.now()}`; fs.copyFileSync(claudePath, backupPath); diff --git a/test/unit/parallel-execution.test.js b/test/unit/parallel-execution.test.js index 667e696..7838779 100644 --- a/test/unit/parallel-execution.test.js +++ b/test/unit/parallel-execution.test.js @@ -39,6 +39,36 @@ describe('Parallel Execution', () => { expect(result.reason).toContain('binary not found'); }); + it('should detect native Mach-O binary and skip patching', () => { + execSync.mockImplementation((cmd) => { + if (cmd.includes('npm root -g')) { + return '/usr/local/lib/node_modules\n'; + } + if (cmd.includes('which') || cmd.includes('where')) { + return '/usr/local/bin/claude\n'; + } + throw new Error('Unexpected command'); + }); + + fs.existsSync.mockReturnValue(true); + fs.realpathSync.mockImplementation((p) => p); + fs.openSync.mockReturnValue(42); + fs.readSync.mockImplementation((fd, buffer) => { + buffer[0] = 0xcf; + buffer[1] = 0xfa; + buffer[2] = 0xed; + buffer[3] = 0xfe; + return 4; + }); + fs.closeSync.mockReturnValue(undefined); + + const result = detectTeammateTool(); + + expect(result.available).toBe(false); + expect(result.reason).toContain('Native binary'); + expect(result.isNative).toBe(true); + }); + it('should detect when TeammateTool code is not present (old version)', () => { // Mock execSync completely - no real calls execSync.mockImplementation((cmd) => { @@ -127,6 +157,35 @@ describe('Parallel Execution', () => { }); describe('enableTeammateTool', () => { + it('should reject native Mach-O binary', () => { + execSync.mockImplementation((cmd) => { + if (cmd.includes('npm root -g')) { + return '/usr/local/lib/node_modules\n'; + } + if (cmd.includes('which') || cmd.includes('where')) { + return '/usr/local/bin/claude\n'; + } + throw new Error('Unexpected command'); + }); + + fs.existsSync.mockReturnValue(true); + fs.realpathSync.mockImplementation((p) => p); + fs.openSync.mockReturnValue(42); + fs.readSync.mockImplementation((fd, buffer) => { + buffer[0] = 0xcf; + buffer[1] = 0xfa; + buffer[2] = 0xed; + buffer[3] = 0xfe; + return 4; + }); + fs.closeSync.mockReturnValue(undefined); + + const result = enableTeammateTool(); + + expect(result.success).toBe(false); + expect(result.error).toContain('Native binary'); + }); + it('should create backup before patching', () => { execSync.mockReturnValue('/usr/local/lib/node_modules'); fs.existsSync.mockReturnValue(true);