USAGE TELEMETRY EVENTS
The Swamp CLI records one usage telemetry event per command invocation. This page describes the transport, every field of the event, what the ingest service adds, and the redaction policy. For opt-outs, the spool and delivery, see the Usage Telemetry reference. For the reasoning behind these choices, see About usage telemetry.
The envelope and redaction on this page were verified against Swamp
20260930.201417.0-sha.69135e9d.
Transport
Events
| Source | Events |
|---|---|
| Any CLI command | One cli_invocation event. |
swamp workflow run |
One event for the command, plus one child event per model-method step. Children carry parentInvocationId. |
swamp serve |
One event per workflow run and per model-method run it executes, plus one event for the server process when it exits. |
The model-method steps inside a nested workflow emit child events, and at any
depth those children report the top-level run's runId and workflowName. A
step still running when the run ends is recorded as an error.
Events are recorded for commands the CLI parses.
Request
| Property | Value |
|---|---|
| Method | POST |
| URL | <endpoint>/ingest |
| Content type | application/json |
| Single event | The event object, bare |
| Batch | { "events": [ ... ] } |
| Batch limits | 500 events and 1 MiB per request |
| Delivered | HTTP 202. Other statuses keep the entry in the spool for retry. |
Headers:
| Header | Sent | Value |
|---|---|---|
User-Agent |
Always | swamp-cli/<swampVersion> |
x-api-key |
When an API key is stored by swamp auth login |
The stored API key |
The request body identifies the install and the repository by random UUIDs. See
What the ingest service adds for what the
server attaches when x-api-key is present.
Flushing
| Process | When | Entries | Time limit |
|---|---|---|---|
| CLI command | At the end of every command | Up to 25 unsent entries, oldest day first | 2 seconds |
swamp serve |
Every 60 seconds | Up to 20 batches of unsent entries | 10 seconds |
When a flush fails, the CLI prints a warning and keeps the entries in the spool for the next invocation:
[WRN] swamp·cli: Telemetry flush failed (network error) — entries are queued locally and will retry on the next invocationWhen swamp serve fails to deliver a batch repeatedly, it retries the entries
one at a time and quarantines entries that keep failing.
Endpoint
The endpoint is resolved in this order. The first match wins. Each event is stamped with the endpoint in effect when it was recorded; see Delivery.
| Priority | Source |
|---|---|
| 1 | SWAMP_TELEMETRY_ENDPOINT environment variable |
| 2 | telemetryEndpoint in .swamp.yaml |
| 3 | http://localhost:8080 when the auth server is localhost, 127.0.0.1, or [::1] |
| 4 | https://telemetry.swamp-club.com |
Local spool
Every entry is written to disk before any send is attempted, in the spool under the config directory.
Each invocation is one file named telemetry-<YYYY-MM-DD>-<id>.json, where
<id> is the event's properties.id. The file holds the event's properties
object. The envelope, distinct_id, and $repo_id are added when the entry is
sent.
| File | Removed |
|---|---|
| Sent entry | Immediately |
Sent entry, with telemetryKeepFlushed: true |
Renamed to .flushed.json, deleted after 2 days |
| Unsent entry | After 7 days, by a CLI command that succeeds |
Quarantined entry (.quarantined.json) |
After 30 days. Written by swamp serve. |
Envelope
{
"event": "cli_invocation",
"distinct_id": "string",
"insert_id": "string",
"properties": {
"id": "string",
"invocation": {
"command": "string",
"subcommand": "string",
"commandPath": ["string"],
"args": ["string"],
"optionKeys": ["string"],
"globalOptions": ["string"]
},
"result": {
"status": "success | error | user_error",
"errorType": "string",
"errorMessage": "string",
"exitCode": 0
},
"startedAt": "ISO-8601",
"completedAt": "ISO-8601",
"durationMs": 0,
"swampVersion": "string",
"denoVersion": "string",
"platform": "string",
"invocationContext": {
"configuredAiTools": ["string"],
"detectedAiTool": "string",
"agentSessionDetected": false,
"isInteractive": false,
"externalDatastoreConfigured": false,
"datastoreType": "string",
"externalVaultConfigured": false
},
"parentInvocationId": "string",
"workflowContext": {
"workflowName": "string",
"runId": "string",
"jobName": "string",
"stepName": "string",
"modelType": "string",
"executor": "string"
},
"triggerSource": "schedule | webhook | api",
"initiatedBy": "string",
"$repo_id": "string"
}
}Optional fields appear when they have a value.
Top-level fields
| Field | Type | Required | Value |
|---|---|---|---|
event |
string | Yes | Always cli_invocation. |
distinct_id |
string | Yes | Random per-install UUID from <config>/identity.json. Falls back to $repo_id when identity.json is unavailable. |
insert_id |
string | Yes | Random per-event UUID, equal to properties.id. The ingest service drops a repeated insert_id. |
properties |
object | Yes | The fields below. |
properties
| Field | Type | Required | Value |
|---|---|---|---|
id |
string | Yes | Random per-event UUID. |
invocation |
object | Yes | See invocation. |
result |
object | Yes | See result. |
startedAt |
string | Yes | ISO-8601 timestamp. |
completedAt |
string | Yes | ISO-8601 timestamp. |
durationMs |
number | Yes | Milliseconds between startedAt and completedAt. |
swampVersion |
string | Yes | The CLI version, e.g. 20260930.201417.0-sha.69135e9d. |
denoVersion |
string | Yes | The embedded Deno runtime version, e.g. 2.9.7. |
platform |
string | Yes | Operating system: linux, darwin, or windows. |
invocationContext |
object | No | See invocationContext. |
parentInvocationId |
string | No | Present on workflow child events. The id of the swamp workflow run event. |
workflowContext |
object | No | Present on workflow child events and swamp serve workflow runs. See workflowContext. |
triggerSource |
string | No | Present on swamp serve runs. schedule, webhook, or api. |
initiatedBy |
string | No | Present on swamp serve runs. The principal that started the run: user:<id>, service:<name>, worker:<name>, or ghost. |
$repo_id |
string | No | Random per-repository UUID from repoId in .swamp.yaml. Present when the command resolved a repository. |
invocation
| Field | Type | Required | Value |
|---|---|---|---|
command |
string | Yes | First word of the command, e.g. model. |
subcommand |
string | No | Second word of the command, e.g. method. |
commandPath |
string[] | No | The resolved command from the CLI's own command tree, e.g. ["model","method","run"]. |
args |
string[] | Yes | Positional arguments after the command words, redacted as described in Redaction. |
optionKeys |
string[] | Yes | Flag names as typed, e.g. --input. An undeclared flag is recorded as <UNKNOWN_OPTION>. |
globalOptions |
string[] | Yes | Global flags used: --json, -q, --quiet, -v, --verbose, --no-telemetry, --no-color, --show-properties. |
swamp serve model-method runs record subcommand as method run and args
as [<model>, <method>]. CLI commands and workflow child events record
subcommand as method and args as ["run", <model>, <method>].
result
| Field | Type | Required | Value |
|---|---|---|---|
status |
string | Yes | success, user_error (the error is a user error), or error. |
exitCode |
number | Yes | 0 on success, 1 on error. |
errorType |
string | No | The error's class name, e.g. UserError. |
errorMessage |
string | No | The first line of the error message, after redaction. |
invocationContext
| Field | Type | Required | Source |
|---|---|---|---|
configuredAiTools |
string[] | No | tools in .swamp.yaml. Present when the command resolved a repository. |
detectedAiTool |
string | No | claude, cursor, kiro, opencode, codex, or pi, derived from the variables below. |
agentSessionDetected |
boolean | Yes | Derived from the variables below. |
isInteractive |
boolean | Yes | Whether stdin is a terminal. |
externalDatastoreConfigured |
boolean | Yes | Whether datastore.type in .swamp.yaml is set to anything other than filesystem. |
datastoreType |
string | No | datastore.type in .swamp.yaml. |
externalVaultConfigured |
boolean | Yes | Whether any vault in the repository uses a provider other than local_encryption or mock. |
detectedAiTool and agentSessionDetected are derived from whether these
environment variables are set, and from their values: CLAUDECODE,
CLAUDE_CODE_ENTRYPOINT, TERM_PROGRAM, CURSOR_TRACE_ID,
AGENT_CONTEXT_OUT, OPENCODE, CODEX_SANDBOX_NETWORK_DISABLED,
CODEX_SANDBOX, PI_CODING_AGENT, AGENT, AI_AGENT, IS_AGENT. The event
carries the derived tool name and boolean.
workflowContext
| Field | Type | Required | Value |
|---|---|---|---|
workflowName |
string | Yes | Name of the top-level workflow. |
runId |
string | Yes | Random per-run UUID of the top-level run. |
jobName |
string | Yes | Name of the job containing the step. |
stepName |
string | Yes | Name of the step. |
modelType |
string | No | Type of the model the step ran, e.g. command/shell. |
executor |
string | No | Where the step ran: loopback or a worker name. |
Identifiers
Every identifier in the envelope is a random UUID generated by Swamp.
| Identifier | Scope | Stored in |
|---|---|---|
distinct_id |
Per install | <config>/identity.json |
insert_id, properties.id |
Per event | The event |
$repo_id |
Per repository | repoId in .swamp.yaml |
runId |
Per workflow run | The workflow run record |
parentInvocationId |
Per invocation | The event |
What the ingest service adds
The ingest service stores each event's event, distinct_id, insert_id, and
properties, a receive timestamp, and the account details below. When a request
carries x-api-key, the service resolves the key and stores the result with
each event.
| Key type | Stored with each event |
|---|---|
| Personal API key | The account's username. |
| Collective API key | distinct_id replaced by collective:<collective id>. The collective's id and slug, and its members' user ids and usernames, added to properties. |
| No key | The username previously linked to the event's distinct_id, if a signed-in event from the same install has linked one. |
Redaction
Telemetry keeps what kind of thing was done and what it was called. Names are labels, and Swamp sends them as typed. Paths, values, and queries over data are content, and Swamp redacts them before the event is built.
| Kept as sent | Redacted before the event is built |
|---|---|
Command words at any depth (commandPath) |
Positional arguments outside the kept slots: <REDACTED> |
| Model, workflow, job, step, and vault names | Flag values, including --input values |
| Model types and method names, including private extensions | swamp vault put keys and values |
| Search queries | swamp data query predicates |
| Flag names | Everything after -- |
| Error class, and the first line of the error after redaction | Paths, home-directory usernames, and internal hostnames in error messages |
Argument redaction
Swamp sends a positional argument when it fills one of these named slots in the
command's definition: collective, data_name, definition_name, enabled,
extension, grant_id, method_name, model_id_or_name, model_or_type,
name, new_name, number, old_name, output_id,
output_id_or_model_name, query, report_name, run_id_or_workflow, slug,
step_name, token-id, type, vault_name, vault_name_or_id, version,
workflow_id_or_name, workflow_name. The argument key is sent for
swamp config commands.
Every other positional argument is replaced with <REDACTED>. A value typed
into a kept slot is sent as typed, whatever it contains.
swamp help sends the words that follow it while they resolve to real commands.
When the positional arguments fall outside the command's definition, Swamp
redacts all of them.
Error message redaction
errorMessage is the first line of the error, rewritten in this order:
- Exact occurrences of the filesystem paths the error names, redacted argument
values and their absolute forms, the working directory,
$HOME, and the repository directory are replaced. A path-like value becomes<PATH>. Any other value becomes<REDACTED>. file://URLs, quoted paths, and unquoted POSIX,~/, Windows drive, and UNC paths become<PATH>. A trailing:line:colis kept.- Remaining usernames in
/Users/<name>,/home/<name>, andC:\Users\<name>become<REDACTED>. - Hostnames ending in
.internal,.local,.lan,.corp,.intranet,.private, or.homebecome<REDACTED-HOST>.
Workflow child events and swamp serve events apply steps 2 to 4.
An event contains exactly the fields described in Envelope.
Worked example
This command, run inside a repository, points --server at an unreachable
internal host:
swamp model create command/shell other --server http://build01.corp:9090Error: Could not connect to ws://build01.corp:9090/: NetworkError: failed to connect to WebSocket: dns errorIt produces this event. UUIDs and timestamps vary.
{
"event": "cli_invocation",
"distinct_id": "cbd26c76-...",
"insert_id": "1cc948c9-...",
"properties": {
"id": "1cc948c9-...",
"invocation": {
"command": "model",
"args": ["command/shell", "other"],
"optionKeys": ["--server"],
"globalOptions": [],
"subcommand": "create",
"commandPath": ["model", "create"]
},
"result": {
"status": "user_error",
"exitCode": 1,
"errorType": "UserError",
"errorMessage": "Could not connect to ws://<REDACTED-HOST>:9090/: NetworkError: failed to connect to WebSocket: dns error"
},
"startedAt": "2026-09-30T22:26:26.132Z",
"completedAt": "2026-09-30T22:26:26.172Z",
"durationMs": 40,
"swampVersion": "20260930.201417.0-sha.69135e9d",
"denoVersion": "2.9.7",
"platform": "linux",
"invocationContext": {
"agentSessionDetected": true,
"isInteractive": false,
"externalDatastoreConfigured": false,
"externalVaultConfigured": false,
"configuredAiTools": ["claude"],
"detectedAiTool": "claude"
},
"$repo_id": "caec33e6-..."
}
}Swamp kept the model type and name, recorded the flag name --server, and wrote
the host in the error as <REDACTED-HOST>.
Other commands, as recorded in args:
| Command | args |
|---|---|
swamp vault put prod-secrets DB_PASSWORD hunter2 |
["prod-secrets","<REDACTED>","<REDACTED>"] |
swamp data query 'attributes.owner == "alice@example.com"' |
["<REDACTED>"] |
swamp extension search aws |
["aws"] |
swamp model method run my-model execute -- --secret=/etc/shadow |
["run","my-model","execute","<REDACTED>"] |
Verifying
To read exactly what is sent, point the endpoint at a local listener that
answers 202, run any command, and read the request body:
SWAMP_TELEMETRY_ENDPOINT=http://127.0.0.1:8080 swamp model get my-modelTo read each entry on disk before it is sent, point the endpoint at a closed
loopback port. The entry stays in <config>/telemetry/:
SWAMP_TELEMETRY_ENDPOINT=http://127.0.0.1:1 swamp model get my-modelOpt-outs
swamp config set telemetry.collection disabled, telemetryDisabled: true in
.swamp.yaml, SWAMP_NO_TELEMETRY, DO_NOT_TRACK, and --no-telemetry each
turn usage telemetry off. See
Opt-outs for the scope of each,
and Turn Off Usage Telemetry for the
steps.
Other outbound requests
| Request | When | Turn off |
|---|---|---|
| Update check | At most once per 24 hours, after a command succeeds, for non-JSON output, when automatic updates are off. A HEAD request to https://artifacts.swamp-club.com with a 5-second timeout. |
SWAMP_NO_UPDATE_CHECK=1 |
| Extension auto-resolve | When a command needs an extension type from a trusted collective that is missing locally. | trustedCollectives: [] in .swamp.yaml, with trustMemberCollectives left false |
whoami |
When SWAMP_API_KEY is set and the cached username or token scopes are missing. |
Unset SWAMP_API_KEY |
SWAMP_NO_UPDATE_CHECK accepts the same values as SWAMP_NO_TELEMETRY.
Extension auto-resolve runs inside a repository. See
Trust Configuration.