EdamAme-x / EdamAme-x/pentect

npm のバイナリキャッシュが CPU アーキテクチャを区別せず、別ターゲットの実行ファイルを再利用する

Open
#1,452 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
26
Forks
6
Avg merge
1h 13m
Merged PRs (30d)
384

Description

## 問題

npm installer は download asset を OS / CPU 別に選びますが、キャッシュのパスには CPU アーキテクチャが含まれません。同じ version と cache を使う x64 / arm64 の起動では、先に保存された側の binary をそのまま返します。

確認対象: `main` / `31c515a7cfe6de6e7909c3c7a420385079d85050`(Pentect `0.0.81`)。Linux、Node.js `26.1.0`、Cargo `1.97.1`。

## 再現と実測

現行の `ensureInstalled()` を直接呼び、GitHub download を合成 bytes + 対応する SHA-256 に置き換えました。単一の一時キャッシュに対して `process.arch` を `x64` → `arm64` と切り替えた結果です。

```json
{
"firstAsset": "pentect-linux-x86_64",
"secondAsset": "pentect-linux-aarch64",
"sameCachePath": true,
"secondDownloadRequests": 0,
"secondCachedBytes": "synthetic binary for x64"
}
```

2 回目の asset 選択は arm64 ですが、`ensureInstalled()` は download も検証もせず x64 の bytes を返しました。

ネットワーク不要の再現スクリプト

以下を `/tmp/pentect-arch-repro.mjs` に保存し、`node /tmp/pentect-arch-repro.mjs /absolute/path/to/pentect` で実行できます。

```js
import assert from 'node:assert/strict';
import {createHash} from 'node:crypto';
import {mkdtemp, readFile, rm} from 'node:fs/promises';
import {tmpdir} from 'node:os';
import {join} from 'node:path';
import {pathToFileURL} from 'node:url';

const root = process.argv[2];
const {ensureInstalled, releaseAsset} = await import(pathToFileURL(join(root, 'packaging/npm/install.js')));
const cache = await mkdtemp(join(tmpdir(), 'pentect-arch-repro-'));
const originalArch = Object.getOwnPropertyDescriptor(process, 'arch');
const originalFetch = globalThis.fetch;
const previousCache = process.env.PENTECT_NPM_CACHE;
process.env.PENTECT_NPM_CACHE = cache;
let requests = [];
globalThis.fetch = async url => {
requests.push(url);
const bytes = Buffer.from(`synthetic binary for ${process.arch}`);
if (url.endsWith('.gz')) return new Response('', {status: 404});
if (url.endsWith('.sha256')) return new Response(createHash('sha256').update(bytes).digest('hex'));
return new Response(bytes);
};
try {
Object.defineProperty(process, 'arch', {...originalArch, value: 'x64'});
const firstAsset = releaseAsset();
const first = await ensureInstalled();
requests = [];
Object.defineProperty(process, 'arch', {...originalArch, value: 'arm64'});
const secondAsset = releaseAsset();
const second = await ensureInstalled();
const bytes = await readFile(second, 'utf8');
const result = {firstAsset, secondAsset, sameCachePath: first === second,
secondDownloadRequests: requests.length, secondCachedBytes: bytes};
console.log(JSON.stringify(result, null, 2));
assert.notEqual(firstAsset, secondAsset);
assert.equal(first, second);
assert.equal(requests.length, 0);
assert.equal(bytes, 'synthetic binary for x64');
} finally {
Object.defineProperty(process, 'arch', originalArch);
globalThis.fetch = originalFetch;
if (previousCache === undefined) delete process.env.PENTECT_NPM_CACHE;
else process.env.PENTECT_NPM_CACHE = previousCache;
await rm(cache, {recursive: true, force: true});
}
```

## 原因と影響

- [releaseAsset()](https://github.com/EdamAme-x/pentect/blob/31c515a7cfe6de6e7909c3c7a420385079d85050/packaging/npm/install.js#L29) は `process.platform` と `process.arch` の組を使います。
- [installationPath()](https://github.com/EdamAme-x/pentect/blob/31c515a7cfe6de6e7909c3c7a420385079d85050/packaging/npm/install.js#L20) は `//pentect` の形で、architecture を使いません。
- `ensureInstalled()` は `access(destination)` が成功すれば、そのファイルを返します。

異なる architecture の開発コンテナや CI 間でキャッシュを共有すると、異なる target の executable が選択されます。macOS の arm64 / Rosetta 起動間もキャッシュキー上は衝突します。`PENTECT_NPM_CACHE` を OS 間で共有した場合、Linux / macOS も同じパスになります。

この再現はキャッシュ選択の検証です。別 CPU の binary の実行や Rosetta 実機での動作までは検証していません。実行可否はエミュレーションの有無にも依存します。

## 完了条件

- バイナリキャッシュを version・platform・architecture、または一意な release asset 名で区別する。
- 旧形式のキャッシュを異なる target 用として無条件に流用しない。
- 同一 cache の x64 → arm64、逆順、OS 切替、および同一 target の cache hit を回帰検証する。

Contributor guide

Open the contributing guide

Research direction

Start in packaging/npm/install.js with installationPath(), releaseAsset(), and ensureInstalled(), then run the supplied /tmp/pentect-arch-repro.mjs script against the repository. Update the cache behavior so version, platform, and architecture or the release asset uniquely identify entries, and verify x64→arm64, the reverse order, OS changes, and same-target cache hits.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.