Skip to content

feat(mcp): discover projects across workspaces#757

Merged
phernandez merged 4 commits into
mainfrom
feat/mcp-workspace-free-projects-756
Apr 18, 2026
Merged

feat(mcp): discover projects across workspaces#757
phernandez merged 4 commits into
mainfrom
feat/mcp-workspace-free-projects-756

Conversation

@phernandez
Copy link
Copy Markdown
Member

Summary

  • require the cloud workspace discovery contract fields (slug, is_default, default_workspace_id) and use them for workspace-qualified project routing
  • build and cache a per-session cloud workspace project index so MCP project resolution can use workspace-slug/project and report ambiguous unqualified names
  • aggregate list_memory_projects across accessible workspaces while preserving local-only fallback for listing UX
  • keep list_workspaces friendly for non-teams/local-only users with a display-only personal workspace fallback

Validation

  • uv run pytest tests/mcp/test_tool_workspace_management.py tests/mcp/test_tool_project_management.py tests/mcp/test_project_context.py tests/mcp/test_project_context_telemetry.py tests/cli/test_workspace_commands.py tests/cli/test_project_info_cloud_status.py tests/cli/test_project_add_with_local_path.py tests/cli/test_project_set_cloud_local.py -q
  • uv run ruff check src/basic_memory/mcp/project_context.py src/basic_memory/mcp/tools/project_management.py src/basic_memory/mcp/tools/utils.py src/basic_memory/mcp/tools/workspaces.py src/basic_memory/schemas/cloud.py tests/cli/test_project_add_with_local_path.py tests/cli/test_project_info_cloud_status.py tests/cli/test_project_set_cloud_local.py tests/cli/test_workspace_commands.py tests/mcp/test_project_context.py tests/mcp/test_project_context_telemetry.py tests/mcp/test_tool_project_management.py tests/mcp/test_tool_workspace_management.py
  • uv run ty check src/basic_memory/mcp/project_context.py src/basic_memory/mcp/tools/project_management.py src/basic_memory/mcp/tools/utils.py src/basic_memory/mcp/tools/workspaces.py src/basic_memory/schemas/cloud.py tests/cli/test_project_add_with_local_path.py tests/cli/test_project_info_cloud_status.py tests/cli/test_project_set_cloud_local.py tests/cli/test_workspace_commands.py tests/mcp/test_project_context.py tests/mcp/test_project_context_telemetry.py tests/mcp/test_tool_project_management.py tests/mcp/test_tool_workspace_management.py
  • git diff --check

Closes #756
Depends on basicmachines-co/basic-memory-cloud#619

Signed-off-by: phernandez <paul@basicmachines.co>
@phernandez phernandez force-pushed the feat/mcp-workspace-free-projects-756 branch from 4882545 to 49148fe Compare April 18, 2026 18:41
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 18, 2026

Claude finished @phernandez's task in 2m 36s —— View job


PR Review: feat(mcp): discover projects across workspaces

Tasks

  • Gather context and understand the PR
  • Review changed files
  • Check code quality and standards
  • Verify testing coverage
  • Check architecture patterns
  • Security and performance review
  • Post final summary

Summary

Good implementation of cross-workspace project discovery. The caching strategy, workspace/project index, and routing fallback chain are well-designed. Test coverage is thorough and the new scenarios are clearly expressed. A few issues worth addressing before merge:


Code Quality & Standards

✅ Passing

  • Python 3.12+ type annotations throughout (tuple[...], X | None, etc.)
  • Line length maintained
  • No commented-out code blocks
  • Async patterns with asynccontextmanager and asyncio.gather
  • Literate-style section headers and decision-point comments in project_context.py

⚠️ Issues Found

1. Bug: Factory mode omits workspace_slug and workspace_is_default from project listing

project_management.py:317-344 — The factory mode block collects cloud_ws_name, cloud_ws_type, and cloud_ws_tenant_id from the matched workspace, but does not collect slug or is_default. As a result, _merge_projects is called without cloud_workspace_slug or cloud_workspace_is_default, so every factory-mode project listing will have workspace_slug=None and qualified_name=None, defeating the purpose of workspace-qualified routing in cloud listings.

# Missing:
cloud_ws_slug: str | None = None
cloud_ws_is_default: bool = False
...
cloud_ws_slug = matched.slug
cloud_ws_is_default = matched.is_default

merged = _merge_projects(
    None,
    project_list,
    cloud_workspace_name=cloud_ws_name,
    cloud_workspace_type=cloud_ws_type,
    cloud_workspace_tenant_id=cloud_ws_tenant_id,
+   cloud_workspace_slug=cloud_ws_slug,
+   cloud_workspace_is_default=cloud_ws_is_default,
)

Fix this →

2. In-place mutation of ProjectItem.is_default in _fetch_workspace_project_entries

project_context.py:443project.is_default = True mutates the ProjectItem Pydantic model returned from the API client. If the same instance is referenced elsewhere (e.g., reused through the ProjectList), this silently changes state. Safer to build the entry with is_default set in the constructor:

# Instead of:
if default_permalink and project.permalink == default_permalink:
    project.is_default = True
entries.append(WorkspaceProjectEntry(workspace=workspace, project=project))

# Prefer:
is_default = bool(default_permalink and project.permalink == default_permalink)
annotated = project.model_copy(update={"is_default": is_default}) if is_default else project
entries.append(WorkspaceProjectEntry(workspace=workspace, project=annotated))

3. Importing private symbol across module boundary

project_management.py:17_ensure_workspace_project_index is imported with the leading-underscore convention (private by intent) into a separate tool module. Consider exposing it as ensure_workspace_project_index or wrapping it in a public helper.

4. Unicode bullet newly introduced in console output

project_management.py:253_format_project_list_text now uses (newly added per diff). CLAUDE.md: "Avoid using emojis and unicode characters in console and log output." The separator on line 255 and in other functions are pre-existing, but the new is in scope for this PR.


Testing & Documentation

✅ Passing

  • Good test coverage for index caching, invalidation, qualified routing, ambiguous resolution, workspace mismatch, and the local-only fallback
  • The _workspace() / _project() factory helpers make test setup readable
  • pragma: no cover at project_context.py:446 is justified (fire-and-forget context.info() in a deeply nested async gather)

⚠️ Issues Found

5. Duplicate _workspace() helper across test files

_workspace() is defined identically in both tests/mcp/test_project_context.py:31 and tests/mcp/test_tool_workspace_management.py:12. This should live in tests/mcp/conftest.py (or the existing conftest) to avoid drift. Similarly _project() vs _make_project() serves the same purpose across files.


Basic Memory Architecture

✅ Passing

  • Local-first principle preserved: cloud workspace resolution only activates when has_cloud_credentials() is true or factory mode is set
  • Explicit routing (--local / --cloud) correctly takes priority before workspace resolution at project_context.py:950
  • Cache invalidation on create_memory_project and delete_project is correct
  • Workspace context clearing when tenant changes (_set_cached_active_workspace) is a nice correctness guard

⚠️ Note

6. Breaking schema change: WorkspaceInfo.slug and is_default are now required

schemas/cloud.py:66,69 — These fields now have no default, so any existing serialized WorkspaceInfo (e.g., in MCP context state from a prior session) will fail model_validate. The _workspace_project_index_from_state deserializer will surface this as a ValidationError. This is likely the intended contract with cloud#619, but any cached context state from before the deploy will need to be invalidated.


Security & Performance

✅ Passing

  • No hardcoded secrets
  • No sensitive data in log output
  • asyncio.gather for parallel workspace project fetching is correct
  • Broad except Exception: pass at project_management.py:335 is explicitly scoped to best-effort workspace metadata enrichment for display, not routing

Checklist

Item Status
Follows coding conventions
Python 3.12+ type annotations and async patterns
FastAPI/Typer conventions followed
100-character line length maintained
No commented-out code blocks
Unit tests for new functions
Test coverage for edge cases
MCP tools follow atomic, composable design
Preserves local-first architecture
No hardcoded secrets or credentials
No sensitive data in logs
Markdown file handling preserved
Factory mode workspace_slug missing in listing ❌ Bug
ProjectItem.is_default mutation ⚠️ Minor
Importing private _ensure_workspace_project_index ⚠️ Minor
Unicode in console output (new) ⚠️ CLAUDE.md violation
Duplicate _workspace() test helper ⚠️ Minor
Breaking schema change (intentional, needs cloud#619 first) ℹ️ Note

The factory-mode slug omission (#1) is the only actionable bug. The rest are minor code-quality items.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 48825456d0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/basic_memory/mcp/project_context.py Outdated
Comment on lines +471 to +473
fetched_entries = await asyncio.gather(
*[_fetch_workspace_project_entries(workspace, context=context) for workspace in workspaces]
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Isolate per-workspace fetch failures when indexing projects

_ensure_workspace_project_index builds the cross-workspace index with asyncio.gather(...) in fail-fast mode, so one list_projects error from any workspace aborts the entire index. In multi-workspace accounts this makes resolve_workspace_project_identifier/get_project_client fail globally (including qualified lookups for healthy workspaces) whenever a single workspace is temporarily failing or unauthorized, which is a regression from the previous single-workspace routing behavior.

Useful? React with 👍 / 👎.

Comment on lines +1001 to +1003
resolved_entry = await resolve_workspace_project_identifier(
resolved_project,
context=context,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve factory-only routing for project resolution

This factory/cloud path now resolves projects through resolve_workspace_project_identifier, which depends on workspace discovery before opening the injected client. In deployments that only configure set_client_factory() (without also registering a workspace provider), this can fall back to control-plane auth and fail before the factory transport is used, breaking previously valid factory integrations that relied on in-process tenant routing.

Useful? React with 👍 / 👎.

Signed-off-by: phernandez <paul@basicmachines.co>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 18, 2026

Claude finished @phernandez's task in 2m 12s —— View job


PR Review: feat(mcp): discover projects across workspaces

Tasks

  • Gather context and understand the PR
  • Review changed files
  • Check code quality and standards
  • Verify testing coverage
  • Check architecture patterns
  • Security and performance review
  • Post final summary

Summary

This PR correctly addresses all three actionable bugs from the previous review round. The caching strategy, workspace/project index, and routing fallback chain are well-designed. Two minor items and one open question remain.


Previous Review — Status

Issue Status
Factory mode omits workspace_slug/is_default ✅ Fixed (lines 320-338, 347-349)
ProjectItem.is_default in-place mutation ✅ Fixed — model_copy now used at project_context.py:444, new test confirms original is not mutated
Importing private _ensure_workspace_project_index ✅ Fixed — project_management.py:17 now imports the public ensure_workspace_project_index

Remaining Items

⚠️ Unicode in console output (CLAUDE.md violation)

project_management.py:253 — This is the only newly-introduced character in the diff. CLAUDE.md: "Avoid using emojis and unicode characters in console and log output."

# Current
result += f"• {label} ({source}){qualified_suffix}\n"
# Fix
result += f"- {label} ({source}){qualified_suffix}\n"

The matching test at test_tool_project_management.py:50 also asserts on f"• {test_project.name}" and would need updating.

Fix this →

⚠️ Duplicate _workspace() / _ContextState helpers in test files

_workspace() and _ContextState are defined identically in both tests/mcp/test_project_context.py:15,32 and tests/mcp/test_tool_workspace_management.py:12,31. tests/mcp/conftest.py is the right home for these.

ℹ️ Codex P1: asyncio.gather without return_exceptions=True

project_context.py:472asyncio.gather(...) runs in fail-fast mode (default return_exceptions=False). A transient failure fetching projects from any one workspace will abort the entire index build, and all cloud project routing (including qualified lookups for healthy workspaces) will fail for that session. For teams with multiple workspaces, this is a potential silent regression.

The fix is asyncio.gather(..., return_exceptions=True) with a post-gather pass that logs per-workspace errors and drops failed fetches:

results = await asyncio.gather(
    *[_fetch_workspace_project_entries(ws, context=context) for ws in workspaces],
    return_exceptions=True,
)
entries = tuple(
    entry
    for result in results
    if not isinstance(result, BaseException)
    for entry in result
)
# log any BaseException items here

Whether to address this now or in a follow-up depends on whether multi-workspace teams are in scope for this release — leaving it as a noted trade-off.

ℹ️ Codex P2: Factory routing depends on workspace provider being registered

project_context.py:1009 — In factory mode with no explicit workspace/workspace_id, routing falls back to resolve_workspace_project_identifier, which calls _ensure_workspace_project_index, which calls get_available_workspaces. If a factory deployment sets a client factory but not a workspace provider, this will raise "No accessible workspaces found". This is an integration contract with cloud#619 and should be documented if not already.


Checklist

Item Status
Follows coding conventions
Python 3.12+ type annotations and async patterns
FastAPI/Typer conventions followed
100-character line length maintained
No commented-out code blocks
Unit tests for new functions
Test coverage for edge cases
MCP tools follow atomic, composable design
Preserves local-first architecture
No hardcoded secrets or credentials
No sensitive data in logs
File path comparisons Windows-compatible
Unicode in console output (new, line 253) ⚠️ CLAUDE.md violation
Duplicate _workspace() / _ContextState test helpers ⚠️ Minor
asyncio.gather fail-fast in multi-workspace index build ℹ️ Trade-off to decide
Factory routing implicit workspace provider contract ℹ️ Integration note
Breaking schema change (WorkspaceInfo.slug/is_default) ℹ️ Intentional per cloud#619

Signed-off-by: phernandez <paul@basicmachines.co>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6b0f19ca81

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/basic_memory/mcp/project_context.py Outdated
Comment on lines +999 to +1002
elif project_entry and project_entry.workspace_id:
active_ws = await resolve_workspace_parameter(
workspace=project_entry.workspace_id,
context=context,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Skip workspace discovery when tenant id is already configured

This branch re-resolves a known workspace_id through resolve_workspace_parameter, which forces a /workspaces discovery call before opening the project client. That creates a new hard dependency on control-plane workspace listing for every cloud project that already has ProjectEntry.workspace_id set: if workspace discovery is temporarily failing or unauthorized, project routing now fails even though the tenant ID is already known and could be sent directly to get_client (as before).

Useful? React with 👍 / 👎.

Comment on lines +376 to +377
active_workspace = await resolve_workspace_parameter(workspace, context)
cloud_list = await _fetch_cloud_projects(active_workspace.tenant_id, context)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve local fallback for explicit workspace project listing

The explicit-workspace path now calls resolve_workspace_parameter before _fetch_cloud_projects. Because _fetch_cloud_projects is where cloud-list failures are intentionally downgraded to local-only results, any failure during workspace discovery (control-plane outage, auth mismatch, etc.) now aborts list_memory_projects outright instead of returning the local project list as the tool’s fallback behavior promises.

Useful? React with 👍 / 👎.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 18, 2026

Claude finished @phernandez's task in 1m 40s —— View job


PR Review: feat(mcp): discover projects across workspaces

Tasks

  • Gather context and understand the PR
  • Review changed files for current state
  • Check status of items from previous reviews
  • Verify new commit addresses asyncio.gather issue
  • Check code quality and standards
  • Post final summary

Summary

The third commit (7ee910c fix(mcp): isolate workspace index failures) addresses both remaining actionable issues from the previous review round. This PR is in good shape to merge.


Previous Review Items — Final Status

Issue Status
Factory mode omits workspace_slug/is_default ✅ Fixed
ProjectItem.is_default in-place mutation ✅ Fixed
Importing private _ensure_workspace_project_index ✅ Fixed
Unicode in console output ✅ Fixed — line 253 now uses "-", test assertion updated
asyncio.gather fail-fast in multi-workspace index build ✅ Fixed — well-implemented
Duplicate _workspace() / _ContextState test helpers ⚠️ Still open (minor)
Factory routing implicit workspace provider contract ℹ️ Noted, not a blocker
Breaking schema change (WorkspaceInfo.slug/is_default) ℹ️ Intentional per cloud#619

asyncio.gather Fix — Quality Check ✅

The _ensure_workspace_project_index fix in project_context.py:487-523 is correctly implemented:

  • return_exceptions=True prevents one workspace failure from aborting the full gather
  • Non-Exception BaseExceptions (e.g. KeyboardInterrupt) are re-raised immediately — correct
  • Per-workspace errors are logged and added to failed_workspaces — correct
  • If all workspaces fail, a consolidated error is raised — correct
  • resolve_workspace_project_identifier uses failed_workspaces to surface actionable messages when a qualified workspace had discovery errors (lines 577-584, 629-636) — correct

The WorkspaceProjectIndex.failed_workspaces field cleanly propagates partial-failure state downstream without swallowing exceptions.


Remaining Minor Item

Duplicate _workspace() / _ContextState test helpers

Three files define functionally identical helpers:

Helper Files
_workspace() test_project_context.py:35, test_tool_workspace_management.py:12
_ContextState test_project_context.py:15, test_tool_workspace_management.py:31, test_project_context_telemetry.py:18

tests/mcp/conftest.py is the right home. The _ContextState in test_tool_workspace_management.py omits the info() method, so consolidation would need to use the fuller version from test_project_context.py. This is low risk and could land in a follow-up.


Checklist

Item Status
Follows coding conventions
Python 3.12+ type annotations and async patterns
FastAPI/Typer conventions followed
100-character line length maintained
No commented-out code blocks
Unit tests for new functions
Test coverage for edge cases
MCP tools follow atomic, composable design
Preserves local-first architecture
No hardcoded secrets or credentials
No sensitive data in logs
No unicode characters in console output
File path comparisons Windows-compatible
asyncio.gather per-workspace isolation
Duplicate test helpers in 3 files ⚠️ Minor — follow-up
Breaking schema change (slug/is_default) ℹ️ Intentional, depends on cloud#619

Signed-off-by: phernandez <paul@basicmachines.co>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 18, 2026

Claude finished @phernandez's task in 1m 43s —— View job


PR Review: feat(mcp): discover projects across workspaces

Tasks

  • Gather context and understand the PR
  • Read latest commit changes
  • Check status of previous review items
  • Verify code quality and standards
  • Post final summary

Summary

The fourth commit (38aeedf fix(mcp): avoid discovery for configured workspace routes) addresses both Codex P1 and P2 items from the previous review rounds. This PR is ready to merge.


Previous Review Items — Final Status

Issue Status
Factory mode omits workspace_slug/is_default ✅ Fixed
ProjectItem.is_default in-place mutation ✅ Fixed
Importing private _ensure_workspace_project_index ✅ Fixed
Unicode in console output ✅ Fixed
asyncio.gather fail-fast in multi-workspace index build ✅ Fixed
Skip discovery for already-configured workspace_id (Codex P1) ✅ Fixed
Preserve local fallback for explicit workspace listing (Codex P2) ✅ Fixed
Duplicate _workspace() / _ContextState test helpers ⚠️ Minor — follow-up
Breaking schema change (WorkspaceInfo.slug/is_default) ℹ️ Intentional per cloud#619

Latest Commit Quality Check ✅

project_context.py:1079-1084 — Skip discovery for configured workspace_id

The project_entry.workspace_id branch now bypasses resolve_workspace_parameter entirely and sends the stored tenant ID straight to get_client. The decision-point comment (# Trigger / Why / Outcome) accurately describes the invariant. active_ws is left None for this path, and the if active_ws is not None: guard at line 1098 correctly prevents a NoneType error on the cache-update call — no active workspace is cached, but routing is unaffected.

workspace_id: str is declared without initialization, but all three branches (workspace is not None, project_entry.workspace_id, else) assign it unconditionally — no uninitialized-variable risk at runtime.

project_management.py:375-396 — Discovery-failure fallback for explicit workspace listing

The try/except around resolve_workspace_parameter preserves the original fallback contract: on discovery failure, _fetch_cloud_projects(workspace, context) is called with the raw workspace argument, which itself is wrapped in its own try/except and returns None on failure, so the merge always degrades gracefully to local-only output. If the raw workspace string happens to be a valid tenant ID, callers get a best-effort project list without workspace metadata (slug, name, etc.) — a reasonable degraded state, since _merge_projects handles all those fields as optional.

The new test test_list_memory_projects_explicit_workspace_discovery_failure_graceful correctly verifies the raw workspace string is forwarded and that the local project appears in the result.


Remaining Minor Item

Duplicate _workspace() / _ContextState test helpers

Three files define functionally identical helpers:

Helper Files
_workspace() test_project_context.py:35, test_tool_workspace_management.py:12
_ContextState test_project_context.py:15, test_tool_workspace_management.py:31, test_project_context_telemetry.py:18

tests/mcp/conftest.py is the right home. Low risk — suitable for a follow-up cleanup.


Checklist

Item Status
Follows coding conventions
Python 3.12+ type annotations and async patterns
FastAPI/Typer conventions followed
100-character line length maintained
No commented-out code blocks
Unit tests for new functions
Test coverage for edge cases
MCP tools follow atomic, composable design
Preserves local-first architecture
No hardcoded secrets or credentials
No sensitive data in logs
No unicode characters in console output
File path comparisons Windows-compatible
asyncio.gather per-workspace isolation
Configured workspace_id routes without discovery
Explicit workspace listing fallback preserved
Duplicate test helpers in 3 files ⚠️ Minor — follow-up
Breaking schema change (slug/is_default) ℹ️ Intentional, depends on cloud#619

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 38aeedfdd8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +474 to +478
if context:
cached_raw = await context.get_state(_WORKSPACE_PROJECT_INDEX_STATE_KEY)
cached_index = _workspace_project_index_from_state(cached_raw)
if cached_index is not None:
return cached_index
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Rebuild partial workspace index after transient failures

_ensure_workspace_project_index returns any cached index immediately, even when that cached index was built with failed_workspaces. If one workspace project fetch fails once (for example a transient outage or temporary auth issue), that partial state is reused for the rest of the MCP session and keeps producing “discovery failed” routing errors after the workspace recovers, until the caller manually invalidates state or starts a new session.

Useful? React with 👍 / 👎.

Comment on lines +1098 to +1099
if active_ws is not None:
await _set_cached_active_workspace(context, active_ws)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clear stale workspace cache on workspace_id routing

The active workspace cache is only updated when active_ws is populated, but the project_entry.workspace_id path never sets active_ws; it only sets workspace_id. In multi-workspace sessions, this leaves the previous workspace cached and later unqualified duplicate project names can be resolved against the wrong tenant via the cached-workspace shortcut in resolve_workspace_project_identifier.

Useful? React with 👍 / 👎.

@phernandez phernandez merged commit f3e46d7 into main Apr 18, 2026
14 of 44 checks passed
@phernandez phernandez deleted the feat/mcp-workspace-free-projects-756 branch April 18, 2026 19:04
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.

Remove X-Workspace-ID requirement from MCP — aggregate projects across workspaces

1 participant