Skip to content

Conversation

@divideby0
Copy link
Contributor

Summary

Fixes #461

CLI commands (tool --help, status, etc.) hang indefinitely after displaying output because database connections created during asyncio.run() are not cleaned up before the event loop closes.

Changes

  • src/basic_memory/services/initialization.py: Wrap initialize_app() with db.shutdown_db() cleanup in finally block
  • src/basic_memory/cli/commands/status.py: Add inline cleanup wrapper for run_status()
  • tests/cli/test_cli_tool_exit.py: Add regression tests using subprocess timeouts to detect hangs

Test plan

  • New regression tests verify commands exit within timeout
  • All 1376 existing tests pass
  • Lint and type checks pass

🤖 Generated with Claude Code

divideby0 and others added 3 commits December 19, 2025 15:42
Add tests to verify CLI commands (tool, status) exit cleanly without hanging.

The tests reproduce a bug where CLI commands hang indefinitely after
displaying output. The root cause is that ensure_initialization() creates
database connections via asyncio.run() that are not cleaned up before the
event loop closes.

These tests will fail until the fix is applied.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: Cedric Hurst <cedric@spantree.net>
Fix issue where CLI commands like `basic-memory tool --help` and
`basic-memory status` hang indefinitely after displaying output.

Root cause:
- ensure_initialization() and CLI commands create database connections
  via asyncio.run()
- When asyncio.run() completes, the event loop closes
- But global database engine holds async connections preventing exit
- Process hangs indefinitely

Fix:
- ensure_initialization: Wrap initialize_app() with db.shutdown_db()
  cleanup in finally block before asyncio.run() returns
- status command: Add inline cleanup wrapper to close database
  connections after run_status() completes

Test:
- Add regression tests that verify CLI commands exit cleanly
- Tests use subprocess with timeout to detect hangs
- Verifies both `tool` subcommands and `status` command

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: Cedric Hurst <cedric@spantree.net>
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ divideby0
❌ feniix
You have signed the CLA already but the status is still pending? Let us recheck it.

phernandez added a commit that referenced this pull request Dec 23, 2025
This fixes CLI commands (status, project, tools) hanging indefinitely
after completing their work. The root causes were:

1. Database connections created during initialization weren't cleaned up
   before the event loop closed, leaving orphaned connections

2. Multiple calls to asyncio.run() created separate event loops that
   didn't properly coordinate database connection cleanup

Changes:

- Make migration f8a9b2c3d4e5 idempotent by checking if columns/indexes
  exist before adding them (prevents failures on partial migrations)

- Add lifespan to MCP server using FastMCP's context manager, replacing
  the separate thread for file sync

- Add db.shutdown_db() cleanup to ensure_initialization() to properly
  clean up connections before the event loop closes

- Skip ensure_initialization for API-using commands (status, project,
  tools, sync) since they get database connections through deps.py

- Add run_with_cleanup() helper for CLI commands that handles proper
  database cleanup after coroutine completion

- Remove ensure_migrations=False parameters (redundant since engine is
  reused within a single event loop)

- Add regression tests in tests/cli/test_cli_exit.py to verify CLI
  commands exit cleanly within timeout

Closes #462

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
@phernandez
Copy link
Member

Thank you for identifying this issue! We've incorporated the fix as part of a larger database initialization refactor in #471 that includes:

  1. Idempotent migration - The f8a9b2c3d4e5 migration now checks if columns exist before adding them
  2. MCP server lifespan - Replaced the separate thread for file sync with FastMCP's lifespan context manager
  3. Database cleanup - Added db.shutdown_db() to ensure_initialization()
  4. Skip redundant initialization - API-using CLI commands skip ensure_initialization since deps.py handles connections
  5. Regression tests - Added tests/cli/test_cli_exit.py

Unfortunately we couldn't merge this PR directly due to CLA requirements on the merge commit, so we re-implemented the core fixes. Your contribution was instrumental in identifying the root cause.

Closing in favor of #471.

@phernandez phernandez closed this Dec 23, 2025
phernandez added a commit that referenced this pull request Dec 23, 2025
This fixes CLI commands (status, project, tools) hanging indefinitely
after completing their work. The root causes were:

1. Database connections created during initialization weren't cleaned up
   before the event loop closed, leaving orphaned connections

2. Multiple calls to asyncio.run() created separate event loops that
   didn't properly coordinate database connection cleanup

Changes:

- Make migration f8a9b2c3d4e5 idempotent by checking if columns/indexes
  exist before adding them (prevents failures on partial migrations)

- Add lifespan to MCP server using FastMCP's context manager, replacing
  the separate thread for file sync

- Add db.shutdown_db() cleanup to ensure_initialization() to properly
  clean up connections before the event loop closes

- Skip ensure_initialization for API-using commands (status, project,
  tools, sync) since they get database connections through deps.py

- Add run_with_cleanup() helper for CLI commands that handles proper
  database cleanup after coroutine completion

- Remove ensure_migrations=False parameters (redundant since engine is
  reused within a single event loop)

- Add regression tests in tests/cli/test_cli_exit.py to verify CLI
  commands exit cleanly within timeout

Closes #462

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI tool commands hang after displaying output

4 participants