Bug Description
After the proactive stale lock cleanup (introduced in PR #626 for issues #622/#623) clears a stale .memory-write.lock file, subsequent proper-lockfile calls fail with:
ENOENT: no such file or directory, lstat '/Users/arthurliang/.openclaw/memory/lancedb-pro/.memory-write.lock'
Root Cause
The proactive cleanup (store.ts lines 221-230) unlinks the stale lock file:
if (existsSync(lockPath)) {
try {
const stat = statSync(lockPath);
const ageMs = Date.now() - stat.mtimeMs;
const staleThresholdMs = 5 * 60 * 1000;
if (ageMs > staleThresholdMs) {
try { unlinkSync(lockPath); } catch {}
console.warn(`[memory-lancedb-pro] cleared stale lock: ${lockPath} ageMs=${ageMs}`);
}
} catch {}
}
This runs before lockfile.lock():
const release = await lockfile.lock(lockPath, {
retries: { retries: 10, factor: 2, minTimeout: 200, maxTimeout: 5000 },
stale: 10000,
});
In proper-lockfile/lib/lockfile.js, the resolveCanonicalPath function calls options.fs.realpath(file) on the lock target path (line 22). The default for realpath is true in proper-lockfile v4. When the lock file was just deleted by proactive cleanup, realpath() throws ENOENT because the target file does not exist.
Relevant proper-lockfile code (lockfile.js lines 15-22):
function resolveCanonicalPath(file, options, callback) {
if (!options.realpath) {
return callback(null, path.resolve(file));
}
// Use realpath to resolve symlinks
// It also resolves relative paths
options.fs.realpath(file, callback);
}
Observed Timeline
| Time |
Event |
| 07:35:17 |
smart-extractor acquired lock, wrote memories, process terminated without releasing |
| 08:35:43.181 |
Proactive cleanup detected stale lock (ageMs=3635657 ≈ 60 min), deleted .memory-write.lock |
| 08:35:43.184 |
3ms later: proper-lockfile.realpath() on deleted file → ENOENT → smart-extractor failed |
Environment
- memory-lancedb-pro: 1.1.0-beta.10
- OS: macOS 15.6 (arm64)
- OpenClaw gateway (agent main, multi-session)
Proposed Fix
Two complementary fixes:
Fix 1: Pass realpath: false to proper-lockfile
Since the lock path is always an absolute path to a known location, symlink resolution is unnecessary:
const release = await lockfile.lock(lockPath, {
realpath: false,
retries: { retries: 10, factor: 2, minTimeout: 200, maxTimeout: 5000 },
stale: 10000,
});
Fix 2: Recreate lock file after proactive cleanup
After unlinking the stale lock file, recreate it so proper-lockfile has a valid target:
if (ageMs > staleThresholdMs) {
try { unlinkSync(lockPath); } catch {}
// Recreate the target file so proper-lockfile can lock it
try { writeFileSync(lockPath, "", { flag: "wx" }); } catch {}
console.warn(`[memory-lancedb-pro] cleared stale lock: ${lockPath} ageMs=${ageMs}`);
}
Recommended: Both fixes together
Fix 1 prevents the ENOENT from proper-lockfile. Fix 2 ensures the lock target file exists even if other code paths depend on it. Together they provide defense in depth.
Reproduction Steps
- Start a session with memory-lancedb-pro
- Trigger smart-extractor to write memories and acquire the file lock
- Kill the process (or let session reset) without releasing the lock
- Wait >5 minutes (stale threshold)
- Trigger another smart-extractor run
- Observe: stale lock cleanup runs, deletes lock file, next lock attempt fails with ENOENT
Related Issues
Bug Description
After the proactive stale lock cleanup (introduced in PR #626 for issues #622/#623) clears a stale
.memory-write.lockfile, subsequentproper-lockfilecalls fail with:Root Cause
The proactive cleanup (store.ts lines 221-230) unlinks the stale lock file:
This runs before
lockfile.lock():In
proper-lockfile/lib/lockfile.js, theresolveCanonicalPathfunction callsoptions.fs.realpath(file)on the lock target path (line 22). The default forrealpathistruein proper-lockfile v4. When the lock file was just deleted by proactive cleanup,realpath()throws ENOENT because the target file does not exist.Relevant proper-lockfile code (lockfile.js lines 15-22):
Observed Timeline
.memory-write.lockproper-lockfile.realpath()on deleted file → ENOENT → smart-extractor failedEnvironment
Proposed Fix
Two complementary fixes:
Fix 1: Pass
realpath: falseto proper-lockfileSince the lock path is always an absolute path to a known location, symlink resolution is unnecessary:
Fix 2: Recreate lock file after proactive cleanup
After unlinking the stale lock file, recreate it so proper-lockfile has a valid target:
Recommended: Both fixes together
Fix 1 prevents the ENOENT from proper-lockfile. Fix 2 ensures the lock target file exists even if other code paths depend on it. Together they provide defense in depth.
Reproduction Steps
Related Issues