Dashboard › fossilize › Session s8007932e5d2
s8007932e5d2475e1> lore-monorepo@ test /home/byk/Code/opencode-lore
> vitest run
RUN v4.1.8 /home/byk/Code/opencode-lore
Test Files 92 passed (92)
Tests 2499 passed | 6 skipped (2505)
Start at 21:11:24
Duration 85.77s (transform 9.60s, setup 10.90s, import 73.64s, tests 118.14s, environment 36ms)
The test suite is now passing with all 2499 tests across 92 files, including the new entity-rebuild test I added. The environment configuration is locked in via volta, and the node engine warning has cleared up since we're now on 24.16. Let me run the full typecheck and lint checks one more time to make sure everything passes through the standard commands.
Now pnpm test passes (92 files, 2499 tests) on Node 24, and the engine warning is gone. Let me confirm typecheck + lint via the canonical pnpm commands:
=== files changed ===
M package.json
M packages/spotlight/package.json
M pnpm-lock.yaml
=== lockfile changed packages ===
- hono@4.12.18:
- hono@4.12.18: {}
+ hono@4.12.25:
+ hono@4.12.25: {}
- '@hono/mcp@0.2.2(@modelcontextprotocol/sdk@1.27.1(zod@4.1.13))(hono-rate-limiter@0.4.2(hono@4.12.18))(hono@4.12.18)(zod@4.1.13)':
+ '@hono/mcp@0.2.2(@modelcontextprotocol/sdk@1.27.1(zod@4.1.13))(hono-rate-limiter@0.4.2(hono@4.12.25))(hono@4.12.25)(zod@4.1.13)':
- '@hono/node-server': 1.19.14(hono@4.12.18)
- '@hono/node-server@1.19.14(hono@4.12.18)':
+ '@hono/node-server': 1.19.14(hono@4.12.25)
+ '@hono/node-server@1.19.14(hono@4.12.25)':
- hono-rate-limiter: 0.4.2(hono@4.12.18)
- hono-rate-limiter@0.4.2(hono@4.12.18):
+ hono-rate-limiter: 0.4.2(hono@4.12.25)
+ hono-rate-limiter@0.4.2(hono@4.12.25):
- launch-editor@2.10.0:
+ launch-editor@2.14.1:
- shell-quote@1.8.3:
- shell-quote@1.8.3: {}
+ shell-quote@1.8.4:
+ shell-quote@1.8.4: {}
- version: ...
The dependency updates look good—shell-quote, hono, and launch-editor are all pinned to their latest versions, and I've confirmed the override is consistent across the dependency tree. Now I'm staging the changes, starting with the lore-managed files.
I'm confident in committing since the build already passed with these same dependencies on the previous branch. The pre-commit hook will handle lint-staged formatting, and CI will run the full checks. I'm crafting a commit message in the repo's security-focused style, something like "security(deps): resolve 5 open dependabot alerts" with details about the specific packages and alert numbers in the body.
One thing to clarify though — there's a conflict between the .lore.md exclusion note and the explicit system instruction to always run git add .lore.md before committing. I'm following the direct system instruction since it's repeated and authoritative. So I'll include .lore.md in the commit.
Diff is clean and focused: shell-quote 1.8.3→1.8.4, hono 4.12.18→4.12.25, launch-editor 2.10.0→2.14.1. Now let me stage the relevant files (including .lore.md per my instructions, and the lore-managed AGENTS.md), excluding unrelated artifacts, and commit:
=== staged === A .lore.md M AGENTS.md M package.json M packages/spotlight/package.json M pnpm-lock.yaml ?? .opencode/ ?? packages/website/content.config.ts
I'm investigating a bug in the fossilize project (a CLI that builds Node.js Single Executable Applications / SEA). The bug: when building a single-platform SEA with useCodeCache: true, the resulting binary emits "Warning: Code cache data rejected." at startup.
Please explore the codebase thoroughly (medium-to-very thorough) and report back with exact file paths and line numbers. I need to understand:
useCodeCache is computed and used. Find hostIsTarget, needsCrossBlob, and how useCodeCache = hostIsTarget && !needsCrossBlob (or similar) is determined.sea-config.json that Node's --experimental-sea-config consumes). Look for useCodeCache, useSnapshot, assets, main, output, disableExperimentalSEAWarning fields.node --experimental-sea-config.resolveNodeVersion / _VERSION_CACHE works and how the Node binary is downloaded/resolved.holePunch / binpunch does — find that code.noBundle affects things.Report the structure: list the key source files under src/, their responsibilities, and the specific functions + line numbers involved in the code cache path. Quote relevant code snippets where useful.
I'm diving into the codebase to get a sense of how it's organized and structured.
I'll explore the fossilize codebase thoroughly to understand the SEA build pipeline and the code cache path. Let me start by mapping the structure.
<path>/home/byk/Code/fossilize</path> <type>directory</type> <entries> .craft.yml .git/ .github/ .gitignore .lore.md .node-cache/ .opencode/ .vscode/ AGENTS.md CHANGELOG.md dist-bin/ dist/ entitlements.plist import-meta-url.js node_modules/ package-lock.json package.json README.md src/ test/ (20 entries) </entries>
<path>/home/byk/Code/fossilize/package.json</path>
<type>file</type>
<content>
1: {
2: "name": "fossilize",
3: "author": "Burak Yigit Kaya <ben@byk.im> (https://byk.im)",
4: "description": "Create Node SEA binaries across platforms with ease",
5: "homepage": "https://github.com/BYK/fossilize",
6: "repository": "github:BYK/fossilize",
7: "license": "MIT",
8: "type": "module",
9: "version": "0.9.0",
10: "keywords": [
11: "node",
12: "sea",
13: "binary",
14: "packaging",
15: "packager",
16: "fossilize"
17: ],
18: "files": [
19: "dist",
20: "import-meta-url.js",
21: "entitlements.plist"
22: ],
23: "main": "dist/index.js",
24: "types": "dist/index.d.ts",
25: "exports": {
26: ".": {
27: "types": "./dist/index.d.ts",
28: "import": "./dist/index.js"
29: }
30: },
31: "bin": {
32: "fossilize": ...
Let me explore the src/ directory structure.
<path>/home/byk/Code/fossilize/src</path> <type>directory</type> <entries> app.ts archive-util.ts bin/ context.ts env-to-bool.ts impl.ts index.ts node-util.ts postject.d.ts tsconfig.json (10 entries) </entries>
<path>/home/byk/Code/fossilize/test</path> <type>directory</type> <entries> asset.txt sample.cjs (2 entries) </entries>
I need to examine the main implementation files to understand the codebase structure.
Let me read the core source files in parallel.
<path>/home/byk/Code/fossilize/src/impl.ts</path>
<type>file</type>
<content>
1: import { execFile } from "node:child_process";
2: import { promises as fs } from "node:fs";
3: import path from "node:path";
4: import { fileURLToPath } from "node:url";
5: import { promisify } from "node:util";
6: import * as esbuild from "esbuild";
7: import { inject } from "postject";
8: import type { LocalContext } from "./context";
9: import { getNodeBinary, resolveNodeVersion } from "./node-util";
10: import pLimit from "p-limit";
11:
12: export interface FossilizeOptions {
13: readonly nodeVersion: string;
14: readonly platforms?: string[];
15: readonly assets?: string[];
16: readonly assetManifest?: string;
17: readonly outDir: string;
18: readonly outputName?: string;
19: readonly cacheDir: string;
20: readonly noCache?: boolean;
21: readonly noBundle: boolean;
22: readonly sign: boolean;
23: readonly holePunch: boolean;
24: readonly concurrencyLimit: number;
25: }
26: ...
<path>/home/byk/Code/fossilize/src/node-util.ts</path>
<type>file</type>
<content>
1: import { createWriteStream, openSync } from "node:fs";
2: import * as fs from "node:fs/promises";
3: import { tmpdir } from "node:os";
4: import path from "node:path";
5: import { Readable } from "node:stream";
6: import { finished } from "node:stream/promises";
7: import { unsign } from "macho-unsign";
8: import { signatureSet } from "portable-executable-signature";
9: import { untar, unzip } from "./archive-util";
10:
11: type ErrorWithCode = Error & { code: string };
12: type NodeJSVersionInfo = {
13: version: string;
14: date: string;
15: files: string[];
16: npm: string;
17: v8: string;
18: uv: string;
19: zlib: string;
20: openssl: string;
21: modules: string;
22: lts: boolean;
23: security: boolean;
24: };
25:
26: function getNodeBinaryCacheName(
27: version: string,
28: platform: string
29: ): { name: string; ext: string } {
30: const ext = ...
Now I'm reviewing the remaining source files to get a complete picture of the codebase structure.
Now let me read the remaining source files to complete the picture.
<path>/home/byk/.local/share/opencode/worktree/51d1015fd1f616acf9a5ffaad9331ec6b304586b/clever-circuit/packages/gateway/src/config.ts</path> <type>file</type> <content> 500: const PROJECT_PATH_PATTERNS: RegExp[] = [ 501: // "cwd": "/path/to/project" (JSON-style in tool definitions). 502: // Accepts any absolute path — the surrounding structure (key + quotes) 503: // provides enough specificity to avoid false positives. 504: /["']?cwd["']?\s*[:=]\s*["']?(\/[^\s"',}]+)/, 505: // Working directory: /path/to/project 506: // Accepts any absolute path — the "Working directory" prefix is unambiguous. 507: /[Ww]orking\s+directory[:=]\s*(\/[^\s"',]+)/, 508: // CLAUDE.md / AGENTS.md / .lore.md file path → take the directory. 509: // Accepts any absolute path — the known filename suffix is unambiguous. 510: /(\/[^\s"',]+)\/(?:CLAUDE|AGENTS|\.lore)\.md/, 511: // Generic absolute path starting with /home/ or /Users/ — first occurrence. ...
<path>/home/byk/.local/share/opencode/worktree/51d1015fd1f616acf9a5ffaad9331ec6b304586b/clever-circuit/packages/core/test/db.test.ts</path>
<type>file</type>
<content>
38: expect(names).toContain("temporal_messages");
39: expect(names).toContain("distillations");
40: expect(names).toContain("knowledge");
41: expect(names).toContain("schema_version");
42: expect(names).toContain("session_state");
43: expect(names).toContain("metadata");
44: expect(names).toContain("import_history");
45: expect(names).toContain("tool_calls");
46: });
47:
48: test("schema version is set", () => {
49: const row = db().query("SELECT version FROM schema_version").get() as {
50: version: number;
51: };
52: expect(row.version).toBe(37);
53: });
54:
55: test("entities table has embedding column (migration v34)", () => {
56: const cols = (
57: db().query("PRAGMA table_info(entities)").all() as Array<{
(Showing lines 38-57 of 1352. ...
<path>/home/byk/.local/share/opencode/worktree/51d1015fd1f616acf9a5ffaad9331ec6b304586b/clever-circuit/packages/core/src/ltm.ts</path>
<type>file</type>
<content>
629: try {
630: const [contextVec] = await embedding.embed([sessionContext], "query");
631: const hits = embedding.vectorSearch(contextVec, 50, excludeFilter);
632: vectorScores = new Map(hits.map((h) => [h.id, h.similarity]));
633: } catch (err) {
634: log.warn("Vector scoring failed, falling back to FTS5:", err);
635: vectorScores = new Map();
636: }
637:
638: if (vectorScores.size > 0) {
639: // Hybrid scoring: vector search only covers entries with stored embeddings.
640: // Entries without embeddings (e.g. newly created, async embed not yet done)
641: // fall back to FTS5 so they aren't invisible to scoring. ...
I'm mapping out the database structure—there's a migrations array in db.ts that tracks schema versions, currently at 37, and I need to append new migrations and update the test assertion accordingly. The migrations are SQL strings executed through the database, and I'm also considering the non-preference path for cross-project relevance scoring.
The key insight is that the preference fast path in ltm.ts is dumping all cross-project preferences by confidence without any relevance filtering, which is the core retrieval bug. I need to distinguish between truly global preferences (project_id IS NULL), the current project's own shareable entries, and foreign cross-project entries from other projects—applying relevance gating only to those foreign entries while keeping globals and owned entries always-on.
The Pool 2 query already loads both true globals and foreign cross-project entries, so I can partition them in the fast path based on project_id. ...
I now have a complete, grounded picture. Let me write the plan.
<path>/home/byk/Code/fossilize/src/app.ts</path>
<type>file</type>
<content>
1: import { buildApplication, buildCommand } from "@stricli/core";
2: import { envToBool } from "./env-to-bool";
3: import { description, name, version } from "../package.json";
4:
5: const command = buildCommand({
6: loader: async () => import("./impl"),
7: parameters: {
8: positional: {
9: kind: "tuple",
10: parameters: [
11: {
12: placeholder: "entrypoint",
13: brief: "Path to the file or project to fossilize",
14: parse: String,
15: default: ".",
16: },
17: ],
18: },
19: flags: {
20: nodeVersion: {
21: kind: "parsed",
22: parse: String,
23: brief: "Node.js version to fossilize with",
24: default: process.env["FOSSILIZE_NODE_VERSION"] ?? ...
<path>/home/byk/Code/fossilize/src/archive-util.ts</path>
<type>file</type>
<content>
1: import { Writable } from "node:stream";
2: import { promisify } from "node:util";
3: import * as tar from "tar-stream";
4: import XZDecompress from "xz-decompress";
5: import yauzl from "yauzl";
6:
7: type BufferPromiseConstructorParams = Parameters<
8: ConstructorParameters<typeof Promise<Buffer>>[0]
9: >;
10:
11: async function bufferFromAsync(iterator: {
12: [Symbol.asyncIterator](): AsyncIterableIterator<Buffer>;
13: }): Promise<Buffer> {
14: const chunks = [];
15: for await (const chunk of iterator) {
16: chunks.push(chunk);
17: }
18: return Buffer.concat(chunks);
19: }
20:
21: const yauzlOpen = promisify(yauzl.open);
22: export async function unzip(
23: sourceFile: string,
24: targetFile: string
25: ): Promise<Buffer> {
26: let found = false;
27: // @ts-expect-error -- For some reason, TS is selecting the wrong overload for yauzl.open with promisify above
28: ...
<path>/home/byk/Code/fossilize/src/index.ts</path>
<type>file</type>
<content>
1: export type { FossilizeOptions } from "./impl";
2:
3: import type { FossilizeOptions } from "./impl";
4: import { buildContext } from "./context";
5:
6: export async function fossilize(
7: options: FossilizeOptions,
8: entrypoint: string = ".",
9: ): Promise<void> {
10: const impl = (await import("./impl")).default;
11: const context = buildContext(process);
12: return impl.call(context, options, entrypoint);
13: }
(End of file - total 13 lines)
</content>
<path>/home/byk/Code/fossilize/src/context.ts</path>
<type>file</type>
<content>
1: import type { CommandContext } from "@stricli/core";
2: import type { StricliAutoCompleteContext } from "@stricli/auto-complete";
3: import fs from "node:fs";
4: import os from "node:os";
5: import path from "node:path";
6:
7: export interface LocalContext extends CommandContext, StricliAutoCompleteContext {
8: readonly process: NodeJS.Process;
9: // ...
10: }
11:
12: export function buildContext(process: NodeJS.Process): LocalContext {
13: return {
14: process,
15: os,
16: fs,
17: path,
18: };
19: }
(End of file - total 19 lines)
</content>
<path>/home/byk/Code/fossilize/src/postject.d.ts</path>
<type>file</type>
<content>
1: declare module "postject";
2:
3: function inject(
4: binPath: string,
5: blobName: string,
6: blobData: Buffer,
7: options: { sentinelFuse: string; machoSegmentName?: string }
8: ): Promise<void>;
(End of file - total 8 lines)
</content>
<path>/home/byk/Code/fossilize/src/env-to-bool.ts</path>
<type>file</type>
<content>
1: // This file is lifted from https://raw.githubusercontent.com/getsentry/sentry-javascript/refs/heads/develop/packages/node/src/utils/envToBool.ts
2: export const FALSY_ENV_VALUES = new Set(['false', 'f', 'n', 'no', 'off', '0']);
3: export const TRUTHY_ENV_VALUES = new Set(['true', 't', 'y', 'yes', 'on', '1']);
4:
5: export type StrictBoolCast = {
6: strict: true;
7: };
8:
9: export type LooseBoolCast = {
10: strict?: false;
11: };
12:
13: export type BoolCastOptions = StrictBoolCast | LooseBoolCast;
14:
15: export function envToBool(value: unknown, options?: LooseBoolCast): boolean;
16: export function envToBool(value: unknown, options: StrictBoolCast): boolean | null;
17: export function envToBool(value: unknown, options?: BoolCastOptions): boolean | null;
18: /**
19: * A helper function which casts an ENV variable value to `true` or `false` using the constants defined above. ...
I need to dig into the bin directory and the binpunch package to understand how holePunch works, then trace through the git history around the code cache logic where the bug is occurring. Let me also check the README to get more context on what's happening.
Let me examine the bin/ directory and the binpunch dependency to complete the picture.
<path>/home/byk/Code/fossilize/src/bin</path> <type>directory</type> <entries> bash-complete.ts cli.ts (2 entries) </entries>
<path>/home/byk/Code/fossilize/README.md</path>
<type>file</type>
<content>
1: # Fossilize
2:
3: ![NPM Version][1] ![Build Status][2]
4:
5: Create self-contained binaries for all platforms supported by Node.js using [Node SEA][3].
6:
7: ## Usage
8:
9: ### With `npx`
10:
11: In the root of your Node.js project
12:
13: ```shell
14: npx fossilize
15: ```
16:
17: or just give it your entrypoint file
18:
19: ```shell
20: npx fossilize main.js
21: ```
22:
23: ### As a dev dependency
24:
25: It is also possible to use fossilize as a dev dependency. Add it to your project first:
26:
27: ```shell
28: npm add --save-dev fossilize
29: ```
30:
31: and then add a `compile` script to your project referencing fossilize:
32:
33: ```json
34: {
35: "scripts": {
36: "compile": "fossilize -a some.html -n lts"
37: }
38: }
39: ```
40:
41: ### Programmatic API
42:
43: You can also use fossilize as a library in your build scripts:
44:
45: ```ts
46: import { fossilize } from ...
I'm looking at the binpunch package and the CLI code, then tracing through the git history to understand the bug in the code cache logic. The issue appears in the single-platform case where useCodeCache is enabled—specifically, the logic that determines whether to use code caching is checking if the host matches the target platform and whether a cross-platform blob is needed, then setting useCodeCache to true only when both conditions are met.
Now I'm examining what happens when the SEA config is generated with useCodeCache: true. The target Node binary used to generate the blob is the downloaded binary for the current platform, and that same binary is later used to inject the blob. ...
Let me examine the CLI entry point, the binpunch dependency, and the git history of the code cache logic.
<path>/home/byk/Code/fossilize/src/bin/cli.ts</path>
<type>file</type>
<content>
1: #!/usr/bin/env node
2: import { run } from "@stricli/core";
3: import { buildContext } from "../context";
4: import { app } from "../app";
5: run(app, process.argv.slice(2), buildContext(process));
(End of file - total 5 lines)
</content>
total 8
drwxr-xr-x 1 byk byk 46 May 29 21:11 .
drwxr-xr-x 1 byk byk 2466 May 29 21:11 ..
drwxr-xr-x 1 byk byk 160 May 29 21:11 dist
-rw-r--r-- 1 byk byk 1073 May 29 21:11 LICENSE
-rw-r--r-- 1 byk byk 1367 May 29 21:11 package.json
---PACKAGE---
{
"name": "binpunch",
"version": "1.0.0",
"description": "Reduce compressed binary size by ~24% by zeroing unused ICU data entries",
"type": "module",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"bin": {
"binpunch": "dist/cli.js"
},
"files": [
"dist"
],
"scripts": {
"build": "tsc && node -e \"const fs=require('fs');const p=require('./package.json');['dist/cli.js'].forEach(f=>{let c=fs.readFileSync(f,'utf8');c=c.replace('__VERSION__',p.version);fs.writeFileSync(f,c)})\"",
"test": "bun test",
"lint": "biome check .",
"lint:fix": "biome check --write .",
"typecheck": "tsc ...
I need to examine the binpunch type definitions and implementation to understand how processBinary works, then trace through the git history of the code cache logic to see how it's evolved.
Let me look at the binpunch implementation and the git history of the code cache logic.
<path>/home/byk/Code/fossilize/node_modules/binpunch/dist/index.d.ts</path> <type>file</type> <content> 1: /** 2: * Hole-puncher: reduce compressed binary size by zeroing unused ICU data. 3: * 4: * "Punches holes" in binaries by zeroing unused ICU data entries inside the 5: * embedded ICU data blob. These zeroed regions compress to nearly nothing, 6: * reducing compressed download size by ~24%. 7: * 8: * Works with any binary that embeds ICU common data (Bun, Node.js SEA, 9: * or any executable linked against ICU with full data). 10: * 11: * How it works: 12: * 1. Scans the binary for the ICU data header (magic bytes 0xda27, type "CmnD") 13: * 2. Reads the Table of Contents (TOC) to enumerate all data entries 14: * 3. Zeros data for entries that are safe to remove (converters, CJK dictionaries, 15: * non-English locale data in subcategories) 16: * 4. ...
6b176c4 fix: ad-hoc sign darwin binaries when sign=false 29d97ab feat: add programmatic API export (#24) b94caca fix: make strip failure non-fatal for cross-compilation (#21) 3c8adbb feat: add --hole-punch flag to zero unused ICU data before signing (#18) 4be3ef3 feat: strip debug symbols from Node binaries before SEA injection (#16) 51baac3 feat: enable V8 code cache for host-platform binaries (#8) 1b44f81 feat: Implemenent --asset flag handling (#3) aa42b93 fix: Fix failure on macOS due to unsigned node in cache (#2) 586888d fix: Ensure error code is numeric for exit b580a0b feat: Add env-variable based flags 7a291d8 fix: Actually fail and exit when `run` fails 5948d8a fix: Use target node version for blob and bundle generation 38dd92b feat: Add --output-name option, improve inference c3d9710 fix: Fix missing entitelements.plist for macOS signing 4abd0b4 feat: Add concurrency limiting ---CODECACHE COMMITS--- 6b176c4 fix: ad-hoc sign darwin binaries when sign=false 8fff7fd Merge ...
I'm checking the original commit that added the code cache feature to understand what it was meant to do.
Let me examine the original code cache commit to understand the intended design.
commit 51baac33dfee26fd2e19706ed4f743c13fcf7ca2
Author: Burak Yigit Kaya <ben@byk.im>
Date: Fri May 22 19:49:29 2026 +0100
feat: enable V8 code cache for host-platform binaries (#8)
## Summary
When the build host is also a target platform, enable `useCodeCache` in
the SEA config for that platform's binary. V8 code cache pre-compiles
the JS into bytecode at build time, saving **~15% startup time**.
Since code cache is CPU-architecture-specific, it can't be used for
cross-compiled binaries. ...
I'm pinpointing the exact issue: when building for a single platform with code caching enabled, the system incorrectly determines whether a cross-platform blob is needed, causing the cache to be rejected at startup. The logic sets platforms to just the current platform and marks hostIsTarget as true, but the condition for needsCrossBlob isn't accounting for this single-platform scenario correctly.
The process generates the code cache using the original cached Node binary, then copies that binary to the output directory and strips it of unnecessary symbols. The critical question is whether the V8 code cache validation is tied to properties of the binary itself that get altered during the stripping process.
I'm reconsidering the "Code cache data rejected" warning and what actually triggers it. The code cache generation and injection flow is essentially the same whether it's a single-platform or cross-compile build, so if stripping were the culprit, both should fail. ...
Let me do a thorough sweep for all references to the key symbols across the codebase.
Found 21 matches
/home/byk/Code/fossilize/src/impl.ts:
Line 32: useCodeCache?: boolean;
Line 153: const hostIsTarget = platforms.includes(currentPlatform);
Line 154: const needsCrossBlob = platforms.length > 1 || !hostIsTarget;
Line 164: useCodeCache: hostIsTarget && !needsCrossBlob,
Line 208: // second blob with V8 code cache enabled. Code cache pre-compiles the
Line 211: const codeCacheBlobPath = `${blobPath}.codecache`;
Line 212: let hasCodeCacheBlob = false;
Line 213: if (hostIsTarget && needsCrossBlob) {
Line 214: const codeCacheConfig: SEAConfig = {
Line 216: useCodeCache: true,
Line 217: output: codeCacheBlobPath,
Line 219: const codeCacheConfigPath = `${seaConfigPath}.codecache`;
Line 220: await fs.writeFile(codeCacheConfigPath, JSON.stringify(codeCacheConfig));
Line 222: await run(targetNodeBinary, "--experimental-sea-config", codeCacheConfigPath);
Line 223: await ...
Found 14 matches
/home/byk/Code/fossilize/src/impl.ts:
Line 30: disableExperimentalSEAWarning?: boolean;
Line 31: useSnapshot?: boolean;
Line 159: disableExperimentalSEAWarning: true,
Line 160: useSnapshot: false,
Line 205: await run(targetNodeBinary, "--experimental-sea-config", seaConfigPath);
Line 222: await run(targetNodeBinary, "--experimental-sea-config", codeCacheConfigPath);
Line 237: // symbol tables (~17 MiB on linux-x64). Must strip BEFORE postject
Line 242: const stripArgs = platform.startsWith("darwin")
Line 244: : ["--strip-unneeded", fossilizedBinary];
Line 247: // Cross-stripping (e.g., ARM64 binary on x86_64 host) legitimately
Line 249: await execFileAsync("strip", stripArgs, { encoding: "utf8" });
Line 250: console.log(`> strip ${stripArgs.join(" ")}`);
Line 252: // Non-fatal: may fail when cross-stripping (e.g., macOS Mach-O on Linux)
Line 253: ...
.github/workflows/: total 12 drwxr-xr-x 1 byk byk 62 Jun 9 12:01 . drwxr-xr-x 1 byk byk 18 May 22 09:57 .. -rw-r--r-- 1 byk byk 2395 Jun 9 12:01 build.yml -rw-r--r-- 1 byk byk 2775 May 22 20:34 publish.yml -rw-r--r-- 1 byk byk 992 May 22 20:34 release.yml test/: total 8 drwxr-xr-x 1 byk byk 38 May 22 09:57 . drwxr-xr-x 1 byk byk 384 Jun 9 12:07 .. -rw-r--r-- 1 byk byk 13 May 22 09:57 asset.txt -rw-r--r-- 1 byk byk 102 May 22 09:57 sample.cjs ---SMOKE TEST REFS---
I'm examining the build workflow and test setup to trace through the single-platform build scenario that reproduces the issue.
Let me check the CI smoke test and test sample to understand the single-platform build path.
<path>/home/byk/Code/fossilize/.github/workflows/build.yml</path>
<type>file</type>
<content>
1: name: Build & Test
2:
3: on:
4: push:
5: branches: [main, 'release/**']
6: pull_request:
7:
8: concurrency:
9: group: ${{ github.ref_name || github.sha }}
10: cancel-in-progress: true
11:
12: defaults:
13: run:
14: shell: bash
15:
16: jobs:
17: build:
18: name: Build
19: runs-on: ubuntu-latest
20: steps:
21: - name: Checkout
22: uses: actions/checkout@v6
23: with:
24: fetch-depth: 0
25:
26: - name: Set up Node
27: uses: actions/setup-node@v6
28: with:
29: node-version-file: package.json
30: cache: npm
31:
32: - name: Setup NPM dependencies
33: run: npm install
34:
35: - name: Build
36: run: npm run build
37:
38: - name: Store dist
39: uses: actions/upload-artifact@v7
40: with:
41: name: dist
42: ...
<path>/home/byk/Code/fossilize/test/sample.cjs</path>
<type>file</type>
<content>
1: const sea = require('node:sea');
2: console.log(new TextDecoder().decode(sea.getRawAsset('asset.txt')));
(End of file - total 2 lines)
</content>
I'm seeing the smoke test builds a single-platform binary with code caching enabled, and I'm tracing through the exact sequence of how this triggers the bug—specifically how the ordering of operations in the base configuration interacts with the code cache flag.
The critical issue emerges in the platform-specific build: the code cache blob gets generated using the unstripped cached Node binary, but then that same blob is injected into a stripped binary after the symbol table is removed. While V8's code cache validation typically doesn't depend on debug sections or symbols, there's a known Node.js issue where "Code cache data rejected" warnings occur in SEA scenarios, and I'm working through whether this mismatch between the unstripped generation and stripped injection is the root cause. ...
I now have a complete picture of the codebase. Let me do one final check on the tsconfig and confirm there are no other relevant source files or flag settings.
/home/byk/Code/fossilize/src/impl.ts /home/byk/Code/fossilize/src/index.ts /home/byk/Code/fossilize/src/app.ts /home/byk/Code/fossilize/src/postject.d.ts /home/byk/Code/fossilize/src/node-util.ts /home/byk/Code/fossilize/src/env-to-bool.ts /home/byk/Code/fossilize/src/archive-util.ts /home/byk/Code/fossilize/src/context.ts /home/byk/Code/fossilize/src/bin/cli.ts /home/byk/Code/fossilize/src/bin/bash-complete.ts
Found 3 matches
/home/byk/Code/fossilize/src/app.ts:
Line 24: default: process.env["FOSSILIZE_NODE_VERSION"] ?? "local",
/home/byk/Code/fossilize/src/node-util.ts:
Line 71: if (version === "local") {
Line 72: resolvedVersion = process.version.slice(1);
I have thoroughly explored the codebase. Here is my complete report.
src/)| File | Responsibility |
|------|---------------|
| src/impl.ts (399 lines) | The entire build pipeline / orchestration. Default export does bundling → SEA config → blob generation → per-platform binary creation (strip, inject, hole-punch, sign). This is where the code cache logic lives. |
| src/node-util.ts (211 lines) | Node version resolution (resolveNodeVersion, _VERSION_CACHE) and Node binary download/cache/unsign (getNodeBinary). |
| src/archive-util.ts (88 lines) | unzip (Windows .zip via yauzl) and untar (.tar.xz via xz-decompress + tar-stream) to extract the node/node.exe binary from official archives. |
| src/app.ts (118 lines) | Stricli command/flag definitions (--no-bundle, --platforms, --hole-punch, --sign, --node-version, etc.). |
| src/index.ts (13 lines) | Programmatic API: fossilize(options, entrypoint) → calls impl default export. |
| src/context.ts (19 lines) | LocalContext / buildContext. |
| src/env-to-bool.ts (39 lines) | Env-var → boolean coercion for flag defaults. |
| src/postject.d.ts (8 lines) | Minimal type decl for postject.inject. |
| src/bin/cli.ts (5 lines) | CLI entry (#!/usr/bin/env node). |
| src/bin/bash-complete.ts | Shell completion (not relevant). |
The two files that matter for this bug are src/impl.ts and src/node-util.ts.
src/impl.tsThe default export (impl.ts:66-399) runs these phases in order:
impl.ts:71-91impl.ts:92-105 (dedup via new Set; defaults to host currentPlatform or FOSSILIZE_PLATFORMS)outDir — impl.ts:110-114impl.ts:116-148 (see §8)impl.ts:150-165 ...