Skip to content

fix: add workspace routing to cloud upload and API client#704

Open
groksrc wants to merge 3 commits intomainfrom
fix/cloud-upload-workspace-routing
Open

fix: add workspace routing to cloud upload and API client#704
groksrc wants to merge 3 commits intomainfrom
fix/cloud-upload-workspace-routing

Conversation

@groksrc
Copy link
Copy Markdown
Member

@groksrc groksrc commented Apr 1, 2026

Summary

  • get_cloud_control_plane_client() now accepts a workspace param and sends X-Workspace-ID header
  • cloud_utils functions (fetch_cloud_projects, create_cloud_project, project_exists) now accept and pass workspace
  • upload command resolves workspace from per-project workspace_id or default_workspace
  • get_client() per-project routing auto-resolves workspace from project config entry

Test plan

  • bm cloud upload ~/aitext --project aitext --dry-run shows correct workspace
  • bm cloud upload ~/aitext --project aitext --create-project creates project in teams workspace
  • Files visible and searchable in teams workspace via MCP after upload
  • Existing personal workspace uploads still work (no regression)
  • Unit tests pass

Fixes #703

🤖 Generated with Claude Code

Cloud upload, project existence checks, and project creation now
resolve and pass the X-Workspace-ID header for teams workspace
support. The get_client() per-project routing also auto-resolves
workspace from project config when not explicitly provided.

Fixes #703

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

Claude finished @groksrc's task in 1m 58s —— View job


PR Review: fix/cloud-upload-workspace-routing

  • Gather context and read changed files
  • Review code quality and standards
  • Review testing and documentation
  • Review architecture patterns
  • Review security and performance
  • Post full review summary

Overview

This PR correctly routes X-Workspace-ID headers through cloud upload and per-project API client calls. The general approach is sound and the workspace resolution priority order (per-project workspace_iddefault_workspace) is consistent across both changed files.


Issues Found

🔴 Breaking test: fake_project_exists missing workspace kwarg

File: tests/cli/cloud/test_upload_command_routing.py:23

async def fake_project_exists(_project_name: str) -> bool:
    return True

The PR changes upload_command.py to call:

if not await project_exists(project, workspace=workspace):

The monkeypatched fake_project_exists does not accept a workspace keyword argument. This will raise a TypeError at runtime, breaking the existing test.

Fix this →


🟡 Missing tests for new workspace behavior

The PR adds workspace propagation but no new tests verify:

  1. _workspace_headers sends the X-Workspace-ID headertest_cloud_utils_fetch_and_exists_and_create_project doesn't assert the header is included when workspace is passed.
  2. get_cloud_control_plane_client(workspace=...) sets the header — existing tests call it without a workspace; no test calls it with a workspace and asserts X-Workspace-ID.
  3. get_client() auto-resolves workspace from entry.workspace_idtest_get_client_cloud_adds_workspace_header passes workspace explicitly; there's no test that sets entry.workspace_id in config and verifies auto-resolution.
  4. upload_command.py workspace resolution — the new workspace-selection logic (project entry → default_workspace fallback) isn't exercised in tests.

Per CLAUDE.md: "Coverage must stay at 100%".


🟡 Duplicated workspace resolution logic

The same resolution pattern appears in two places:

upload_command.py:78-84:

entry = config.projects.get(project)
if entry and entry.workspace_id:
    workspace = entry.workspace_id
elif config.default_workspace:
    workspace = config.default_workspace

async_client.py:188-194:

entry = config.projects.get(project_name)
if entry and entry.workspace_id:
    effective_workspace = entry.workspace_id
elif config.default_workspace:
    effective_workspace = config.default_workspace

This is acceptable under the project's "no speculative abstraction" rule (two callsites), but worth noting for future reference if a third site appears.


🟡 Missing docstring workspace param in fetch_cloud_projects

create_cloud_project and project_exists both have workspace documented in their docstrings, but fetch_cloud_projects does not.

File: src/basic_memory/cli/commands/cloud/cloud_utils.py:26-49


🟢 Minor: Inconsistent Optional vs union type syntax

async_client.py uses Optional[str] (line 91, 143) while cloud_utils.py uses str | None (lines 19, 28, 55, 114). Both are valid in Python 3.12+ but the file-level inconsistency is minor.


Checklist

Category Status Notes
Python 3.12+ type annotations Both str | None and Optional[str] used
Async patterns asynccontextmanager usage is correct
FastAPI/Typer conventions Typer CLI patterns followed
100-char line length Within limits
No commented-out code
Unit tests for new code Missing workspace header assertions and auto-resolve tests
100% test coverage ⚠️ New branches in workspace resolution uncovered
No hardcoded secrets
No sensitive data in logs workspace ID is logged, which seems acceptable
Input validation Truthy check on workspace is appropriate
Local-first architecture preserved Workspace only affects cloud routing
Windows path compatibility No path comparisons in changed code
No emoji/unicode in console output

Summary

The workspace routing logic itself is correct and well-structured. The critical blocker is that the existing test test_cloud_upload_uses_control_plane_client will fail because fake_project_exists doesn't accept the new workspace keyword argument. Additional tests for the new workspace header behavior are needed to maintain 100% coverage.

- Fix fake_project_exists and fake_get_client signatures in upload routing test
- Add config_manager fixture to upload routing test
- Add tests for get_cloud_control_plane_client with workspace header
- Add tests for get_client auto-resolving workspace from project config and default_workspace
- Add tests for explicit workspace overriding config
- Add tests for _workspace_headers helper and cloud_utils workspace passing
- Add missing docstring for workspace param in fetch_cloud_projects

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

Claude finished @groksrc's task in 2m 11s —— View job


PR Review: fix/cloud-upload-workspace-routing

  • Gather context and read changed files
  • Review code quality and standards
  • Review testing and documentation
  • Review architecture patterns
  • Review security and performance
  • Post full review summary

Overview

This revision addresses all critical issues from the previous review. The workspace routing logic is sound, the broken test is fixed, and comprehensive workspace coverage has been added for the async_client and cloud_utils layers.


Fixed Since Last Review ✅

Issue Status
fake_project_exists missing workspace kwarg Fixed — workspace: str | None = None added
_workspace_headers untested Fixed — test_workspace_headers_with_workspace + test_workspace_headers_without_workspace added
get_cloud_control_plane_client(workspace=...) untested Fixed — test_get_cloud_control_plane_client_with_workspace added
get_client() auto-resolve from entry.workspace_id untested Fixed — test_get_client_auto_resolves_workspace_from_project_config and test_get_client_explicit_workspace_overrides_config added
get_client() auto-resolve from default_workspace untested Fixed — test_get_client_auto_resolves_workspace_from_default added
fetch_cloud_projects missing workspace docstring Fixed — workspace: Cloud workspace tenant_id to list projects from added
fetch_cloud_projects/project_exists/create_cloud_project workspace header not asserted Fixed — test_cloud_utils_pass_workspace_header asserts x-workspace-id for all three

Remaining Issues

🟡 Upload command workspace resolution branches not exercised

File: src/basic_memory/cli/commands/cloud/upload_command.py:80-84

entry = config.projects.get(project)
if entry and entry.workspace_id:
    workspace = entry.workspace_id
elif config.default_workspace:
    workspace = config.default_workspace

The sole test in test_upload_command_routing.py invokes --project routing-test against a blank config_manager, so config.projects.get("routing-test") returns None and config.default_workspace is None. The two workspace = assignment lines (82 and 84) are never executed. Per CLAUDE.md the 100% coverage requirement is strict, so these branches need tests.

Two tests would cover this:

  1. Set cfg.projects["routing-test"].workspace_id = "tenant-from-entry" → assert fake_get_client receives workspace="tenant-from-entry".
  2. Set cfg.default_workspace = "default-tenant" (no per-project entry) → assert fake_get_client receives workspace="default-tenant".

Fix this →


🟢 Minor: Optional[str] vs str | None type syntax

async_client.py uses Optional[str] in three function signatures (lines 72, 92, 143) while cloud_utils.py and the rest of the project use str | None (Python 3.12+ style). Pre-existing in the file, but the new additions follow the same pattern. Low priority.


Checklist

Category Status Notes
Python 3.12+ type annotations ⚠️ Optional[str] in async_client.py — pre-existing
Async patterns asynccontextmanager usage is correct throughout
FastAPI/Typer conventions Followed correctly
100-char line length Within limits
No commented-out code
_workspace_headers unit tests Both with/without workspace
Cloud utils workspace header tests All three functions asserted
get_client() workspace auto-resolve tests Project entry, default_workspace, and explicit override all covered
get_cloud_control_plane_client workspace test Both with/without workspace
Upload command workspace resolution entry.workspace_id and default_workspace branches uncovered
No hardcoded secrets
No sensitive data in logs workspace ID in dim console output is acceptable
Input validation Truthy check on workspace is appropriate
Local-first architecture preserved Workspace only affects cloud routing
Windows path compatibility No path comparisons in changed code

Summary

All critical issues from the previous review are resolved. The one remaining gap is test coverage for the upload command's workspace resolution logic — the entry.workspace_id and default_workspace branches in upload_command.py:80-84 aren't exercised by any test. Adding two focused tests there would satisfy the 100% coverage requirement and make this PR ready to merge.

Resolved conflicts in cloud_utils.py, upload_command.py, async_client.py,
and related tests. Adopted main's resolve_configured_workspace() helper
as the centralized workspace resolution pattern, replacing inline config
lookups from the PR branch. Removed superseded direct _workspace_headers
tests in favor of main's more comprehensive workspace resolution tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 6, 2026

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


PR Review: fix/cloud-upload-workspace-routing

  • Gather context and read changed files
  • Review code quality and standards
  • Review testing and documentation
  • Review architecture patterns
  • Review security and performance
  • Post full review summary

Overview

This revision fully resolves all issues from the prior two reviews. The workspace routing logic is sound, the broken fake_project_exists signature is fixed, and the upload command workspace resolution is now covered by test_cloud_upload_uses_project_workspace_for_api_and_webdav.


Fixed Since Last Review ✅

Issue Status
Upload command workspace resolution branches uncovered Fixed — new test_cloud_upload_uses_project_workspace_for_api_and_webdav test covers workspace_id priority over default_workspace
if resolved_workspace: print branch both paths covered ✅ — truthy path covered by new test, falsy path covered by original test

Remaining Notes

🟡 default_workspace fallback not exercised by upload command tests directly

test_cloud_upload_uses_project_workspace_for_api_and_webdav sets both workspace_id = "project-workspace" and default_workspace = "default-workspace", asserting the project-level entry wins. There is no upload-specific test where only default_workspace is configured (no per-project workspace_id). The default_workspace branch of resolve_configured_workspace is tested in test_async_client_modes.py::test_get_client_auto_resolves_workspace_from_default, which is the correct place since the logic was extracted there.

This is acceptable — resolve_configured_workspace is a pure function with its own coverage. If you want belt-and-suspenders coverage for the upload command specifically, a third test could be added, but it's not blocking.

🟡 Console print placement

File: src/basic_memory/cli/commands/cloud/upload_command.py:109-110

# Perform upload (or dry run)
if resolved_workspace:
    console.print(f"[dim]Using workspace: {resolved_workspace}[/dim]")
if dry_run:
    console.print(...)

The workspace is printed after the project-exists guard but before the dry-run announcement. In a dry run, both messages will print — which is fine. Minor UX observation only.

🟢 Unnecessary blank lines in test file

File: tests/cli/cloud/test_cloud_api_client_and_utils.py:270-271

Two extra blank lines added between test_project_exists_surfaces_cloud_lookup_failures and the next test. PEP 8 convention is two blank lines between top-level functions; three is not a problem but is slightly inconsistent. Not a blocking issue.

🟢 Pre-existing: Optional[str] vs str | None

async_client.py still uses Optional[str] (lines 72, 92, 143) while the rest of the project uses str | None. Pre-existing, not introduced by this PR.


Checklist

Category Status Notes
Python 3.12+ type annotations ⚠️ Optional[str] in async_client.py — pre-existing
Async patterns asynccontextmanager usage is correct throughout
FastAPI and Typer conventions Followed correctly
100-char line length Within limits
No commented-out code
Unit tests for new code Workspace header + auto-resolve + upload routing tests all present
100% coverage Both branches of new if resolved_workspace: are covered; resolve_configured_workspace branches covered in dedicated tests
Docstrings updated fetch_cloud_projects Args block added
No hardcoded secrets
No sensitive data in logs workspace ID in dim console output is acceptable
Input validation Truthy check on workspace is appropriate
Local-first architecture preserved Workspace only affects cloud routing
Windows path compatibility No path comparisons in changed code
No emoji/unicode in console output

Summary

All critical and major issues from previous reviews are resolved. The workspace routing is correct and consistently applied across the control-plane client, cloud utils, and the upload command. The minor notes above are non-blocking. This PR is ready to merge.

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.

Cloud upload and API routing don't support workspace selection

2 participants