Auto-detect agent contexts for JSON output; gh-style exit codes#222
Conversation
Emit JSON automatically when stdout is not a terminal or one of CLAUDECODE / CURSOR_AGENT / CODEX_SANDBOX is set, so agents and pipelines get structured output without passing --json. Exit codes now follow gh conventions: 0 success, 1 error, 2 cancelled, 4 auth required. CloudError carries a kind (Generic / Auth) so 401/403 responses and missing-credential paths route to Error::AuthRequired.
…assify token-save IO as generic error - Thread explicit --json through to start_server so JsonForegroundConflict only fires when the user actually passed --json. Auto-detected JSON in a non-TTY / agent context no longer breaks `local server start --foreground`. - Map ensure_fresh_tokens() failures to Error::Cloud (exit 1), not AuthRequired (exit 4). Its only error path is a filesystem write failure while persisting refreshed tokens — refresh-rpc failures are swallowed and tokens cleared — so re-auth wouldn't help. - Trim added comments / doc-comments to match the project's existing voice.
|
@kcmannem love the idea; dynamically adapting the experience for agents I've something I've been playing around with (even so far as completely changing the command layout for agents - some fun results in evals) - but I haven't played around with output formats yet. I assume most run Before merging, I'm going to run some comparative evals to see how models react to this change. |
Bug fix added the json_explicit param, pushing the count to 8. Matches the existing project pattern (see cloud/commands.rs, local/postgres.rs).
Replace the hand-maintained AGENT_ENV_VARS list (CLAUDECODE / CURSOR_AGENT / CODEX_SANDBOX) with is_ai_agent::detect(), the same detection already used for the outbound User-Agent in user_agent.rs. This keeps the two consistent and broadens coverage to every agent the crate knows about (the standard AGENT var, Claude Code, Cursor IDE + CLI, all Codex vars, Gemini CLI, Goose, Devin, …) instead of a narrow subset. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the "stdout is not a terminal" trigger from output-mode resolution. JSON is now emitted automatically only when --json is passed or a coding agent is detected; pipes and redirects stay human-readable unless --json is passed, matching gh/kubectl conventions. The non-TTY default silently changed output format for any `clickhousectl ... | grep` pipeline (it broke the project's own postgres integration tests, which grep markdown rows from piped output). Agents still get JSON via is_ai_agent::detect(); everyone else opts in. Updates README, CLAUDE.md, and the cloud after_help text to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The output-mode decision is now trivial (--json or a detected agent), so fold it into a small json_output() helper in main and drop the output_mode module. `server start --foreground` already ignores json — it streams the server's stdout/stderr and never emits a JSON summary — so make that explicit instead of erroring. This removes: - the JsonForegroundConflict error and its runtime check - the json_explicit plumbing that existed only to scope that check - the #[allow(clippy::too_many_arguments)] on start_server (now 7 args) Agents still get JSON automatically; everyone else opts in with --json. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cover the previously-untested logic behind gh-style exit codes: - convert_error: 401/403 -> Auth, other statuses and non-API errors -> Generic. - cloud_error_to_top_level / boxed_cloud_error_to_top_level: a CloudError routes by kind (Auth -> AuthRequired, Generic -> Cloud), survives the Box<dyn Error> downcast, and a non-CloudError falls back to Cloud. The downcast fallback test pins the contract that anything which isn't a concrete CloudError (e.g. a handler that stringifies before boxing) silently loses the exit-4 signal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ 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 8440191. Configure here.
output_mode.rs was folded into a json_output() helper in main.rs, and server start --foreground now ignores json instead of erroring. Point the developer note at the current code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The json_output() helper is small and self-explanatory; the detailed note didn't earn its place in the dev guide. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Non-interactive `cloud auth login` with only one of --api-key / --api-secret returned Error::Cloud (exit 1), while the same partial-flag condition in resolve_auth exits 4 (auth required). Align login to Error::AuthRequired so agents keyed on exit 4 for credential problems handle it consistently. Reported by Cursor Bugbot. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Pushed a round of review changes (commits JSON output mode
Exit codes
Docs: README, Follow-ups filed: #233 (return a concrete |
--json is already documented as a flag; drop the prose restatement and keep just the auto-JSON-for-agents line plus the exit codes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # CLAUDE.md # CLAUDE.md~HEAD # crates/clickhousectl/src/cloud/client.rs # crates/clickhousectl/src/cloud/mod.rs
There was a problem hiding this comment.
take a look at how we do agent detection when building the user agent string for http calls; we should do it the same way (I maintain a library for it that detects more agents already)
There was a problem hiding this comment.
There was a problem hiding this comment.

Summary
Mirrors the workos CLI behavior so agents and shell pipelines get machine-readable output and meaningful exit codes without callers having to opt in.
clickhousectlnow emits JSON to stdout when any of these hold:--jsonis passed, stdout is not a terminal, or one ofCLAUDECODE/CURSOR_AGENT/CODEX_SANDBOXis set. Resolution lives insrc/output_mode.rsand is computed once inmain/run_cloud, so all downstreamjson: boolplumbing stays unchanged.gh-style exit codes.Error::exit_code()now returns0success,1error,2cancelled,4auth required.CloudErrorcarries akind(Generic / Auth); 401/403 responses, missing creds, and the OAuth-only-can't-write block all route toError::AuthRequiredand exit with4.README.md,CLAUDE.md,cli.rsafter_help) document the new behavior.Test plan
cargo build -p clickhousectlcargo test -p clickhousectl— 283 + 37 tests pass, including new tests foroutput_mode::resolveandError::exit_codecargo run -- local list(non-TTY) → JSON without--jsonCLAUDECODE=1 cargo run -- local list→ JSONcargo run -- cloud service listwith no creds → exit4cargo run -- local use 99.99→ generic error, exit1Note
Low Risk
Behavior changes are limited to CLI output mode, exit codes, and error classification; no auth protocol or cloud API contract changes beyond clearer exit 4 for credential failures.
Overview
Adds agent-aware JSON output and
gh-style exit codes so scripts and coding agents can rely on machine-readable stdout and meaningful process exits without always passing--json.JSON: A new
json_output()helper turns on JSON when--jsonis set oris_ai_agent::detect()finds a known agent (same signal as the outbound User-Agent). Local and cloud dispatch use it instead of the raw flag;cloud auth statusfollows the same rule.local server start --foregroundwith--jsonno longer errors—the foreground path ignores JSON because it never prints a summary.Exit codes:
Error::exit_code()maps success/cancelled/auth to 0 / 2 / 4 (everything else 1);mainexits with that code.CloudErrorgainsCloudErrorKind(AuthvsGeneric): missing/partial API keys, no credentials, and API 401/403 becomeError::AuthRequired(exit 4); OAuth blocked on write commands and half-set login flags route there too. Cloud handlers that returnBox<dyn Error>preserve auth kind via downcast toCloudError.Docs (
README,AGENTS.md, cloudafter_help) describe auto-JSON for agents and the exit table.Reviewed by Cursor Bugbot for commit dc032b4. Bugbot is set up for automated code reviews on this repo. Configure here.