CLAUDE.md is a symlink to this file. Edit AGENTS.md; never replace the symlink.
clickhousectl (chctl) is the CLI for ClickHouse and Postgres, local and in ClickHouse Cloud. Use --help to
learn the current command surface. Root README.md introduces the CLI; docs/ holds user guides, references,
and development docs. See CONTRIBUTING.md for documentation placement. The API library has its own README.
Do not duplicate user-facing documentation here.
cargo fmt --allbefore every commit (fmt.ymlrunscargo fmt --all --check; bulk formatting commits are in.git-blame-ignore-revs).cargo clippy -p clickhousectl -- -D warnings&&cargo test -p clickhousectl.- Keep telemetry-compiled-out building and linting, as CI does:
cargo check -p clickhousectl --no-default-features&&cargo clippy -p clickhousectl --all-targets --no-default-features -- -D warnings. - Library crates:
cargo clippy -p clickhouse-cloud-api -p clickhouse-openapi-analyzer --all-targets -- -D warnings&&cargo test -p clickhouse-cloud-api -p clickhouse-openapi-analyzer;python3 -m unittest discover -s scripts/tests -p 'test_*.py'. Ifdeprecated-fieldschanged, alsocargo check --workspace --all-features.
Done means: cargo fmt --all; both clippy configurations clean; tests pass for every crate touched;
classifier mappings updated if a file was added or renamed; the relevant guide/reference updated for user-visible behaviour;
work on a branch, with an associated issue and a PR.
crates/clickhousectl/— the CLI. All local logic; wrapsclickhouse-cloud-apifor cloud.crates/clickhouse-cloud-api/— typed Cloud API client, published to crates.io.crates/clickhouse-openapi-analyzer/— private OpenAPI/Rust drift tooling. Both library crates are governed bycrates/clickhouse-cloud-api/AGENTS.md; read it before touching either.- To resolve OpenAPI drift for the ClickHouse Cloud API, use the
openapi-drift-remediationskill (.agents/skills/openapi-drift-remediation/SKILL.md). - Update the API library on its own; add CLI exposure separately. The CLI need not cover 100% of the library's endpoints — be intentional.
- Project-local data lives in
.clickhouse/; globally installed ClickHouse binaries in~/.clickhouse/. OAuth tokens (~/.clickhouse/tokens.json) are the exception — global user identity, not project-scoped.
- New Cloud handlers go through
CloudClientwrapper methods co-located in each domain module, notclickhouse_cloud_api::Clientdirectly.src/cloud/client.rsowns the core client, credential precedence, error conversion, and response unwrapping. - One deliberate exception to wrapper error conversion:
run_service_query{,_bearer}insrc/cloud/services/client.rsreturn the raw library error and an unreadreqwest::Response, because the query paths stream the body and classify the error themselves beforeconvert_query_error. Do not copy that shape for other endpoints. - Cloud handlers support
--jsonunless there is good reason not to. JSON is emitted automatically when--jsonis passed or a coding agent is detected —json_output()inmain.rswrapsis_ai_agent::detect(). CloudError.kinddecides the top-level error:Auth(401, missing credentials; a 403 is not) → exit 4;Usage(input validation found at runtime) → clap-rendered, exit 2; elseGeneric. JSON mode prints every Cloud runtime failure, including auth and cancellation, viacloud::output::print_error; usage failures keep clap's text. Output mode never changes the exit code or the human message.- CLI-owned stdout uses
src/stdout.rs: crate-scopedprint!/println!andstdout::stdout()suppress only typedBrokenPipeerrors. Keep new manual writers on that handle; never suppress command/API failures or child exits. - Exit codes:
0success, elseError::exit_code()—1error,3cancelled,4auth required, andChildExit(code)passes a spawned child's status through. Clap uses2for usage errors.
CloudError.failure(src/failure.rs) is classified only byfailure::classify_api_error, reached through theCloudClient::convert_error*methods. Add the stage witherror.at_stage(FailureStage::…)at the boundary that owns it; first write wins, so an outer fallback never overwrites an inner stage.- Never derive a category from message text. Vocabularies are only
&'static strfrom an enum or an allowlisted status, which keeps SQL, identifiers, response bodies and credentials out of telemetry. - A boundary that rewrites a message must carry
failureacross (..errorin a struct literal, orwith_failure).
To add or change a local or Cloud command or flag, use the add-cli-command skill
(.agents/skills/add-cli-command/SKILL.md). Local clap definitions live in src/local/cli.rs. Each Cloud
domain keeps its definitions, handlers and tests together in src/cloud/<domain>.rs, or src/cloud/<domain>/ for
large domains (clap definitions in cli.rs, CloudClient wrappers in client.rs, one file per command family,
tests beside the code they test).
Use the cli-help-text skill (.agents/skills/cli-help-text/SKILL.md) whenever you add or change a command, a
flag, or any help text. It holds the help standard and its test rule.
Test coverage is non-negotiable. Test each behaviour in one layer: the cheapest one that reaches it, which is the unit layer by default. Where a unit test and a subprocess test overlap, keep the unit test.
- Clap parsing —
Cli::try_parse_fromtests next to each command definition (src/cli.rs, the owningsrc/cloud/<domain>.rsorsrc/cloud/<domain>/cli.rs,src/cloud/cli.rs,src/local/cli.rs). Assert flag names, types, defaults, repeatability. - Request builders — unit tests for
build_*_requesthelpers next to the owning cloud domain code, asserting on library request-struct fields with minimal + maximal inputs. - Cloud subprocess + wiremock —
tests/cli_request_shape_test.rs. Spawn the real binary against a local mock server. It is the only layer that runs handlers, so a test belongs here only if it exercises: a handler making two or more calls or a read-modify-write; a distinct error path's exit code or JSON error; process behaviour (stdout/stderr, closed pipes, files, env, credentials); auth-mode wiring, once rather than per command; or one wiring test per command family (method, path, org, builder body on the wire). Not here: input validation (including "fails before HTTP" when clap rejects), field-by-field bodies, per-command repeats of shared behaviour (convert_error, the OAuth write check), or wording. - Local subprocess — one
local_*binary per concern undercrates/clickhousectl/tests/, for what needs the real binary: files, processes, locks, signals, exit codes, JSON on stdout/stderr. Add a new file rather than growingcli_request_shape_test.rs, which is Cloud-only. - Pure logic — inline
mod testsblocks acrosssrc/for version resolution, auth precedence, output formatting, platform detection, and other module-local helpers. - No wording pins — anywhere: help, README, error messages or human output. Assert the exit code, error code
or kind, stream, JSON fields, and the identifiers a message must carry (the offending flag or value, an ID, a
suggested command, re-parsed through clap). Asserting that a secret is absent is fine. Help structure: see the
cli-help-textskill. - Not worth a test — clap, serde or std behaviour on its own (a derive parsing, a
PossibleValuesParserrejecting); flags that don't exist; assertions that cannot fail. Subprocess tests never wait out a production timeout; use the overrides insrc/timing.rs(docs/development.md).
- Pin all GitHub Actions deps to SHA hashes, not tags. Never populate secrets in Actions triggered by external PRs.
audit.ymlrunscargo audit --deny warningson manifest/lockfile PRs and every weekday; fix or bump an advisory rather than ignoring it. Dependabot (.github/dependabot.yml) opens weekly grouped Cargo and Actions updates.- Two path classifiers fail closed; both need an entry when a source or test file is added or renamed, or CI
breaks.
scripts/classify-cloud-integration.pymaps API-library source/test paths to theservice,postgres,organization,clickpipessuites (unknown paths select all suites);scripts/classify-install-integration.pyholdsINSTALL_EXACT_PATHS/INSTALL_PREFIXESfor the live local install matrix, verified bytest-cli.ymlandtest-install.ymlon PRs that touch the classifier or the CLI (scripts/tests/test_classify_install_integration.py). - Affected Cloud integration suites run only after the
run-cloud-integrationlabel is applied; theCloud integration decisioncheck passes by itself when none are affected. Rules:.github/CLOUD_INTEGRATION.md.
Use cargo add with the latest version and an explicit crate, e.g. cargo add -p clickhouse-cloud-api url.
Every crate declares the same rust-version; raise it in all three manifests, both READMEs and msrv.yml together.
Use the release skill (.agents/skills/release/SKILL.md); a release needs a manual step in another repo.
- Branch per feature/issue and use the PR workflow. PRs should have an associated issue.
- Keep root
README.mdto product capabilities, installation, and representative examples. Put user tasks indocs/guides/, complex behavior and output contracts indocs/reference/, and development workflows inCONTRIBUTING.md,docs/development.md, or the relevant repository skill. Update the existing document; do not add every new flag or command to the README. Keep safety constraints at the point of use and link new guides fromdocs/README.md. SeeCONTRIBUTING.mdfor documentation checks and writing conventions. - API-library-only changes, Rust caller migrations, and analyzer work belong in
crates/clickhouse-cloud-api/README.md; they do not require a root README or CLI user-guide change. - Keep
AGENTS.mdup to date when development practice changes materially.