Summary
The daily auto-backup has failed on every execution since upgrading to OpenClaw v2026.7.1-2. The .. path traversal used to derive the backup directory returns undefined from api.resolvePath(), and the subsequent mkdir() throws. The failure is caught and logged at warn level, so backups stop silently — ours went unnoticed for four and a half months while the store grew from 60 to 137 memories.
Environment
|
|
| Plugin |
memory-lancedb-pro@1.1.0-beta.9 (current beta dist-tag) |
| OpenClaw |
2026.7.1-2 (0790d9f) |
| Node |
24.19.0 |
| OS |
macOS 26.5.1 (arm64) |
| Store |
137 memories, LanceDB at ~/.openclaw/memory/lancedb-pro |
| Install |
external / global (~/.openclaw/extensions/) |
Observed
[plugins] memory-lancedb-pro: backup failed: TypeError [ERR_INVALID_ARG_TYPE]:
The "path" argument must be of type string or an instance of Buffer or URL. Received undefined
Fires ~60s after every gateway start (index.ts:3144), then every 24h. Last backup the plugin successfully wrote: 2026-04-07, immediately before the OpenClaw upgrade.
Expected
A dated memory-backup-YYYY-MM-DD.jsonl in <dbPath>/../backups/, as it did before OpenClaw 2026.7.
Root cause
index.ts:3011:
const backupDir = api.resolvePath(
join(resolvedDbPath, "..", "backups"),
);
await mkdir(backupDir, { recursive: true }); // ← throws: backupDir is undefined
The undefined comes from api.resolvePath(), not from join(). Node distinguishes the two by one clause:
path.join(undefined, …) → The "path" argument must be of type string. Received undefined
fs.mkdir(undefined) → The "path" argument must be of type string **or an instance of Buffer or URL**. Received undefined
The logged message contains the Buffer/URL clause, so join() returned a valid string and api.resolvePath() returned undefined.
This is the only api.resolvePath() call in the plugin whose argument contains a .. segment. The other two — config.dbPath (index.ts:1594) and config.mdMirror.dir (index.ts:1524) — pass non-traversing paths and work correctly; the store reads and writes normally throughout, so this is specific to the traversal.
OpenClaw's own path helpers in dist reject upward traversal by returning undefined rather than throwing:
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) return;
This strongly suggests the 2026.7 plugin sandbox applies the same rule to api.resolvePath, making any ..-based derivation silently return undefined.
Suggested fix
Derive the sibling directory without traversing upward. dirname is already imported at index.ts:8, so this needs no new import:
- const backupDir = api.resolvePath(
- join(resolvedDbPath, "..", "backups"),
- );
+ const backupDir = api.resolvePath(
+ join(dirname(resolvedDbPath), "backups"),
+ );
Two secondary suggestions
-
Backup failure shouldn't be warn-only and silent. A backup subsystem failing every run for months without escalating is arguably the more serious half of this bug. Consider escalating after N consecutive failures, or exposing a last-successful-backup timestamp in memory-pro stats so it's visible without log archaeology.
-
Guard the resolvePath return value. Returning undefined rather than throwing means any future sandbox tightening produces this same class of silent breakage elsewhere in the plugin. An explicit null check with a clear error message would turn a multi-step investigation into a five-minute fix.
Workaround for other users
The export CLI doesn't go through the broken path and produces an equivalent artifact:
openclaw memory-pro export --output ~/.openclaw/memory/backups/memory-backup-$(date +%F).json
Omit --scope. --scope global exported only 18 of our 137 memories while still reporting success — verify the resulting record count against openclaw memory-pro stats rather than trusting the CLI's success line.
Summary
The daily auto-backup has failed on every execution since upgrading to OpenClaw v2026.7.1-2. The
..path traversal used to derive the backup directory returnsundefinedfromapi.resolvePath(), and the subsequentmkdir()throws. The failure is caught and logged atwarnlevel, so backups stop silently — ours went unnoticed for four and a half months while the store grew from 60 to 137 memories.Environment
memory-lancedb-pro@1.1.0-beta.9(currentbetadist-tag)~/.openclaw/memory/lancedb-pro~/.openclaw/extensions/)Observed
Fires ~60s after every gateway start (
index.ts:3144), then every 24h. Last backup the plugin successfully wrote: 2026-04-07, immediately before the OpenClaw upgrade.Expected
A dated
memory-backup-YYYY-MM-DD.jsonlin<dbPath>/../backups/, as it did before OpenClaw 2026.7.Root cause
index.ts:3011:The
undefinedcomes fromapi.resolvePath(), not fromjoin(). Node distinguishes the two by one clause:path.join(undefined, …)→The "path" argument must be of type string. Received undefinedfs.mkdir(undefined)→The "path" argument must be of type string **or an instance of Buffer or URL**. Received undefinedThe logged message contains the Buffer/URL clause, so
join()returned a valid string andapi.resolvePath()returnedundefined.This is the only
api.resolvePath()call in the plugin whose argument contains a..segment. The other two —config.dbPath(index.ts:1594) andconfig.mdMirror.dir(index.ts:1524) — pass non-traversing paths and work correctly; the store reads and writes normally throughout, so this is specific to the traversal.OpenClaw's own path helpers in
distreject upward traversal by returningundefinedrather than throwing:This strongly suggests the 2026.7 plugin sandbox applies the same rule to
api.resolvePath, making any..-based derivation silently returnundefined.Suggested fix
Derive the sibling directory without traversing upward.
dirnameis already imported atindex.ts:8, so this needs no new import:Two secondary suggestions
Backup failure shouldn't be
warn-only and silent. A backup subsystem failing every run for months without escalating is arguably the more serious half of this bug. Consider escalating after N consecutive failures, or exposing a last-successful-backup timestamp inmemory-pro statsso it's visible without log archaeology.Guard the
resolvePathreturn value. Returningundefinedrather than throwing means any future sandbox tightening produces this same class of silent breakage elsewhere in the plugin. An explicit null check with a clear error message would turn a multi-step investigation into a five-minute fix.Workaround for other users
The export CLI doesn't go through the broken path and produces an equivalent artifact:
Omit
--scope.--scope globalexported only 18 of our 137 memories while still reporting success — verify the resulting record count againstopenclaw memory-pro statsrather than trusting the CLI's success line.