Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ All notable changes to MeMesh are documented here.
- Importing with `overwrite` (MCP `import`, `POST /v1/import`, `memesh import --merge overwrite`) now keeps the memory's previous observations, tags and title in its `replaced_history`, the same way `remember` with `replace: true` does. Before, the old content was erased with no way back. Importing a file that names one memory more than once is refused before anything is written (#530).
- A message sent through the MCP `message` tool keeps every `null` in its JSON payload. Before, the MCP boundary removed each null-valued key at any depth of the payload while `send` still reported success, so the recipient got different data than the CLI or HTTP would have stored (#517). A null-valued top-level tool parameter still means "left blank", except `payload`, whose null is a value (#553).
- The MCP `message` tool accepts `payload: null` on `send`, as its schema and the HTTP API already did; it was refused with `payload: Invalid input` (#553).
- Opening a database you made read-only (for example `chmod 444` on a snapshot or backup) no longer makes it writable. memesh used to set the file to `600` on every open, which gave the owner back the write permission, so every later hook, CLI or MCP process wrote into it (#520). memesh now only removes other users' access, on the database, its folder (for a symlinked database path, the folder of the link; the real database's folder is checked but never changed) and its `-wal` and `-shm` files, also when the open fails, and leaves the owner's permissions on the database as you set them. It never adds an owner permission to the database, its `-wal`/`-shm`, its folder or the remote token either: a read-only database opens for reads, writes to it are refused, and memesh prints the `chmod u+w` command naming every file that needs it (the database and the `-wal`/`-shm` a read-only open leaves behind). A `-wal` or `-shm` with fewer owner permissions than the database (for example after only the database was made writable again), or an empty one with more, is refused before the open with the `chmod` commands for those files, because SQLite would otherwise change its permissions or open the database silently read-only. This check runs on every open of the database by memesh: the CLI, MCP and HTTP servers, every hook (read-only opens included), `npm run audit:memory` and the other audit scripts in `scripts/audit/` that read the database; a symlinked database path is checked where SQLite keeps those files, beside the real database. `memesh doctor` shows a read-only database as read-only in its Database row, with the command, and when memesh refused to open the database for a permission reason, its Hook activity and Capture liveness rows point to that command instead of suggesting a full disk. A database in a read-only folder (for a symlinked path, the real database's folder) opens for reading when its `-wal` and `-shm` are already there, and writes to it are refused; when one of them is missing, opening fails with an error that names the folder and what to do, instead of making anything writable. `memesh doctor --fix` no longer sets the database to `600`: doctor tells you the `chmod` command for your case and leaves the decision to you, and it no longer suggests moving the database away when the problem is a permission. No part of memesh sets the data folder to `700` any more (the hooks, `memesh serve --host` with a remote token, saving settings, the install id, the agent router and the Codex session companion); a new folder is still created `700`. An existing remote token file is only read, so a `0400` token stays `0400`; a new one is created `0600`. The router and the Codex session companion need to write into the data folder, so on a read-only one they stop with the reason. When other users' access cannot be removed (the file system refuses the change), memesh prints a warning with the `chmod` command to run instead of continuing silently. When the file or folder belongs to another user, so a `chmod` is not yours to run, that warning, the read-only database message and the read-only folder error say instead to point `MEMESH_DB_PATH` at a database you own, in a folder you own. Every command memesh prints for you to paste (these `chmod` commands, doctor's `mv`, `rm` and `mkdir` advice, `memesh export`'s `mkdir -p` and `--limit` hints, the `memesh recall` and `memesh remember` hints (the memory name and type, passed as `--name=…`/`--type=…` or after `--`, so a value that starts with `-` reaches memesh unchanged), doctor's `CLAUDE_PLUGIN_ROOT=… memesh doctor` hint, `memesh agent setup`'s launch and registration commands, and `upgrade-plugin`'s recovery commands) now puts file paths and memory names in single quotes, so a name that contains `$(...)`, a backtick, `;` or a quote can no longer run anything when you paste the command. When `memesh serve` cannot open the database it now prints the same diagnosis and fix as `memesh doctor`'s Database row, so a permission problem gets its `chmod`, not the advice to move the database away. Doctor's Database row now also says when the database's "folder" is a file, asks for `chmod u+wx` when the folder has lost its search permission (the database inside then looks missing), and says when another process has the database locked instead of suggesting to move it away; its Hook activity and Capture liveness rows say the same instead of pointing at the disk.
- `memesh kg rename-project --from X --to X` (the same name twice) is refused. Before, `--apply` counted every memory of X as a merge and removed its only project tag (#519).
- `kg rename-project --apply` writes its backup to `backups/` beside the database instead of `./data/backups/` under whatever directory it was run from, and the backup now includes changes still in the database's write-ahead log; the printed restore command uses `sqlite3 .restore` and is quoted for any path. The dry run prints how many message rows would be left in place, and an apply that cannot write no longer reports every row as left in place. Listing and the dry run open the database read-only, so they no longer trigger the automatic confidence decay or any other write; the counts they print now match `--apply`, including when the database still needs a unique index that `--apply` creates, and the "moved" count no longer includes the rows left in place. Giving only one of `--from`/`--to`, an invalid `--to`, or the same name twice prints one error line and exits 1 before the database is opened, even with `--apply`. Listing and the dry run also no longer create a database: with none present, listing prints "No MeMesh database yet" (exit 0, `[]` with `--json`), and a dry run with `--from`/`--to` exits 1 saying there is no database at that path. Before, both created an empty database and printed "No project:* tags found." `--apply` with neither `--from` nor `--to` is refused instead of listing through a writable open. The dry run now runs the real rename on a temporary copy of the database instead of estimating collisions, so its counts match `--apply` for any index, trigger or pending migration the database has; and `--apply` reads back, inside its transaction, every renamed tag (the old tag gone, the new one present) and every moved message row (scoped to the new project), and rolls back with one error line if any is not where it should be, for example because a trigger ignored or undid the change. Before, the memory could keep `project:old` while its messages moved. A message row counts as left in place only when the destination project really holds a row with the same unique key; any other failure, including an error a trigger raises or a collision a trigger's own write hits, rolls the rename back instead of being reported as a success. The dry run's temporary copy is deleted when it finishes; an interrupted preview (Ctrl-C, kill) can leave it in the temp folder, owner-only. A database whose `-wal` file is empty can now be listed and previewed from a read-only directory, and a preview that cannot write its temporary copy names the temp folder and the cause. On a read-only directory, listing and the dry run read the database as immutable when no write-ahead log, or only an empty one, is present (a non-empty log next to the real file, also through a symlink, is refused with one line), and any failure while listing is one error line, not a stack trace.
- `kg rename-project --apply` writes its backup to `backups/` beside the database instead of `./data/backups/` under whatever directory it was run from, and the backup now includes changes still in the database's write-ahead log; the printed restore command uses `sqlite3 .restore` and is quoted for any path. The dry run prints how many message rows would be left in place, and an apply that cannot write no longer reports every row as left in place. Listing and the dry run open the database read-only, so they no longer trigger the automatic confidence decay or any other write to the data (like every open of the database, they still take group and other access off its files and refuse a `-wal`/`-shm` whose owner permissions opening would change, #520); the counts they print now match `--apply`, including when the database still needs a unique index that `--apply` creates, and the "moved" count no longer includes the rows left in place. Giving only one of `--from`/`--to`, an invalid `--to`, or the same name twice prints one error line and exits 1 before the database is opened, even with `--apply`. Listing and the dry run also no longer create a database: with none present, listing prints "No MeMesh database yet" (exit 0, `[]` with `--json`), and a dry run with `--from`/`--to` exits 1 saying there is no database at that path. Before, both created an empty database and printed "No project:* tags found." `--apply` with neither `--from` nor `--to` is refused instead of listing through a writable open. The dry run now runs the real rename on a temporary copy of the database instead of estimating collisions, so its counts match `--apply` for any index, trigger or pending migration the database has; and `--apply` reads back, inside its transaction, every renamed tag (the old tag gone, the new one present) and every moved message row (scoped to the new project), and rolls back with one error line if any is not where it should be, for example because a trigger ignored or undid the change. Before, the memory could keep `project:old` while its messages moved. A message row counts as left in place only when the destination project really holds a row with the same unique key; any other failure, including an error a trigger raises or a collision a trigger's own write hits, rolls the rename back instead of being reported as a success. The dry run's temporary copy is deleted when it finishes; an interrupted preview (Ctrl-C, kill) can leave it in the temp folder, owner-only. A database whose `-wal` file is empty can now be listed and previewed from a read-only directory, and a preview that cannot write its temporary copy names the temp folder and the cause. On a read-only directory, listing and the dry run read the database as immutable when no write-ahead log, or only an empty one, is present (a non-empty log next to the real file, also through a symlink, is refused with one line), and any failure while listing is one error line, not a stack trace.

## [4.10.11] — 2026-09-29

Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/DoctorBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface DoctorCheck {
fix?: string;
code?: string;
params?: Record<string, string | number>;
fixId?: 'install-hooks' | 'fts-rebuild' | 'chmod-db' | 'config-retired-settings' | 'plugin-cache-refresh';
fixId?: 'install-hooks' | 'fts-rebuild' | 'config-retired-settings' | 'plugin-cache-refresh';
}
interface DoctorResult { status: string; checks: DoctorCheck[] }

Expand Down
2 changes: 1 addition & 1 deletion dist/core/config.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 3 additions & 10 deletions dist/core/config.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading