Status 2026-10-02. PR #1584 (approved, awaiting a signed commit) removes the unguarded duplicate of _cleanup_semantic_history so the guarded definition takes effect; that covers the no-database configuration only. The complete fix is the call-site skip in MemMachine plus the refusal in the composition root, as corrected on #1600 (not a gate in the semantic getters). Tracked under #1755.
What happened
With semantic memory disabled in the server config, deleting a project never completes: the session row stays in delete status forever, the project cannot be recreated (POST /projects returns 409), and a POST /memories on it raises SessionDeletedError, which is unhandled on that route and reaches the client as a dropped connection.
Config that reproduces it (event backend on SQLite, everything else default):
semantic_memory:
enabled: false
config_database: profile_storage
Flow: create a project, add a few episodic memories (types: ["episodic"]), POST /projects/delete (returns 204), then POST /projects with the same ids. Server log:
[ERROR] memmachine_server.main.memmachine - Failed to delete session manual_org/proj_a
...
File ".../main/memmachine.py", line 383, in _delete_session_episode_store
await self._cleanup_semantic_history(episode_ids)
File ".../main/memmachine.py", line 1178, in _cleanup_semantic_history
semantic_service = await self._resources.get_semantic_service()
...
memmachine_server.common.errors.ResourceNotReadyError: No database configured for semantic storage.
Line numbers as of 231ce171 (main): _delete_queued_session at packages/server/src/memmachine_server/main/memmachine.py:357 gates the semantic delete on self._conf.semantic_memory.enabled (line 361), but _delete_session_episode_store (line 369) calls _cleanup_semantic_history unconditionally (line 383), and that method (line 1168) requests the semantic service unconditionally (line 1177). With semantic memory disabled there is no semantic database, so SemanticManager.get_semantic_storage (common/resource_manager/semantic_manager.py:89) raises. The worker (_delete_session_worker, line 342) logs the exception and drops the job; nothing retries it. Because delete_session (line 582) returns early once the status is Deleted, a second delete request is a no-op, so the only way out is a server restart, which re-queues deleted sessions at boot (start, line 411) and fails the same way.
Note the partial state left behind: the deletion gathers the episode-store delete, the episodic-memory delete and (when enabled) the semantic delete concurrently, so by the time the episode-store branch raises, the episodic branch may already have dropped the vector collection and segment partition. The project is then neither deleted nor intact.
Expected
A deployment with semantic memory disabled must be able to delete projects. _cleanup_semantic_history should be skipped (or be a no-op) when semantic_memory.enabled is false, mirroring the guard _delete_queued_session already applies to the semantic delete itself. Independently, a failed deletion should not leave a session in a state that no API call can revisit.
Notes
Found by a manual end-to-end run against the server with the event backend on SQLite and again on PostgreSQL; the failure is dialect-independent. The same run also showed that a search on a project in delete status is still served from the process-local instance cache with 200 responses.
Investigated and written by Claude (Claude Code), filed from the account of the user who commissioned the investigation.
Status 2026-10-02. PR #1584 (approved, awaiting a signed commit) removes the unguarded duplicate of
_cleanup_semantic_historyso the guarded definition takes effect; that covers the no-database configuration only. The complete fix is the call-site skip inMemMachineplus the refusal in the composition root, as corrected on #1600 (not a gate in the semantic getters). Tracked under #1755.What happened
With semantic memory disabled in the server config, deleting a project never completes: the session row stays in
deletestatus forever, the project cannot be recreated (POST /projectsreturns 409), and aPOST /memorieson it raisesSessionDeletedError, which is unhandled on that route and reaches the client as a dropped connection.Config that reproduces it (event backend on SQLite, everything else default):
Flow: create a project, add a few episodic memories (
types: ["episodic"]),POST /projects/delete(returns 204), thenPOST /projectswith the same ids. Server log:Line numbers as of
231ce171(main):_delete_queued_sessionatpackages/server/src/memmachine_server/main/memmachine.py:357gates the semantic delete onself._conf.semantic_memory.enabled(line 361), but_delete_session_episode_store(line 369) calls_cleanup_semantic_historyunconditionally (line 383), and that method (line 1168) requests the semantic service unconditionally (line 1177). With semantic memory disabled there is no semantic database, soSemanticManager.get_semantic_storage(common/resource_manager/semantic_manager.py:89) raises. The worker (_delete_session_worker, line 342) logs the exception and drops the job; nothing retries it. Becausedelete_session(line 582) returns early once the status isDeleted, a second delete request is a no-op, so the only way out is a server restart, which re-queues deleted sessions at boot (start, line 411) and fails the same way.Note the partial state left behind: the deletion gathers the episode-store delete, the episodic-memory delete and (when enabled) the semantic delete concurrently, so by the time the episode-store branch raises, the episodic branch may already have dropped the vector collection and segment partition. The project is then neither deleted nor intact.
Expected
A deployment with semantic memory disabled must be able to delete projects.
_cleanup_semantic_historyshould be skipped (or be a no-op) whensemantic_memory.enabledis false, mirroring the guard_delete_queued_sessionalready applies to the semantic delete itself. Independently, a failed deletion should not leave a session in a state that no API call can revisit.Notes
Found by a manual end-to-end run against the server with the event backend on SQLite and again on PostgreSQL; the failure is dialect-independent. The same run also showed that a search on a project in
deletestatus is still served from the process-local instance cache with 200 responses.Investigated and written by Claude (Claude Code), filed from the account of the user who commissioned the investigation.