Skip to content

fix(mcp): treat missing memory/sessions tables as empty memory#255

Merged
efenocchi merged 3 commits into
mainfrom
fix/mcp-fresh-org-missing-tables
Jun 11, 2026
Merged

fix(mcp): treat missing memory/sessions tables as empty memory#255
efenocchi merged 3 commits into
mainfrom
fix/mcp-fresh-org-missing-tables

Conversation

@efenocchi

@efenocchi efenocchi commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #252.

On a brand-new org no agent session has run yet, so the memory/sessions tables don't exist — provisioning happens in the per-agent SessionStart hooks (src/hooks/session-start.ts and the hermes/cursor/codex variants), not at install time. The MCP server queries the tables directly, so hivemind_search / hivemind_read / hivemind_index surfaced the raw backend 400:

Index failed: Query failed: 400: {"error":"Table does not exist: relation \"memory\" does not exist","code":"INVALID_REQUEST",...}

Fix

In the three MCP tool handlers, classify the caught error with isMissingTableError() (the same helper commands/rules.ts and skillify/pull.ts already use for this exact situation) and return the friendly empty result the zero-rows path produces, plus a hint:

No summaries found. Hivemind memory is empty — tables are created when the first agent session starts, and entries appear after it ends.

Intentionally no DDL from the MCP server: it is a read-only path, a READ-role org member couldn't CREATE TABLE anyway, and createTableWithRetry could block a tool call for ~17s. Tables keep being provisioned by the SessionStart hooks.

Answers to the questions in #252

  • The per-org tables are auto-provisioned — on the first agent session start, not on install or first write. No manual backend step needed.
  • The feared session-end write failure cannot happen: the capture hooks call ensureSessionsTable before writing.
  • The only broken path was MCP-before-first-session, fixed here.

Verification

  • Reproduced live against api.deeplake.ai by running the built MCP server over raw stdio JSON-RPC (same probe as the issue) pointed at nonexistent tables: both tools returned the exact 400 from the issue. After the fix, the same probe returns the clean empty-memory message.
  • Also exercised the Hermes pre_tool_call grep fallback from the built bundle: on missing tables it logs fast-path failed, falling through and stays silent (unchanged by this PR, by design).
  • 5 new tests in tests/claude-code/mcp-server.test.ts using the exact backend error string (all three tools, bare-postgres wording variant, and a negative test that a missing column still surfaces raw). Full suite: 227 files / 4419 tests pass.

Session Context

Session transcript

Summary by CodeRabbit

  • Bug Fixes

    • Improved error messaging for fresh organizations: search/read/index now return friendly "no matches/content/summaries" messages with a hint that memory/session tables are created during the first agent session, instead of surfacing backend table errors.
    • Render empty content correctly when stored value is SQL NULL (avoids showing the literal "null").
  • Tests

    • Added tests verifying fresh-org messaging behavior and that other errors continue to surface normally.

On a fresh org no session has run yet, so the memory/sessions tables
don't exist (provisioning lives in the per-agent SessionStart hooks).
The MCP tools hivemind_search / hivemind_read / hivemind_index surfaced
the raw backend 400 ("relation \"memory\" does not exist") instead of
a clean empty result.

Classify the caught error with isMissingTableError and return the same
friendly empty-memory message the zero-rows path produces, plus a hint
that tables are created when the first agent session starts. No DDL
from the MCP server: it is a read-only path and a READ-role member
could not CREATE TABLE anyway.

Fixes #252
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e0531c30-caf3-4ea4-b150-a148dacefe82

📥 Commits

Reviewing files that changed from the base of the PR and between 7a30924 and 5c09174.

📒 Files selected for processing (1)
  • tests/claude-code/mcp-server.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/claude-code/mcp-server.test.ts

📝 Walkthrough

Walkthrough

MCP handlers now detect backend "missing table" errors and return empty-result messages with a hint about tables being created during the first agent session. Changes apply to hivemind_search, hivemind_read, and hivemind_index and include tests for null content and fresh-org classification.

Changes

Fresh-org missing-table detection

Layer / File(s) Summary
Fresh-org detection in MCP tool handlers
src/mcp/server.ts
Import isMissingTableError, add FRESH_ORG_HINT explaining that memory/sessions tables are created during the first agent session, and update error handling in hivemind_search, hivemind_read, and hivemind_index to return empty-result responses with the hint instead of exposing raw backend errors.
Fresh-org test coverage
tests/claude-code/mcp-server.test.ts
Add Vitest suite that stubs Deeplake failures and asserts that index/search/read return the empty-result + hint for missing-table or Postgres-style missing relation errors, while missing-column errors still surface as failure messages; also add a hivemind_read test ensuring SQL NULL content renders as an empty string.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • kaghni

Poem

🐰
I nibble bugs beneath the moon,
Tables sprout by morning soon.
First session writes will do their part,
Quiet hope from fluffy heart.
Hooray — the handlers sing, "Start!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: treating missing memory/sessions tables as empty memory rather than returning errors.
Description check ✅ Passed The description is comprehensive and includes a clear summary of the fix, version bump note, and test plan section with all required checkboxes present.
Linked Issues check ✅ Passed The PR fully addresses issue #252 by implementing error classification for missing-table errors in all three MCP tool handlers (hivemind_index, hivemind_search, hivemind_read) and returning user-friendly messages instead of raw backend errors.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing issue #252: modifications to src/mcp/server.ts for error handling and test coverage additions in tests/claude-code/mcp-server.test.ts. No unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/mcp-fresh-org-missing-tables

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Scope: files changed in this PR. Enforced threshold: 90% per metric (per file via vitest.config.ts).

Status Category Percentage Covered / Total
🟢 Lines 97.01% (🎯 90%) 65 / 67
🟢 Statements 96.10% (🎯 90%) 74 / 77
🟢 Functions 90.00% (🎯 90%) 9 / 10
🟢 Branches 94.23% (🎯 90%) 49 / 52
File Coverage — 1 file changed
File Stmts Branches Functions Lines
src/mcp/server.ts 🟢 96.1% 🟢 94.2% 🟢 90.0% 🟢 97.0%

Generated for commit f244634.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
tests/claude-code/mcp-server.test.ts (4)

303-308: ⚡ Quick win

Assert the complete expected message including the hint.

This test only verifies "No summaries found." is present, but doesn't verify the fresh-org hint is included. This is inconsistent with the other tests in this suite and provides incomplete coverage.

♻️ Proposed fix to assert the exact expected message
-    expect(out.content[0].text).toContain("No summaries found.");
+    expect(out.content[0].text).toBe(
+      "No summaries found. Hivemind memory is empty — tables are created when the first agent session starts, and entries appear after it ends."
+    );

As per coding guidelines: tests/**: Prefer asserting on specific values (paths, messages) over generic substrings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/claude-code/mcp-server.test.ts` around lines 303 - 308, Update the test
that mocks queryMock rejection and calls
registeredTools.get("hivemind_index")!.handler to assert the full returned
message (including the fresh-org hint) rather than only checking for the
substring "No summaries found."; locate the test in mcp-server.test.ts (the it
block that sets queryMock.mockRejectedValue(new Error('relation "sessions" does
not exist')) and calls importServer() and the handler) and replace the partial
expect with an assertion that the entire out.content[0].text equals (or
contains) the exact expected message string including the hint so the test
matches the other suite tests and provides complete coverage.

Source: Coding guidelines


294-301: ⚡ Quick win

Prefer exact message assertion over substring matching.

The test uses toContain() to check for substrings. Per the coding guidelines, tests should prefer asserting on specific values rather than generic substrings.

♻️ Proposed fix to assert the exact expected message
-    expect(out.content[0].text).toContain("No content found at /summaries/alice/a.md");
-    expect(out.content[0].text).toContain("first agent session");
-    expect(out.content[0].text).not.toContain("Read failed");
+    expect(out.content[0].text).toBe(
+      "No content found at /summaries/alice/a.md. Hivemind memory is empty — tables are created when the first agent session starts, and entries appear after it ends."
+    );
+    expect(out.content[0].text).not.toContain("Read failed");

As per coding guidelines: tests/**: Prefer asserting on specific values (paths, messages) over generic substrings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/claude-code/mcp-server.test.ts` around lines 294 - 301, The test
"hivemind_read: missing table → 'No content found' + fresh-org hint, no raw 400"
currently uses substring assertions on out.content[0].text; change these to
assert the exact expected message string (use a single equality assertion such
as toEqual or toBe) for the entire text returned by
registeredTools.get("hivemind_read")!.handler so the test verifies the full
composed message (including "No content found at /summaries/alice/a.md" and the
"first agent session" hint) and also assert that the exact message does not
include "Read failed" (use exact inequality) while leaving
queryMock.mockRejectedValue(missingTableErr) and importServer() unchanged.

Source: Coding guidelines


275-283: ⚡ Quick win

Prefer exact message assertion over substring matching.

The test uses toContain() to check for substrings ("No summaries found." and "first agent session"). Per the coding guidelines, tests should prefer asserting on specific values rather than generic substrings.

♻️ Proposed fix to assert the exact expected message
-    expect(out.content[0].text).toContain("No summaries found.");
-    expect(out.content[0].text).toContain("first agent session");
-    expect(out.content[0].text).not.toContain("Index failed");
-    expect(out.content[0].text).not.toContain("400");
+    expect(out.content[0].text).toBe(
+      "No summaries found. Hivemind memory is empty — tables are created when the first agent session starts, and entries appear after it ends."
+    );
+    expect(out.content[0].text).not.toContain("Index failed");
+    expect(out.content[0].text).not.toContain("400");

As per coding guidelines: tests/**: Prefer asserting on specific values (paths, messages) over generic substrings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/claude-code/mcp-server.test.ts` around lines 275 - 283, Replace the
loose substring assertions with an exact equality check against the full
expected message string produced by the hivemind_index handler: locate the test
case (the it block) that calls registeredTools.get("hivemind_index")!.handler
and replace the two expect(...).toContain(...) checks on out.content[0].text
with a single expect(out.content[0].text).toEqual(<full_expected_message>) using
the exact message the handler returns (include both the "No summaries found."
sentence and the "first agent session" hint in the exact order/format). Keep the
existing negative assertions (not toContain "Index failed" and not toContain
"400") and do not change importServer or the queryMock setup.

Source: Coding guidelines


285-292: ⚡ Quick win

Prefer exact message assertion over substring matching.

The test uses toContain() to check for substrings. Per the coding guidelines, tests should prefer asserting on specific values rather than generic substrings.

♻️ Proposed fix to assert the exact expected message
-    expect(out.content[0].text).toContain('No matches for "needle".');
-    expect(out.content[0].text).toContain("first agent session");
-    expect(out.content[0].text).not.toContain("Search failed");
+    expect(out.content[0].text).toBe(
+      'No matches for "needle". Hivemind memory is empty — tables are created when the first agent session starts, and entries appear after it ends.'
+    );
+    expect(out.content[0].text).not.toContain("Search failed");

As per coding guidelines: tests/**: Prefer asserting on specific values (paths, messages) over generic substrings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/claude-code/mcp-server.test.ts` around lines 285 - 292, The test for
hivemind_search uses substring assertions; change them to assert the exact
expected output string. Call registeredTools.get("hivemind_search")!.handler({
query: "needle" }) to get the response and replace the three toContain checks
with a single strict equality (or exact string) assertion against the full
expected message that includes 'No matches for "needle".' and the "first agent
session" hint, and ensure it does not equal or include the raw 400 error text;
update references: searchDeeplakeTablesMock, missingTableErr, and the
hivemind_search handler to construct the exact expected message used in the
assertion.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/claude-code/mcp-server.test.ts`:
- Around line 303-308: Update the test that mocks queryMock rejection and calls
registeredTools.get("hivemind_index")!.handler to assert the full returned
message (including the fresh-org hint) rather than only checking for the
substring "No summaries found."; locate the test in mcp-server.test.ts (the it
block that sets queryMock.mockRejectedValue(new Error('relation "sessions" does
not exist')) and calls importServer() and the handler) and replace the partial
expect with an assertion that the entire out.content[0].text equals (or
contains) the exact expected message string including the hint so the test
matches the other suite tests and provides complete coverage.
- Around line 294-301: The test "hivemind_read: missing table → 'No content
found' + fresh-org hint, no raw 400" currently uses substring assertions on
out.content[0].text; change these to assert the exact expected message string
(use a single equality assertion such as toEqual or toBe) for the entire text
returned by registeredTools.get("hivemind_read")!.handler so the test verifies
the full composed message (including "No content found at /summaries/alice/a.md"
and the "first agent session" hint) and also assert that the exact message does
not include "Read failed" (use exact inequality) while leaving
queryMock.mockRejectedValue(missingTableErr) and importServer() unchanged.
- Around line 275-283: Replace the loose substring assertions with an exact
equality check against the full expected message string produced by the
hivemind_index handler: locate the test case (the it block) that calls
registeredTools.get("hivemind_index")!.handler and replace the two
expect(...).toContain(...) checks on out.content[0].text with a single
expect(out.content[0].text).toEqual(<full_expected_message>) using the exact
message the handler returns (include both the "No summaries found." sentence and
the "first agent session" hint in the exact order/format). Keep the existing
negative assertions (not toContain "Index failed" and not toContain "400") and
do not change importServer or the queryMock setup.
- Around line 285-292: The test for hivemind_search uses substring assertions;
change them to assert the exact expected output string. Call
registeredTools.get("hivemind_search")!.handler({ query: "needle" }) to get the
response and replace the three toContain checks with a single strict equality
(or exact string) assertion against the full expected message that includes 'No
matches for "needle".' and the "first agent session" hint, and ensure it does
not equal or include the raw 400 error text; update references:
searchDeeplakeTablesMock, missingTableErr, and the hivemind_search handler to
construct the exact expected message used in the assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 29781e0f-3165-4ece-8aa9-6e7963fe01b9

📥 Commits

Reviewing files that changed from the base of the PR and between a54660f and 3f872ca.

📒 Files selected for processing (2)
  • src/mcp/server.ts
  • tests/claude-code/mcp-server.test.ts

CodeRabbit review on #255: the repo test guidelines prefer asserting
specific values over generic substrings. Replace the toContain checks
in the fresh-org block with exact toBe assertions on the full message
(shared freshOrgHint constant).
Index and read map backend rows straight into the text the agent
consumes; without the ?? fallbacks a NULL description or message::text
would render the literal strings 'null'/'undefined' into the recall
context. Cover both paths with exact-output assertions (branches
84.6% -> 94.2% on src/mcp/server.ts).
@efenocchi efenocchi merged commit b684bb8 into main Jun 11, 2026
11 checks passed
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.

MCP hivemind_index and hivemind_search return 400: relation "memory" does not exist

2 participants