fix: Stale lock recovery and MCP init retry

Fixes #47 — "database is locked" after crash and MCP "not initialized"
when project IS initialized.

- FileLock: treat locks older than 10 minutes as stale regardless of PID
  status, covering cases where PID was reused or kill signal check fails
- MCP server: log errors from tryInitializeDefault() to stderr instead of
  silently swallowing, so transient open failures are diagnosable
- MCP server: retryInitIfNeeded() properly cleans up failed instances
  before retrying, preventing resource leaks
- CLI: add 'codegraph unlock' command for manual lock file removal
This commit is contained in:
Colby McHenry
2026-03-18 14:50:25 -05:00
parent bdbe59b457
commit b964d5909a
3 changed files with 51 additions and 4 deletions
+10
View File
@@ -120,13 +120,18 @@ export class MCPServer {
this.cg = await CodeGraph.open(resolvedRoot);
this.toolHandler.setDefaultCodeGraph(this.cg);
} catch (err) {
// Log the error so transient failures are diagnosable (see issue #47)
captureException(err);
const msg = err instanceof Error ? err.message : String(err);
process.stderr.write(`[CodeGraph MCP] Failed to open project at ${resolvedRoot}: ${msg}\n`);
}
}
/**
* Retry initialization of the default project if it previously failed.
* Called lazily on tool calls that need the default project.
* Re-walks parent directories each time so it picks up projects
* initialized after the MCP server started.
*/
private retryInitIfNeeded(): void {
// Already initialized successfully
@@ -138,6 +143,11 @@ export class MCPServer {
if (!resolvedRoot) return;
try {
// Close any previously failed instance to avoid leaking resources
if (this.cg) {
try { this.cg.close(); } catch { /* ignore */ }
this.cg = null;
}
this.cg = CodeGraph.openSync(resolvedRoot);
this.projectPath = resolvedRoot;
this.toolHandler.setDefaultCodeGraph(this.cg);