fix: resolve Setup hook broken reference and warn on macOS-only binary (#1547) (#1696)

* fix: resolve Setup hook broken reference and warn on macOS-only binary (#1547)

On Linux ARM64, the plugin silently failed because:
1. The Setup hook called setup.sh which was removed; the hook exited 127
   (file not found), causing the plugin to appear uninstalled.
2. The committed plugin/scripts/claude-mem binary is macOS arm64 only;
   no warning was shown when it could not execute on other platforms.

Fix the Setup hook to call smart-install.js (the current setup mechanism)
and add checkBinaryPlatformCompatibility() to smart-install.js, which reads
the Mach-O magic bytes from the bundled binary and warns users on non-macOS
platforms that the JS fallback (bun-runner.js + worker-service.cjs) is active.

Generated by Claude Code
Vibe coded by ousamabenyounes

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: close fd in finally block, strengthen smart-install tests to use production function

- Wrap openSync/readSync in checkBinaryPlatformCompatibility with a finally block so the file descriptor is always closed even if readSync throws
- Export checkBinaryPlatformCompatibility with an optional binaryPath param for testability
- Refactor Mach-O detection tests to call the production function directly, mocking process.platform and passing controlled binary paths, eliminating duplicated inline logic
- Strengthen plugin-distribution test to assert at least one command hook exists before checking for smart-install.js, preventing vacuous pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Ben Younes
2026-04-15 09:58:29 +02:00
committed by GitHub
parent f97c50bfb9
commit 4538e686ad
4 changed files with 207 additions and 2 deletions
+117
View File
@@ -3,6 +3,7 @@ import { existsSync, mkdirSync, writeFileSync, rmSync, readFileSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { spawnSync } from 'child_process';
import { checkBinaryPlatformCompatibility } from '../plugin/scripts/smart-install.js';
/**
* Smart Install Script Tests
@@ -237,3 +238,119 @@ describe('smart-install stdout JSON output (#1253)', () => {
}
});
});
/**
* Tests for checkBinaryPlatformCompatibility() (#1547).
*
* The bundled plugin/scripts/claude-mem binary is macOS arm64 only.
* On Linux/Windows it cannot execute and hooks fail silently.
* These tests call the production function directly, mocking process.platform
* and passing controlled binary paths to verify Mach-O detection behaviour.
*/
describe('smart-install binary platform compatibility (#1547)', () => {
let testDir: string;
let originalPlatform: PropertyDescriptor | undefined;
beforeEach(() => {
testDir = join(tmpdir(), `claude-mem-binary-compat-test-${process.pid}`);
mkdirSync(testDir, { recursive: true });
originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform');
});
afterEach(() => {
rmSync(testDir, { recursive: true, force: true });
// Restore process.platform
if (originalPlatform) {
Object.defineProperty(process, 'platform', originalPlatform);
}
});
function setPlatform(value: string) {
Object.defineProperty(process, 'platform', { value, configurable: true });
}
it('should detect native arm64/x86_64 Mach-O binary and warn on Linux', () => {
// Real macOS arm64 binary header: bytes CF FA ED FE (MH_MAGIC_64)
const binaryPath = join(testDir, 'claude-mem');
writeFileSync(binaryPath, Buffer.from([0xCF, 0xFA, 0xED, 0xFE, 0x0C, 0x00, 0x00, 0x01]));
const stderrLines: string[] = [];
const originalError = console.error;
console.error = (...args: any[]) => stderrLines.push(args.join(' '));
setPlatform('linux');
try {
checkBinaryPlatformCompatibility(binaryPath);
} finally {
console.error = originalError;
}
expect(stderrLines.some(l => l.includes('macOS-only'))).toBe(true);
expect(stderrLines.some(l => l.includes('linux'))).toBe(true);
});
it('should detect byte-swapped Mach-O binary and warn on Linux', () => {
// Byte-swapped 64-bit Mach-O: bytes FE ED FA CF (MH_CIGAM_64)
const binaryPath = join(testDir, 'claude-mem-swapped');
writeFileSync(binaryPath, Buffer.from([0xFE, 0xED, 0xFA, 0xCF, 0x01, 0x00, 0x00, 0x0C]));
const stderrLines: string[] = [];
const originalError = console.error;
console.error = (...args: any[]) => stderrLines.push(args.join(' '));
setPlatform('linux');
try {
checkBinaryPlatformCompatibility(binaryPath);
} finally {
console.error = originalError;
}
expect(stderrLines.some(l => l.includes('macOS-only'))).toBe(true);
});
it('should NOT warn for an ELF binary (Linux native) on Linux', () => {
// ELF magic: 0x7F 'E' 'L' 'F'
const binaryPath = join(testDir, 'claude-mem-elf');
writeFileSync(binaryPath, Buffer.from([0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00]));
const stderrLines: string[] = [];
const originalError = console.error;
console.error = (...args: any[]) => stderrLines.push(args.join(' '));
setPlatform('linux');
try {
checkBinaryPlatformCompatibility(binaryPath);
} finally {
console.error = originalError;
}
expect(stderrLines.some(l => l.includes('macOS-only'))).toBe(false);
});
it('should not throw when binary path does not exist', () => {
const binaryPath = join(testDir, 'nonexistent-claude-mem');
expect(existsSync(binaryPath)).toBe(false);
setPlatform('linux');
expect(() => checkBinaryPlatformCompatibility(binaryPath)).not.toThrow();
});
it('should skip the check entirely when platform is darwin', () => {
// Write a Mach-O binary — on macOS the check returns early, so no warning
const binaryPath = join(testDir, 'claude-mem');
writeFileSync(binaryPath, Buffer.from([0xCF, 0xFA, 0xED, 0xFE, 0x0C, 0x00, 0x00, 0x01]));
const stderrLines: string[] = [];
const originalError = console.error;
console.error = (...args: any[]) => stderrLines.push(args.join(' '));
setPlatform('darwin');
try {
checkBinaryPlatformCompatibility(binaryPath);
} finally {
console.error = originalError;
}
expect(stderrLines.length).toBe(0);
});
});