fix(cli): seed config projects into the database on CLI-only installs - #1354
Conversation
A fresh install writes `main` into config.json, but only the API/MCP server lifespan (initialize_app) reconciled config projects into the projects table. The CLI skips that initialization for `project`, `status`, `tool`, and most other commands, so a CLI-only flow hit "Project not found: 'main'" on every default-project command while `project add main` refused with "already exists" — the #974 wedge again. Run the same reconciliation once per process from the API dependency that opens the local database outside a server lifespan (get_engine_factory's CLI/MCP fallback), gated on skip_local_initialization like initialize_app so cloud/stateless deployments are untouched. Regression tests: a pristine-HOME subprocess `bm status` must succeed and see `main`; the once-guard runs reconciliation a single time and skips cloud mode. Fixes #1334 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017STCpbNsYjZgUdftxgEAZ4 Signed-off-by: phernandez <paul@basicmachines.co>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: da5424fb55
ℹ️ 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".
Two tests used "project validation fails against the empty local DB" to prove --local routing, and faked get_or_create_db with stand-ins. The CLI/MCP local ASGI client now seeds config projects into the database (#1334), so validation succeeds: assert routing directly (no workspace lookup, ASGI transport) and skip reconciliation where the engine is a stand-in. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017STCpbNsYjZgUdftxgEAZ4 Signed-off-by: phernandez <paul@basicmachines.co>
synchronize_projects() created a local projects row for every config entry regardless of mode, and let the database default overwrite the configured one. With reconciliation now running on every CLI process (#1334) that undid `set-cloud` on each run: the row set-cloud had deliberately deleted came back, and a cloud default could be flipped to a local project. Skip cloud-mode entries when seeding rows and leave a cloud-mode default alone; the database default is only the local fallback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017STCpbNsYjZgUdftxgEAZ4 Signed-off-by: phernandez <paul@basicmachines.co>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cc8b54a97e
ℹ️ 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".
Reconciliation now skips cloud-mode entries, so the routing test's cloud project has no local row and validation 404s. That miss — not a workspace error — is the signal that routing stayed local; assert it while keeping the workspace-lookup guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017STCpbNsYjZgUdftxgEAZ4 Signed-off-by: phernandez <paul@basicmachines.co>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e78be5000a
ℹ️ 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".
…copy Codex round two on #1354: `add --cloud --local-path` writes a cloud-mode entry that still owns a local sync directory, and local-side commands (`project ls --local`, startup watching) look it up in the projects table. Skip only cloud-only entries — cloud mode with no local_sync_path — when seeding rows and when protecting the configured default. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017STCpbNsYjZgUdftxgEAZ4 Signed-off-by: phernandez <paul@basicmachines.co>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c78d8fbf6a
ℹ️ 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".
…after failure Codex round three on #1354: - set-cloud blanked `path` but left `local_sync_path` (and bisync flags) behind, so an entry created with `add --cloud --local-path` and then cut over still read as a local copy and reconciliation recreated the row set-cloud had just dropped. The cutover now clears the sync metadata too, which is what "purely cloud" already promised. - The once-per-process guard recorded the database path before reconciliation ran, and reconcile_projects_with_config() swallows synchronize failures, so a transient error on the first request pinned a long-lived MCP process to an unseeded database. It now reports success, and the path is only retired after a completed run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017STCpbNsYjZgUdftxgEAZ4 Signed-off-by: phernandez <paul@basicmachines.co>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a143d25836
ℹ️ 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".
…alized cloud default Codex round four on #1354: a local row an earlier reconciliation had recreated for a cut-over project never converged away, because the deletion pass treated every config key as a local claim; and a default written in display form (`Research Cloud`) stopped matching its normalized key, so config validation reset it to the first local project. Cloud-only entries are now absent from the local-row set for both passes, and the default is renamed together with its key. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017STCpbNsYjZgUdftxgEAZ4 Signed-off-by: phernandez <paul@basicmachines.co>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 662964b71e
ℹ️ 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".
Codex round five on #1354: older cloud-mode entries record the local sync copy only in `path`, and `_require_local_sync_path` still honors that fallback. Treating them as cloud-only would drop their local row and make local-side commands report the project missing. A cloud entry is cloud-only only when it has neither local_sync_path nor path — the state set-cloud and `add --cloud` without --local-path produce. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017STCpbNsYjZgUdftxgEAZ4 Signed-off-by: phernandez <paul@basicmachines.co>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 00d5206e23
ℹ️ 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".
Codex round six on #1354: a legacy cloud-only entry can carry the remote slug (`research`) in `path`. Treating any non-empty path as a local copy would seed a row whose base resolves against the process cwd. Mirror is_locally_syncable and _require_local_sync_path: the fallback path must be absolute. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017STCpbNsYjZgUdftxgEAZ4 Signed-off-by: phernandez <paul@basicmachines.co>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f057cd46b8
ℹ️ 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".
Codex round seven on #1354: ProjectEntry accepts a relative local_sync_path, and _require_local_sync_path rejects it, so reconciliation must not seed a row for it either. Apply that function's rule as written — local_sync_path, falling back to path, absolute only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017STCpbNsYjZgUdftxgEAZ4 Signed-off-by: phernandez <paul@basicmachines.co>
|
@codex review |
|
Codex Review: Didn't find any major issues. What shall we delve into next? Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
…copy Codex round two on #1354: `add --cloud --local-path` writes a cloud-mode entry that still owns a local sync directory, and local-side commands (`project ls --local`, startup watching) look it up in the projects table. Skip only cloud-only entries — cloud mode with no local_sync_path — when seeding rows and when protecting the configured default. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017STCpbNsYjZgUdftxgEAZ4 Signed-off-by: phernandez <paul@basicmachines.co>
…after failure Codex round three on #1354: - set-cloud blanked `path` but left `local_sync_path` (and bisync flags) behind, so an entry created with `add --cloud --local-path` and then cut over still read as a local copy and reconciliation recreated the row set-cloud had just dropped. The cutover now clears the sync metadata too, which is what "purely cloud" already promised. - The once-per-process guard recorded the database path before reconciliation ran, and reconcile_projects_with_config() swallows synchronize failures, so a transient error on the first request pinned a long-lived MCP process to an unseeded database. It now reports success, and the path is only retired after a completed run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017STCpbNsYjZgUdftxgEAZ4 Signed-off-by: phernandez <paul@basicmachines.co>
…alized cloud default Codex round four on #1354: a local row an earlier reconciliation had recreated for a cut-over project never converged away, because the deletion pass treated every config key as a local claim; and a default written in display form (`Research Cloud`) stopped matching its normalized key, so config validation reset it to the first local project. Cloud-only entries are now absent from the local-row set for both passes, and the default is renamed together with its key. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017STCpbNsYjZgUdftxgEAZ4 Signed-off-by: phernandez <paul@basicmachines.co>
Codex round five on #1354: older cloud-mode entries record the local sync copy only in `path`, and `_require_local_sync_path` still honors that fallback. Treating them as cloud-only would drop their local row and make local-side commands report the project missing. A cloud entry is cloud-only only when it has neither local_sync_path nor path — the state set-cloud and `add --cloud` without --local-path produce. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017STCpbNsYjZgUdftxgEAZ4 Signed-off-by: phernandez <paul@basicmachines.co>
Codex round six on #1354: a legacy cloud-only entry can carry the remote slug (`research`) in `path`. Treating any non-empty path as a local copy would seed a row whose base resolves against the process cwd. Mirror is_locally_syncable and _require_local_sync_path: the fallback path must be absolute. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017STCpbNsYjZgUdftxgEAZ4 Signed-off-by: phernandez <paul@basicmachines.co>
Fixes #1334 (regression of #974).
Problem
A fresh install seeds
mainintoconfig.json, but onlyinitialize_app()— run by the API/MCP server lifespans and a handful of CLI commands — reconciles config projects into theprojecttable.cli/app.pyskips it forproject,status,tool,sync,reindex, … i.e. nearly every first command a new user runs. Result on a clean profile (reproduced locally before this change):#974's fixes (#985/#987/#990) promote an existing DB project when the config default is missing, but a fresh DB has none to promote, and #990's message points at the one command that can't work.
Fix
get_engine_factory's non-lifespan fallback — the path every CLI/MCP local ASGI request takes to open the database — now callsreconcile_projects_with_config_once(), a once-per-process wrapper around the samereconcile_projects_with_config()the servers run, gated onskip_local_initializationexactly likeinitialize_app()so cloud/stateless deployments are untouched. This is what the comment incli/app.py("API-using commands handle initialization via deps.py") already claimed happened.Tests
tests/cli/test_fresh_install.py— pristineHOME, subprocessbm statusmust exit 0, not say "Project not found", and listmain.tests/services/test_initialization_reconcile_once.py— reconciles once per database per process; skipped underBASIC_MEMORY_CLOUD_MODE.ruff/tyclean;tests/cli/test_cli_exit.py(import-lightness + exit guards) still pass.🤖 Generated with Claude Code
https://claude.ai/code/session_017STCpbNsYjZgUdftxgEAZ4