Repository navigation
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 533c6d2. Configure here.
| Ok(current_project_processes(processes, ¤t_dir) | ||
| .next() | ||
| .is_some() | ||
| || !docker::discover_project_postgres_blocking(¤t_dir).is_empty()) |
There was a problem hiding this comment.
Stopped Postgres recreates project state
Medium Severity
The new “create state only if something is still running” gate uses discover_project_postgres_blocking, which lists every labelled container (all(true)), not just running ones. postgres stop leaves the container in place, so after .clickhouse/ is deleted any read path that calls lock_recovered_metadata (server list, udf list, local remove, client, and start pre-checks) recreates .clickhouse/servers/ for a stopped instance. That reintroduces #1077 for the common leftover-container case, and it contradicts the comments and docs that recovery should run only when something is still running.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 533c6d2. Configure here.
`server::lock_metadata()` created `.clickhouse/servers/` to open its lock file, so listing, stopping, removing, connecting to or deploying UDFs to servers left `.clickhouse/servers/.metadata.lock` in any directory. Add `MetadataLock::acquire_existing_at` / `lock_existing_metadata()`, which returns `None` when the servers directory does not exist. Opening the lock file is the existence check, so there is no check-then-use window: a concurrent start either created the directory first (and is waited for through the lock) or is ordered after the reader, which sees no servers. Server list/stop/stop-all/remove/dotenv, managed `local client`, the `local udf` server lookup, postgres stop/stop-all/remove/client/dotenv, `local remove <version>`'s in-use guard and the start pre-checks use it and return their existing empty or not-found results. Only server start, postgres start and `local init` create the directory. Postgres lookups share one lock-and-resolve helper so the not-found error stays identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ng (#1077) Read paths returned early when `.clickhouse/servers/` was absent, so a still-running project-labelled Postgres container or a ClickHouse process started from the project was no longer recovered after `rm -rf .clickhouse`: `postgres stop-all` reported nothing running and `postgres stop default` reported server_not_found while the container kept its port. Add `server::lock_recovered_metadata()`. With server state it locks and recovers as before. Without it, it runs the same best-effort discovery (ClickHouse process scan plus the Docker label lookup, which tolerates Docker being unreachable) writing nothing; only when that finds an instance of this project does it create the directory, take the lock and recover under it, so the command behaves as it did before #1077. Recovery re-checks under the lock and skips anything a concurrent command recorded since. An empty directory still gains no `.clickhouse/`. Every lookup that recovered orphans now goes through the helper, and the Docker recovery is split into a discovery step it shares. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
533c6d2 to
aff3978
Compare


Closes #1077.
Summary
Read-only and lookup
localcommands no longer create.clickhouse/servers/.metadata.lockin the current directory. Onlylocal server start,local postgres startandlocal initcreate project state now.MetadataLock::acquire_existing_atandserver::lock_existing_metadata()returnNonewhen.clickhouse/serversdoesn't exist. The lock file is opened withoutcreate_dir_all, andENOENTmaps toNone. There's no separate existence check, so there's no race: a concurrentstarteither created the directory first, in which case the reader waits on the lock, or it comes after the reader, which correctly sees no servers.local server list,stop,stop-all,removeanddotenvlocal clientlocal udf list,reload,removeanddeploylocal postgres stop,stop-all,remove,clientanddotenv, through a sharedlock_and_resolve_pg_targetlocal remove <version>and in installserver start's pre-checks, so a start that fails before installing leaves nothing behindorigin/main: exit codes, stdout and stderr were identical. The only difference is that main creates the lock directory.Visible changes
stoporremovewith no name after alist. These now report "no project state", becauselistno longer leaves.clickhouse/behind. The state-machine tests are updated to match..clickhouse/serversis a file. The error now says "open the server metadata lock file" instead of "create the server metadata lock directory"..clickhouse/serversis missing, read commands first run a discovery-only pass that writes nothing. Only if it finds a running ClickHouse process or a labelled Postgres container for this project do they create the directory, lock, and recover as before. Example: afterrm -rf .clickhousewith a Postgres container still running,stop-allstill finds and stops it. Recovery runs again under the lock, so a concurrentstartisn't duplicated. An empty non-project directory still gets nothing.Notes
init_after_server_list_creates_runtime_gitignorenow asserts thatlistleaves nothing, then seeds the leftovers an older release would have left.local udf deploy|list|remove|reload) #1068's dotfile filter inlooks_like_project_rootis kept. The lock file still remains after the last server is removed, and interrupted temp files are dotfiles too. Only its comment changed.Tests
tests/local_readonly_no_project_state_test.rs, 9 tests. Each command runs in an empty directory, in an init-only directory, and aslocal remove <version>. The file is classified in both CI scripts and in the mirrored test set.--no-default-featurescheck,cargo test -p clickhousectland the Python classifier tests. I also checked by hand that the issue's repro leaves an empty directory.🤖 Generated with Claude Code
Note
Medium Risk
Release workflow and self-update version resolution now depend on
latest.txt; a failed or stale publish affects all updaters, while the org module split is large but should be behavior-preserving if tests pass.Overview
This PR bundles release discovery, Cloud API/client updates, and a cloud CLI layout refactor (the attached PR title/description about local read-only commands is not reflected in this diff).
Release: Adds a Publish latest.txt job after uploads that writes
clickhousectl/latest.txtto builds.clickhouse.com with forward-only semver (re-tagging an older release does not downgrade). The job verifies the CDN-served file and fails the release if it does not match. Docs and the release skill now sayclickhousectl updateandinstall.shread that manifest (GitHubreleases/latestonly as fallback).clickhouse-cloud-api: Refreshes the OpenAPI spec and models for ClickStack log sources (
eventNameExpression,severityNumberExpression) and MongoDB ClickPipe table mappings (mongoConfig.persistDeletedDocuments). Conversion, README, and unit/smoke tests cover round-trips and optional fields.clickhousectl: Deletes the monolithic
organizations.rsin favor of theorganizations/directory pattern (with blame-ignore entries for similar splits ofservices/andpostgres/). Drops theservice_querymodule re-export fromcloud/mod.rs. ClickPipes--table-mapping-jsonacceptsmongoConfig; ClickStack source file tests include the new log expressions. Agent docs (AGENTS.md, add-cli-command, cli-help-text) describe the directory layout and stricterconvert_error_for_lookupguidance.Reviewed by Cursor Bugbot for commit aff3978. Bugbot is set up for automated code reviews on this repo. Configure here.