cli: first-run telemetry consent silently drops command generation
#1,273 opened on Jun 28, 2026
Repository metrics
- Stars
- (35 stars)
- PR merge metrics
- (PR metrics pending)
Description
[agent]
Agent: Claude Code (claude-sonnet-4-6) — caro-qa-agent
Problem
On the very first invocation that triggers command generation in a fresh environment (or after config reset), the telemetry consent UI fires correctly — but after the user's choice is recorded and the config is saved, no command is generated. Exit code is 0 (silent failure). The user must run the identical command a second time to get any output.
This is a critical new-user friction point: someone running caro -p 'show disk usage' --dry-run for the first time sees the consent screen, sees "Telemetry disabled. No data will be collected.", and then… nothing. Their command was never processed.
Reproduction
# Start from a fresh config state (or a fresh environment)
rm -rf ~/.config/caro/config.toml
cargo build --release --features embedded-cpu
CARO=./target/release/caro
# First invocation (triggers consent) - NO COMMAND OUTPUT:
$CARO -p 'list files in current directory' --dry-run 2>&1
# Output: [telemetry consent UI]
# Output: ✓ Telemetry disabled. No data will be collected.
# [NOTHING ELSE — exit 0]
# Second invocation (consent already persisted) - COMMAND GENERATED:
$CARO -p 'list files in current directory' --dry-run 2>&1
# Output: Command: echo 'Please clarify your request'
# Output: [Dry Run Mode output]
Expected vs Actual
Expected: After handling telemetry consent on first run, caro continues to generate and display the command.
Actual: After handling telemetry consent, the program exits with code 0 without generating any command output.
Environment
- caro version:
caro 1.4.0 (9311c9e 2026-06-13) - OS: Linux 6.18.5 x86_64 (remote QA sandbox)
- Test date: 2026-06-28
- Backend: static (no model downloaded)
- Stdin: non-TTY (piped from bash script)
Investigation
The consent flow in src/main.rs:3346-3376:
if user_config.telemetry.first_run && is_interactive_output && !cli.no_telemetry {
let consent = caro::telemetry::consent::prompt_consent();
user_config.telemetry.first_run = false;
user_config.telemetry.enabled = consent;
// show message, save config
}
The prompt_consent() function (src/telemetry/consent.rs:54-58) uses dialoguer::Confirm::interact().unwrap_or(false).
In a non-TTY environment (CI, scripts, QA sandboxes), dialoguer::Confirm::interact() returns Err because stdin is not a terminal. .unwrap_or(false) catches this, consent = false, and the disabled message is shown. Config is saved correctly. However, something in the program path after this block causes the command generation to be skipped on this first invocation only — the second invocation (with consent already persisted) works correctly.
Root cause candidates:
dialoguerleaves stdin in a consumed/drained state that affects downstream input handling- The
is_interactive_outputflag or some other session state is modified by the consent flow in a way that short-circuits the main command path - Non-TTY detection happens at a different code point after consent and gates command generation
Confirmed: Second invocation always works. Config is persisted after first run. So the issue is in the single-invocation path where consent fires, not in persistence.
Severity
P1 — every new user in a scripted/non-TTY environment (CI, shell scripts, --dry-run smoke tests, remote QA runners) silently loses their first command. Exit code 0 makes it appear successful. The advertised "first-time flow" is broken in automation contexts.
caro QA agent — daily rotation slot A (smoke), scheduled remote run 2026-06-28