claude-mem-lite 3.93.0 → 3.93.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -2
- package/hook-update.mjs +4 -4
- package/install.mjs +9 -5
- package/lib/hook-stdin.mjs +7 -1
- package/lib/resolve-data-dir.mjs +20 -10
- package/mem-cli.mjs +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/scripts/binding-probe-cli.mjs +1 -1
- package/scripts/hook-launcher.mjs +32 -22
- package/scripts/launch.mjs +1 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "claude-mem-lite",
|
|
13
|
-
"version": "3.93.
|
|
13
|
+
"version": "3.93.1",
|
|
14
14
|
"source": "./",
|
|
15
15
|
"description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark)."
|
|
16
16
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "3.93.
|
|
3
|
+
"version": "3.93.1",
|
|
4
4
|
"description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark).",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "sdsrss"
|
package/README.md
CHANGED
|
@@ -897,8 +897,9 @@ Set by the tool or by the test harness. Setting these by hand is not supported:
|
|
|
897
897
|
|
|
898
898
|
The last two are worth one more sentence each, because they are the ones a harness reaches
|
|
899
899
|
for. `CLAUDE_MEM_RUNTIME_DIR` relocates the runtime directory for hook-written state — markers,
|
|
900
|
-
cooldowns, hook-error telemetry, the native-binding breakage marker,
|
|
901
|
-
|
|
900
|
+
cooldowns, hook-error telemetry, the native-binding breakage marker, episode buffers.
|
|
901
|
+
(`metrics/` is NOT in that set: it is a sibling of `runtime/` under the data dir and moves
|
|
902
|
+
with `CLAUDE_MEM_DIR` only.) Before v3.93.0 it was honoured by some readers and ignored by others, so setting it
|
|
902
903
|
split the runtime rather than moving it. Installation-identity state (`install.lock`,
|
|
903
904
|
`update-state.json`, update residue) deliberately stays under `CLAUDE_MEM_DIR`: two
|
|
904
905
|
installers pointed at different override directories would otherwise each take their own
|
package/hook-update.mjs
CHANGED
|
@@ -33,7 +33,7 @@ const INSTALL_DIR = CODE_DIR; // ~/.claude-mem-lite/ (code)
|
|
|
33
33
|
// DB_DIR), matching hook-shared RUNTIME_DIR and install.mjs doctor's read path.
|
|
34
34
|
// Equal to INSTALL_DIR unless CLAUDE_MEM_DIR relocates the data dir.
|
|
35
35
|
const STATE_DIR = DB_DIR;
|
|
36
|
-
const STATE_FILE = join(STATE_DIR, 'runtime', 'update-state.json');
|
|
36
|
+
const STATE_FILE = join(STATE_DIR, 'runtime', 'update-state.json'); // runtime-dir:stays-put — installation identity
|
|
37
37
|
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
38
38
|
const FETCH_TIMEOUT_MS = 3000; // 3s network timeout
|
|
39
39
|
// When rate-limited we got NO release data, so re-check sooner than the normal 24h
|
|
@@ -641,7 +641,7 @@ export async function verifyReleaseAuthenticity(extractedDir, assets, publicKey
|
|
|
641
641
|
// hooks are best-effort, so losing one fire beats importing a mixed module graph.
|
|
642
642
|
// Carries pid + ts because the launcher must never be muted permanently by an
|
|
643
643
|
// updater that was killed mid-swap (it applies the same staleness bound).
|
|
644
|
-
const SWAP_MARKER = join(STATE_DIR, 'runtime', 'swap-in-progress');
|
|
644
|
+
const SWAP_MARKER = join(STATE_DIR, 'runtime', 'swap-in-progress'); // runtime-dir:stays-put — installation identity
|
|
645
645
|
// Intent journal, written INSIDE the backup dir before each rename. On a hard kill
|
|
646
646
|
// the backup dir survives (every normal exit deletes it) and this file says exactly
|
|
647
647
|
// which paths were in flight, so the next entry can finish the rollback at the right
|
|
@@ -781,7 +781,7 @@ export async function installExtractedRelease(sourceDir, targetDir = INSTALL_DIR
|
|
|
781
781
|
// holding the lock means an install is already in flight — skip rather than
|
|
782
782
|
// race. Shared path with install.mjs so direct install + repair + auto-update
|
|
783
783
|
// are mutually exclusive.
|
|
784
|
-
const release = acquireLock(join(STATE_DIR, 'runtime', 'install.lock'));
|
|
784
|
+
const release = acquireLock(join(STATE_DIR, 'runtime', 'install.lock')); // runtime-dir:stays-put — install lock serialises real installers
|
|
785
785
|
if (!release) {
|
|
786
786
|
debugLog('DEBUG', 'hook-update', 'installExtractedRelease: another install/update is in progress — skipping');
|
|
787
787
|
return false;
|
|
@@ -1115,7 +1115,7 @@ function readState() {
|
|
|
1115
1115
|
|
|
1116
1116
|
function saveState(state) {
|
|
1117
1117
|
try {
|
|
1118
|
-
const dir = join(STATE_DIR, 'runtime');
|
|
1118
|
+
const dir = join(STATE_DIR, 'runtime'); // runtime-dir:stays-put — mkdir for update-state.json above
|
|
1119
1119
|
mkdirSync(dir, { recursive: true });
|
|
1120
1120
|
const tmpFile = STATE_FILE + `.tmp-${process.pid}`;
|
|
1121
1121
|
writeFileSync(tmpFile, JSON.stringify(state, null, 2));
|
package/install.mjs
CHANGED
|
@@ -1791,7 +1791,7 @@ async function doctor() {
|
|
|
1791
1791
|
|
|
1792
1792
|
// Update state
|
|
1793
1793
|
try {
|
|
1794
|
-
const stateFile = join(MEM_DATA_DIR, 'runtime', 'update-state.json');
|
|
1794
|
+
const stateFile = join(MEM_DATA_DIR, 'runtime', 'update-state.json'); // runtime-dir:stays-put — installation identity
|
|
1795
1795
|
if (existsSync(stateFile)) {
|
|
1796
1796
|
const state = JSON.parse(readFileSync(stateFile, 'utf8'));
|
|
1797
1797
|
const parts = [];
|
|
@@ -1951,8 +1951,12 @@ async function doctor() {
|
|
|
1951
1951
|
try {
|
|
1952
1952
|
// hook-update + the episode workers write runtime/ + staging under DB_DIR
|
|
1953
1953
|
// (= MEM_DATA_DIR, env-aware), NOT the homedir code dir — scan there so doctor
|
|
1954
|
-
// sees the real residue under relocation.
|
|
1955
|
-
|
|
1954
|
+
// sees the real residue under relocation. MEM_RUNTIME_DIR rather than
|
|
1955
|
+
// join(MEM_DATA_DIR,'runtime'): `pending-*` / `ep-flush-*` are written through
|
|
1956
|
+
// hook-shared.mjs's override-aware RUNTIME_DIR, and `cleanup()` below deletes them from
|
|
1957
|
+
// MEM_RUNTIME_DIR — v3.93.0 moved the deleter and left this scanner behind, so under the
|
|
1958
|
+
// override doctor reported "none" while the cleanup it recommends removed files.
|
|
1959
|
+
const runtimeDir = MEM_RUNTIME_DIR;
|
|
1956
1960
|
let staleCount = 0;
|
|
1957
1961
|
const stalePatterns = ['.update-staging-', '.update-backup-'];
|
|
1958
1962
|
if (existsSync(MEM_DATA_DIR)) {
|
|
@@ -2560,7 +2564,7 @@ function bindingHostDir() {
|
|
|
2560
2564
|
// two concurrent rebuilds can clobber the .node mid-compile. A live peer → report
|
|
2561
2565
|
// and exit 0 (it is doing this very work), never race it.
|
|
2562
2566
|
async function rebuildBinding() {
|
|
2563
|
-
const release = acquireLock(join(MEM_DATA_DIR, 'runtime', 'install.lock'));
|
|
2567
|
+
const release = acquireLock(join(MEM_DATA_DIR, 'runtime', 'install.lock')); // runtime-dir:stays-put — install lock serialises real installers
|
|
2564
2568
|
if (!release) {
|
|
2565
2569
|
// NOT exit 0: skipping is not healing. Callers key their state on the exit
|
|
2566
2570
|
// code — a false success would let the launcher drop its cooldown and the
|
|
@@ -2609,7 +2613,7 @@ async function rebuildBinding() {
|
|
|
2609
2613
|
// install/self-heal) holds it → skip rather than race into a torn install. Lock
|
|
2610
2614
|
// path is shared with hook-update.installExtractedRelease (both env-aware).
|
|
2611
2615
|
async function runLockedInstall() {
|
|
2612
|
-
const release = acquireLock(join(MEM_DATA_DIR, 'runtime', 'install.lock'));
|
|
2616
|
+
const release = acquireLock(join(MEM_DATA_DIR, 'runtime', 'install.lock')); // runtime-dir:stays-put — install lock serialises real installers
|
|
2613
2617
|
if (!release) {
|
|
2614
2618
|
console.log('[install] Another install/repair is in progress — skipping to avoid a torn write.');
|
|
2615
2619
|
return;
|
package/lib/hook-stdin.mjs
CHANGED
|
@@ -61,7 +61,13 @@ export const TOOL_INPUT_FILE_MAX_BYTES = 8 * 1024 * 1024;
|
|
|
61
61
|
* @returns {{filePath: string, sessionId: string|null, toolName: string|null} | null}
|
|
62
62
|
*/
|
|
63
63
|
export function salvageTruncatedHookEvent(prefix) {
|
|
64
|
-
|
|
64
|
+
// Bounded capture. Unbounded, an 8 MB prefix whose `file_path` string is never closed
|
|
65
|
+
// makes V8 exceed its regexp backtrack limit and THROW RangeError — measured at the cap by
|
|
66
|
+
// the v3.93.0 post-release review. The throw escapes the caller's catch into its top-level
|
|
67
|
+
// one, which still exits 0 but writes a `pre-recall:top` telemetry row: the same
|
|
68
|
+
// hook-error noise the caller split `pre-recall:json` away from. 4096 is far above any
|
|
69
|
+
// real path (PATH_MAX is 4096 on Linux, 1024 on macOS), so no reachable payload is lost.
|
|
70
|
+
const fp = prefix.match(/"file_path"\s*:\s*"((?:[^"\\]|\\.){0,4096})"/);
|
|
65
71
|
if (!fp) return null;
|
|
66
72
|
let filePath;
|
|
67
73
|
// The captured group is still JSON-escaped (Windows paths arrive as `C:\\x`).
|
package/lib/resolve-data-dir.mjs
CHANGED
|
@@ -111,16 +111,26 @@ export function resolveDataDir(raw) {
|
|
|
111
111
|
* the native-binding breakage marker, which is the self-heal trigger). The rule that
|
|
112
112
|
* resolved them, stated so the next sweep does not overshoot:
|
|
113
113
|
*
|
|
114
|
-
* MOVES with the override — state a HOOK writes
|
|
115
|
-
* cross-hook injected-ids markers, skill/pre-recall cooldowns,
|
|
116
|
-
* the
|
|
117
|
-
* the shadow-recommendation log.
|
|
114
|
+
* MOVES with the override — state a HOOK writes INTO THE RUNTIME DIR and another
|
|
115
|
+
* component reads back: cross-hook injected-ids markers, skill/pre-recall cooldowns,
|
|
116
|
+
* hook-error telemetry, the launcher's breakage/heal markers, the native-binding
|
|
117
|
+
* breakage marker, `ep-flush-*` / `pending-*` buffers, the shadow-recommendation log.
|
|
118
118
|
*
|
|
119
|
-
* STAYS under the data dir
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
119
|
+
* STAYS under the data dir, for two DIFFERENT reasons — do not collapse them:
|
|
120
|
+
* (a) state about the ONE REAL INSTALLATION — `install.lock`, `update-state.json`,
|
|
121
|
+
* `swap-in-progress`, the `.update-staging-*` / `.update-backup-*` residue scan.
|
|
122
|
+
* These must not follow a per-harness override: the lock exists to serialise
|
|
123
|
+
* concurrent installers, and two installers pointed at different override
|
|
124
|
+
* directories would each take their own lock and both proceed.
|
|
125
|
+
* (b) state that was never under `runtime/` at all — `metrics/` is a SIBLING of it
|
|
126
|
+
* (`lib/metrics.mjs` is `join(dbDir, 'metrics')`) and every caller passes the data
|
|
127
|
+
* dir. A v3.93.0 draft of this list put it in the MOVES column, which is the one
|
|
128
|
+
* error a reader following this docblock would act on: a sweep obeying it would
|
|
129
|
+
* convert `recordMetric(DB_DIR, …)` to a runtime-relative path and re-open exactly
|
|
130
|
+
* what that release closed. What was fixed there was `join(RUNTIME_DIR, '..')` —
|
|
131
|
+
* an inverse derivation of the data dir that stopped equalling `DB_DIR` the moment
|
|
132
|
+
* the override was honoured. Deriving one dir from the other is the defect; the
|
|
133
|
+
* sink itself never moved.
|
|
124
134
|
*
|
|
125
135
|
* `tests/runtime-dir-single-home.test.mjs` sweeps for the defect FORM (a shipped module
|
|
126
136
|
* building `join(…, 'runtime')` itself) with the stays-put sites as a named allowlist, so
|
|
@@ -137,6 +147,6 @@ export function resolveRuntimeDir(dataDir, env = process.env) {
|
|
|
137
147
|
// turning a previously-working relative path into a throw would break isolation setups
|
|
138
148
|
// in order to enforce tidiness. It is resolved against cwd instead, so the value is at
|
|
139
149
|
// least absolute by the time anything writes to it.
|
|
140
|
-
if (raw === undefined || raw === null || raw === '') return join(dataDir, 'runtime');
|
|
150
|
+
if (raw === undefined || raw === null || raw === '') return join(dataDir, 'runtime'); // runtime-dir:stays-put — this IS the default branch of the rule
|
|
141
151
|
return isAbsolute(raw) ? raw : resolve(raw);
|
|
142
152
|
}
|
package/mem-cli.mjs
CHANGED
|
@@ -1326,7 +1326,7 @@ async function cmdStats(db, args) {
|
|
|
1326
1326
|
out(` Low-value (imp≤1, never used, >30d): ${lowVal.c} (${(noiseRatio * 100).toFixed(1)}% noise)`);
|
|
1327
1327
|
out(` Low-signal titles (Modified/Error/Worked on…): ${lowSignalTitle.c} (${(lowSignalRatio * 100).toFixed(1)}%)`);
|
|
1328
1328
|
out(` Compressed: ${compressedCount.c}`);
|
|
1329
|
-
out(` Hook errors (last 24h): ${hookErrors24h}${hookErrors24h > 0 ? ` ← tail ${join(DB_DIR, '
|
|
1329
|
+
out(` Hook errors (last 24h): ${hookErrors24h}${hookErrors24h > 0 ? ` ← tail ${join(resolveRuntimeDir(DB_DIR), 'hook-errors')}` : ''}`);
|
|
1330
1330
|
// Hint threshold = the REAL eviction budget (pre-release review 2026-08-16: a
|
|
1331
1331
|
// hardcoded 3×-DB heuristic promised an eviction that fires only past the budget).
|
|
1332
1332
|
out(` Disk: DB ${mb(dbBytes)}MB | ${snaps.length} backup snapshot(s) ${mb(backupBytes)}MB${backupBytes > backupBudgetBytes() ? ` ← over the ${mb(backupBudgetBytes())}MB backup budget; next maintain/save snapshot evicts oldest (>7d old)` : ''}`);
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "3.93.
|
|
3
|
+
"version": "3.93.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "claude-mem-lite",
|
|
9
|
-
"version": "3.93.
|
|
9
|
+
"version": "3.93.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
12
12
|
"better-sqlite3": "^12.6.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "3.93.
|
|
3
|
+
"version": "3.93.1",
|
|
4
4
|
"description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@10.9.2",
|
|
@@ -102,7 +102,7 @@ if (first.ok) process.exit(0);
|
|
|
102
102
|
// .node mid-compile.
|
|
103
103
|
let lockPath;
|
|
104
104
|
try {
|
|
105
|
-
lockPath = join(helpers.resolveDataDir(process.env.CLAUDE_MEM_DIR), 'runtime', 'install.lock');
|
|
105
|
+
lockPath = join(helpers.resolveDataDir(process.env.CLAUDE_MEM_DIR), 'runtime', 'install.lock'); // runtime-dir:stays-put — install lock serialises real installers
|
|
106
106
|
} catch (e) {
|
|
107
107
|
// resolveDataDir THROWS on a non-absolute CLAUDE_MEM_DIR. Unhandled, that
|
|
108
108
|
// prints an 8-line rejection stack onto SessionStart stderr; one line is enough.
|
|
@@ -39,13 +39,36 @@ const INSTALL_DIR = join(__dirname, '..');
|
|
|
39
39
|
// non-absolute → default. Data-writing paths import that module and throw instead.
|
|
40
40
|
const MEM_DIR = process.env.CLAUDE_MEM_DIR;
|
|
41
41
|
const DATA_DIR = MEM_DIR && isAbsolute(MEM_DIR) ? MEM_DIR : join(homedir(), '.claude-mem-lite');
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
// TWO runtime dirs, because this file writes BOTH classes of state and v3.93.0 shipped a
|
|
43
|
+
// split by moving only doctor's READ side. `RUNTIME_DIR` is data-dir-relative and serves
|
|
44
|
+
// `swap-in-progress`, which is installation identity and must NOT follow a per-harness
|
|
45
|
+
// override (two installers pointed at different override dirs would each see no swap in
|
|
46
|
+
// progress). `HOOK_RUNTIME_DIR` is override-aware and serves every marker another component
|
|
47
|
+
// reads back — the launcher's own breakage/heal markers, which `install.mjs doctor` reads,
|
|
48
|
+
// and the native-binding pair below. That second constant already existed here as
|
|
49
|
+
// `NB_RUNTIME_DIR` and was applied to only half this file's markers.
|
|
50
|
+
//
|
|
51
|
+
// Inline `||`, not `lib/resolve-data-dir.mjs::resolveRuntimeDir`, and this is the single
|
|
52
|
+
// declared exception in the tree: the launcher runs BEFORE the native binding is known to
|
|
53
|
+
// work, so it imports only `node:` builtins on purpose — even a leaf lib module is a
|
|
54
|
+
// resolution it declines to make on the path whose job is surviving a broken install. The
|
|
55
|
+
// standalone hook scripts it mirrors (pre-tool-recall / pre-skill-bridge) honour the same
|
|
56
|
+
// variable and write 78 of every 79 of these markers, so reading a different dir would mean
|
|
57
|
+
// never healing. `resolveRuntimeDir` is the canonical rule (audit 2026-09-02 P1-14) and this
|
|
58
|
+
// is NOT identical to it: the resolver additionally makes a RELATIVE override absolute
|
|
59
|
+
// (`isAbsolute(raw) ? raw : resolve(raw)`), while this hands the relative value straight to
|
|
60
|
+
// `fs`, which resolves it against cwd at call time rather than at module load. They agree on
|
|
61
|
+
// unset, empty and absolute — the three cases that reach a real install. Keep the DEFAULTING
|
|
62
|
+
// behaviour in step; do not read "identical" into the difference.
|
|
63
|
+
// `tests/runtime-dir-single-home.test.mjs` asserts this file still carries the rule.
|
|
64
|
+
const RUNTIME_DIR = join(DATA_DIR, 'runtime'); // runtime-dir:stays-put — serves swap-in-progress only; HOOK_RUNTIME_DIR carries the hook markers
|
|
65
|
+
const HOOK_RUNTIME_DIR = process.env.CLAUDE_MEM_RUNTIME_DIR || RUNTIME_DIR;
|
|
66
|
+
const HEAL_MARKER = join(HOOK_RUNTIME_DIR, 'hook-launcher-lastheal');
|
|
44
67
|
const HEAL_COOLDOWN_MS = 6 * 60 * 60 * 1000;
|
|
45
68
|
// Observable breakage state: written when the launcher degrades a broken install
|
|
46
69
|
// to exit 0, cleared once the install is confirmed healthy. `doctor` reads it so
|
|
47
70
|
// the intentional silence (no stack trace per fire) stays detectable. (#4/#8)
|
|
48
|
-
const BROKEN_MARKER = join(
|
|
71
|
+
const BROKEN_MARKER = join(HOOK_RUNTIME_DIR, 'hook-launcher-broken');
|
|
49
72
|
|
|
50
73
|
// ── Native-binding (ABI) self-heal ──────────────────────────────────────────
|
|
51
74
|
// A stale better_sqlite3.node after a Node upgrade does NOT throw at import time
|
|
@@ -63,22 +86,9 @@ const BROKEN_MARKER = join(RUNTIME_DIR, 'hook-launcher-broken');
|
|
|
63
86
|
// lib/hook-telemetry.mjs and lib/native-binding-hint.mjs); this heals from it at
|
|
64
87
|
// SESSION-START only — never on the per-tool hot path, where an npm run would
|
|
65
88
|
// stall the user's edit.
|
|
66
|
-
// Marker dir
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// The last hand-written copy of this rule, and it stays: this launcher runs BEFORE the
|
|
70
|
-
// native binding is known to work, so it imports only `node:` builtins on purpose — even
|
|
71
|
-
// `lib/resolve-data-dir.mjs` is a module resolution it declines to make on the path whose
|
|
72
|
-
// job is to survive a broken install. `lib/resolve-data-dir.mjs::resolveRuntimeDir` is the
|
|
73
|
-
// canonical rule (audit 2026-09-02 P1-14). It is NOT identical to it, and saying so was
|
|
74
|
-
// wrong: the resolver additionally makes a RELATIVE override absolute (`isAbsolute(raw) ?
|
|
75
|
-
// raw : resolve(raw)`), while this expression hands the relative value straight to `fs`,
|
|
76
|
-
// which resolves it against cwd at call time instead of at module load. They agree on
|
|
77
|
-
// unset, on empty and on an absolute override — the three cases that reach a real install.
|
|
78
|
-
// Keep the DEFAULTING behaviour in step; do not read "identical" into the difference.
|
|
79
|
-
const NB_RUNTIME_DIR = process.env.CLAUDE_MEM_RUNTIME_DIR || RUNTIME_DIR;
|
|
80
|
-
const NB_BROKEN_MARKER = join(NB_RUNTIME_DIR, 'native-binding-broken');
|
|
81
|
-
const NB_HEAL_MARKER = join(NB_RUNTIME_DIR, 'native-binding-lastheal');
|
|
89
|
+
// Marker dir: HOOK_RUNTIME_DIR (see its definition above for why it is override-aware).
|
|
90
|
+
const NB_BROKEN_MARKER = join(HOOK_RUNTIME_DIR, 'native-binding-broken');
|
|
91
|
+
const NB_HEAL_MARKER = join(HOOK_RUNTIME_DIR, 'native-binding-lastheal');
|
|
82
92
|
// Literal, not imported: the pure-`node:` charter above forbids importing lib/
|
|
83
93
|
// here (this file must survive a broken install). Kept in sync with
|
|
84
94
|
// lib/binding-probe.mjs::NATIVE_BINDING_REBUILD_CMD, which is the single home
|
|
@@ -225,7 +235,7 @@ function recentHealAttempt() {
|
|
|
225
235
|
|
|
226
236
|
function recordHealAttempt() {
|
|
227
237
|
try {
|
|
228
|
-
mkdirSync(
|
|
238
|
+
mkdirSync(HOOK_RUNTIME_DIR, { recursive: true });
|
|
229
239
|
writeFileSync(HEAL_MARKER, String(Date.now()));
|
|
230
240
|
} catch { /* best-effort */ }
|
|
231
241
|
}
|
|
@@ -239,7 +249,7 @@ function clearHealMarker() {
|
|
|
239
249
|
|
|
240
250
|
function recordBreakage(reason) {
|
|
241
251
|
try {
|
|
242
|
-
mkdirSync(
|
|
252
|
+
mkdirSync(HOOK_RUNTIME_DIR, { recursive: true });
|
|
243
253
|
writeFileSync(BROKEN_MARKER, JSON.stringify({ reason, ts: Date.now() }));
|
|
244
254
|
} catch { /* best-effort */ }
|
|
245
255
|
}
|
|
@@ -316,7 +326,7 @@ function healNativeBindingIfBroken() {
|
|
|
316
326
|
if (Date.now() - statSync(NB_HEAL_MARKER).mtimeMs < HEAL_COOLDOWN_MS) return;
|
|
317
327
|
} catch { /* no marker → not on cooldown */ }
|
|
318
328
|
try {
|
|
319
|
-
mkdirSync(
|
|
329
|
+
mkdirSync(HOOK_RUNTIME_DIR, { recursive: true });
|
|
320
330
|
writeFileSync(NB_HEAL_MARKER, String(Date.now()));
|
|
321
331
|
} catch { /* best-effort */ }
|
|
322
332
|
|
package/scripts/launch.mjs
CHANGED
|
@@ -76,7 +76,7 @@ try {
|
|
|
76
76
|
// proceeds; a broken one defers to the peer instead of racing it).
|
|
77
77
|
const { acquireLock } = await import('../lib/proc-lock.mjs');
|
|
78
78
|
const { resolveDataDir } = await import('../lib/resolve-data-dir.mjs');
|
|
79
|
-
const lockPath = join(resolveDataDir(process.env.CLAUDE_MEM_DIR), 'runtime', 'install.lock');
|
|
79
|
+
const lockPath = join(resolveDataDir(process.env.CLAUDE_MEM_DIR), 'runtime', 'install.lock'); // runtime-dir:stays-put — install lock serialises real installers
|
|
80
80
|
let release = null;
|
|
81
81
|
for (let i = 0; i < 20 && !(release = acquireLock(lockPath)); i++) {
|
|
82
82
|
await new Promise((r) => setTimeout(r, 500));
|