WendyOS Docs
Detailed referenceClientsWendy cli

Analytics

We track analytics for our CLI's usage through a dedicated self-hosted telemetry service. This is opt-out, or through WENDYANALYTICS=false in your environment.

We track analytics for our CLI's usage through a dedicated self-hosted telemetry service. This is opt-out, or through WENDY_ANALYTICS=false in your environment.

How it works

When analytics are enabled, each tracked event is serialised to JSON and sent via an HTTP POST request. Delivery is best-effort: a network failure, timeout, or non-2xx response never changes the command's result.

The CLI sends cli_seen before command execution or argument validation, including help, version, unknown commands, and invalid flags. Unknown commands use the last recognized command path; the unknown text is never sent. command_executed records the outcome before optional update or completion prompts. Help and version invocations do not count as successful product operations. Pending events are flushed before sudo process replacement or a Windows elevation launch.

MCP tool calls emit command_executed events as each call finishes, using a command name such as wendy mcp device_info and command_root of mcp. The duration and success flag describe the individual call, including tool results that report an error and panics recovered by the MCP server. Calls to app-provided tools use the fixed name wendy mcp container_tool so app and tool names stay private. Tool arguments and result content are never sent. The same analytics opt-out and CI settings apply to MCP calls.

wendy doctor and wendy update --check skip analytics entirely. These read-only invocations also skip update and syntax-error cache writes and routine sudo ownership cleanup. Installation plans and privileged internal helpers also skip analytics. wendy analytics disable does not send an event about disabling analytics. An unreadable or malformed config prevents telemetry initialization, so a stored opt-out cannot be accidentally ignored.

wendy analytics status reports the effective state, including CI detection, the environment opt-out, and an analytics initialization failure. A saved enabled preference does not override these conditions.

Endpoint

Events are posted to:

https://wendy-cli-telemetry-114319063177.us-central1.run.app/v1/telemetry/events

Event payload

Every event is an anonymous JSON object. The fields sent are:

FieldTypeDescription
schema_versioninteger2 for the lifecycle-aware event contract
event_idstringRandom event UUID, allowing the receiver to discard duplicate delivery
occurred_atstringUTC event time recorded by the CLI, independent of delivery time
attempt_idstringRandom UUID shared by one invocation or tour and its child operations
anonymous_idstringStable random UUID stored in ~/.wendy/ — never tied to a real identity
eventstringEvent name, e.g. "command_executed"
command_namestringCanonical command path, e.g. "wendy device wifi connect"
command_rootstringTop-level command token
duration_msintegerCommand duration in milliseconds
successbooleanWhether the command completed without error
error_classstringBounded enum describing the error category (never free-form error text)
cli_versionstringCLI version string
osstringOperating system (GOOS)
archstringCPU architecture (GOARCH)
is_dev_buildbooleantrue when cli_version is "dev" or has a -dev suffix
propertiesobjectEvent-specific string values, sent only by events that define them (see Deploy events). At most 32 allowlisted entries of up to 64 bytes, or 128 bytes for canned remediation; invalid values are discarded

Privacy note: Flag values, positional arguments, file paths, hostnames, image and app names, and error message text are never included in telemetry payloads. Only the fields in this table are sent, and properties holds only the bounded values documented below.

Activation events

The installer emits install_started, install_progress, and install_finished. The outcome is completed, failed, or cancelled; declining an erase confirmation counts as cancellation. image_written records the completed media write, before optional provisioning. device_boot_verified requires the first-boot verifier to contact the expected agent and pass its requested version, board, identity and enrollment checks. The verifier does not check application readiness.

Runs emit run_started, run_progress, and run_finished. app_started is emitted immediately when the agent acknowledges container start, before attached logs end. app_ready is emitted only after the attached run's configured readiness check succeeds. A failed check emits app_readiness_failed with a canned diagnostic even when the attached command continues streaming logs. Detached runs do not claim readiness. Multi-service, Compose, fleet and provider paths do not yet all report these application milestones; command success must not substitute for a missing acknowledgement.

Lifecycle properties include the current stage and step, outcome, entrypoint (cli, mcp, or tour), interactive-terminal status, duration and a subprocess exit code when available. Run diagnostics retain unknown for checks not actually observed, including authentication and build prerequisites. Recognized project and selected/reachable target checks become true only after their corresponding operations succeed. Tour events may include the selected route (physical, simulator, or existing_device). Failures include a stable error_code and an error_detail selected from fixed remediation sentences. Unknown failures use step_failed and a diagnostic-report instruction; they do not assert a root cause. No arbitrary error text is sent.

MCP tools use an attempt ID per call. The run subprocess inherits that ID. Durable installation jobs preserve their attempt ID across worker processes and first-boot verification. IDs do not identify people or join browser sessions to CLI installations. Installation method is currently unknown; acquisition source and OS version are unavailable and are not inferred from shell environment variables.

Delivery remains best-effort, with no persistent offline queue. A start without a terminal event has an unknown outcome; it is not proof of abandonment. Analytics remain disabled by the same opt-out and CI settings for every event.

Legacy first_run means the first completed CLI invocation, and install_completed describes the Homebrew postinstall hook. These are not device setup or first deployment events. Version 2 marks delivered milestones as successful. Historical events keep their original semantics.

first_deploy_success is emitted by deployMetrics.markStarted after the agent acknowledges container start for an app deployment. The command-completion milestoneFor mapping no longer owns this event. Managed-robot provisioning does not emit it, and a successful command exit alone is insufficient.

Deploy events

wendy run and wendy watch send one deploy_completed event per deploy, in addition to the command's command_executed event. It is sent once a deploy path has been chosen; earlier failures, such as an invalid wendy.json or an unreachable device, are covered by command_executed alone. Managed-robot provisioning builds are never reported.

duration_ms and success describe the deploy up to the agent's confirmation that the container started, not the log session that follows. Paths without that confirmation (compose, multi-service, fleet builds, and fast-path runs that reuse a running container) cover the whole command.

Its properties hold only numbers and fixed values. A phase is reported only once it happened.

PropertyDescription
deploy_transportHow the image reached the device: chunk, registry, fastpath, buildhost, multiservice, native, xcode, swiftpm, swift, or compose
deploy_startedtrue when the agent confirmed the container started
deploy_fallbackWhy a registry push was used: unimplemented, transport, or other after a failed chunk upload; darwin_agent, deploy_only, chunking_off, or not_attempted when no chunk upload was tried
deploy_target_platformTarget platform, such as linux/arm64 or linux/arm/v7; unrecognized values are sent as other
deploy_device_typeBoard type reported by the device's OS, such as jetson-orin-nano
deploy_build_ms, deploy_push_ms, deploy_upload_ms, deploy_device_prepare_ms, deploy_start_msChunk deploys: build, whole push, upload, device-side image preparation, and container start times in milliseconds
deploy_bytes_sent, deploy_chunks_sent, deploy_chunks_total, deploy_layers_total, deploy_layers_reusedChunk deploys: bytes and chunks uploaded, chunks and layers in the image, and layers the device already had
deploy_compressionChunk deploys: gzip or none
deploy_build_push_msRegistry deploys: build and push time in milliseconds
deploy_chunk_attempt_msTime spent on a chunk upload that fell back to a registry push
deploy_image_bytesImage size in bytes

Error categories

Failed commands report a fixed error_class. Shared operations use the same categories across commands, including run, watch, build, and device commands.

CategoryScenario
build_failedAn image, Swift, Xcode, or provider build failed. Fused build-and-push commands also use this category when the subprocess cannot distinguish the two stages.
builder_unavailableDocker, Apple Container, a Swift toolchain, or builder setup is unavailable or failed to start.
tool_not_foundA required executable could not be found.
config_invalidInvalid run options, missing build files, unsupported project detection, or failed loading/validation of wendy.json.
cli_usageFlag parsing rejected an unknown flag, missing value, invalid value, or invalid syntax.
project_target_mismatchThe project cannot run on the selected target, architecture, or host platform.
no_deviceNo target was selected or no matching/enrolled device was found.
device_ambiguousMultiple devices match and an explicit name or ID is required.
device_offlineThe selected cloud device, or all enrolled cloud devices, are reported offline.
device_unreachableNo agent is listening, no authenticated endpoint answered, or a selected device has no reachable IP agent.
simulator_unavailableThe selected simulator could not be made available.
device_identity_mismatchThe selected device's identity does not match its saved pin.
device_org_mismatchThe device belongs to an organization for which the CLI has no credentials.
device_tls_rejectedThe device TLS handshake rejected the CLI's credentials.
device_auth_requiredA provisioned agent requires authentication that the CLI could not satisfy.
auth_required, auth_session_ambiguousCloud login is missing, or multiple sessions require an explicit choice.
auth_certificate_failedCloud certificate issuance/renewal returned a structured failure.
registry_auth, registry_unavailableRequired registry credentials/transport are unavailable, or the Mac agent has no reachable registry.
transfer_failedChunk upload, file sync, or a separate image push failed.
container_start_failedThe agent's create/start stream ended before confirming completion.
readiness_timeoutThe application's TCP readiness probe did not succeed in time.
user_cancelled, context_canceled, context_deadlineA picker or operation was cancelled, or its context deadline expired.
network_dns, network_timeout, network_refused, network_unreachable, connection_closedA typed network error identifies DNS, timeout, refused connection, unreachable network/host, or reset/broken pipe.
permission_denied, file_not_found, disk_fullAn underlying OS error identifies a local resource failure.
unexpected_eof, process_failedAn unclassified operation ended with a truncated stream or a subprocess exit failure.
otherThe error has no recognized category or typed cause. Its message is still never sent.

gRPC errors use grpc_unavailable, grpc_deadline, grpc_unimplemented, grpc_invalid_argument, grpc_not_found, grpc_already_exists, grpc_permission_denied, grpc_resource_exhausted, grpc_failed_precondition, grpc_aborted, grpc_out_of_range, grpc_internal, grpc_data_loss, grpc_unauthenticated, or grpc_unknown. gRPC cancellation uses context_canceled; unrecognized future status codes use grpc_other.

Cancellation and deadlines take precedence, followed by known command categories, then gRPC and OS/network causes. Wrapped and joined errors retain their causes. For a multi-service failure with several categories, the classifier reports the first matching category in its fixed priority order, not a majority vote.

These categories apply to events sent by updated clients. Historical events that only stored other or grpc_other cannot be reclassified without additional failure data.

The CLI stores a private, timestamp-only activation-state.json beside analytics_id after a successful install operation. A later run_started clears the waiting window. On a subsequent CLI launch, it emits next_launch_no_run_24h and next_launch_no_run_7d when each threshold has elapsed. Each threshold is acknowledged only after successful HTTP delivery, so failed sends can retry on later launches. These are observations of returning installations; they do not classify absent users as abandoned. The check follows analytics opt-out and creates no state when disabled. Delivery remains best-effort, and concurrent CLI processes can duplicate a threshold observation.

Opting out

Analytics can be disabled in several ways:

  1. Set the environment variable to override the saved preference:
    WENDY_ANALYTICS=false wendy <command>
  2. Save the disabled preference with:
    wendy analytics disable
  3. CI detection automatically disables analytics when a standard CI environment variable has a non-empty value. Even CI=false disables analytics. There is no opt-in escape hatch in CI.

On this page