uxon emits one structured audit event per substantive operator
gesture to the host's platform log channel — journald native protocol
on systemd hosts, /dev/log syslog otherwise. Sink detection is
automatic and one-shot per process; the wire layer is stdlib-only.
This document is the event reference: what each event means, when
it fires, what fields it carries. For operational topology (where
events land, ACLs, rotation) and for copy-pasteable journalctl
recipes see ../guides/operate/forward-audit-to-collector.md.
For the config keys that gate the channel (audit.enabled,
audit.syslog_facility) see
configuration.md.
Every event carries the same envelope. Fields are written in the order shown; readers must not assume order, but writers commit to this set.
| Field | Type | Notes |
|---|---|---|
v |
int | Schema version. Currently 1. |
event |
string | Event name from the alphabet below. |
outcome |
string | One of ok, denied, error, not_found. Default ok. |
ts |
string | ISO-8601 UTC, millisecond precision (2026-05-06T10:11:12.345Z). |
host |
string | socket.gethostname() of the emitting host. |
uxon_version |
string | Package version of the emitter. |
caller_user |
string | Human operator (resolved through SUDO_USER if uxon was sudo'd). |
caller_uid |
int | UID of caller_user. |
launch_user |
string | The user uxon is operating as (post sudo -iu). Same as caller_user for self-only gestures. |
pid / ppid |
int | Emitter process and its parent. |
subcmd |
string | The uxon subcommand under which the event fired (attach, kill, run, …). |
ssh_client |
string | Present only on peer-inbound emits, copied from SSH_CONNECTION. Absent locally. |
On the journald native sink each envelope field is reachable as a
first-class FIELD=value selector (uppercased — journald wire
convention). On the /dev/log syslog fallback the body lands as a
single @cee: {…} JSON line readable via journalctl … -o json | jq.
journald additionally stamps its own metadata for free (_PID,
_UID, _AUDIT_LOGINUID, _CMDLINE, _HOSTNAME,
_SYSTEMD_UNIT); uxon does not duplicate those.
outcome is a closed enum:
| Value | When |
|---|---|
ok |
The operation completed as intended. Default if outcome is omitted. |
denied |
A policy / ACL gate refused the operation (sudo unreachable, enable_all_users_list = false, …). |
error |
The operation failed for a reason other than policy (subprocess non-zero, exception, ssh fail). |
not_found |
The named target did not exist (session id unknown, peer alias unknown). |
State-changing events emit on both success and failure. A refused
or errored attach / kill / launch is more interesting to an auditor
than a successful one, so the failure path always lands a record with
the appropriate outcome — querying OUTCOME != "ok" is a complete
sweep of everything that didn't go through.
| Event | When it fires | Extra fields beyond envelope | Outcomes observed |
|---|---|---|---|
cli.start |
Non-TUI subcommand startup, after argv parse. Skipped for --help, --version, and the bare-TUI invocation (which emits tui.open instead). |
flags (sanitised list), agents_enabled (list), enable_all_users_list (bool), audit_enabled (bool, always true — present so a query returning no cli.start records unambiguously means the channel was disabled; were it off, this event would not be emitted at all), allowed_roots_count (int), remote_hosts_count (int) |
ok |
tui.open |
TUI process started (uxon with no args on a TTY). |
(envelope only) | ok |
session.new |
uxon run / uxon new / TUI launch-new created and dispatched a session. |
agent (claude | codex | cursor), project (abs path), branch (or empty), session, dry_run |
ok, error |
worktree.create |
uxon created a git worktree (-w / TUI new-worktree). Emitted in addition to the launched session's session.new. |
agent, project (repo root, abs path), branch, path (worktree dir), base (local | remote), session |
ok |
session.attach |
Local uxon attach or TUI Enter on a local row. |
session, target_user |
ok, denied, error, not_found |
session.ended |
A wrapped subprocess (TUI launch) returned. | session, rc, wall_seconds, error (string ≤256 chars; only on outcome=error) |
ok, error |
session.kill |
Local uxon kill or TUI d on a local row. |
session, target_user, force (bool), dry_run, rc (int; only on outcome=error) |
ok, denied, error, not_found |
session.kill_all |
uxon kill-all or TUI D. |
target_users (list), killed_count (int), dry_run |
ok, error |
attach.remote.out |
Local TUI/CLI dispatching a peer attach over SSH (caller side of the wire). | peer_name, ssh_alias, target_user, target_session, correlation_id |
ok, error |
attach.remote.in |
Peer's own uxon attach invoked over SSH (peer side of the wire). Replaces session.attach on peer. |
target_user, target_session, correlation_id |
ok, denied, error, not_found |
kill.remote.out |
Local uxon kill --host / TUI d on a remote row (caller side). |
peer_name, ssh_alias, target_user, target_session, force, dry_run, correlation_id, rc (int) + error (string ≤256 chars) on outcome=error |
ok, error |
kill.remote.in |
Peer's own uxon kill invoked over SSH (peer side). Replaces session.kill on peer. |
session, target_user, force, dry_run, correlation_id |
ok, denied, error, not_found |
list.peek |
Local enumeration of other users' sessions (uxon list --all-users / TUI with the gate enabled). |
scope_users (list), scope_skipped (list) |
ok |
list.remote.in |
Peer's own uxon list --json invoked over SSH. Replaces list.peek on peer. |
scope (own | all-users), correlation_id |
ok, denied |
git.remote.create |
uxon new --git-remote <profile> reached the external-repo create step. |
profile, repo, creds_user, rc |
ok, error |
config.error |
Startup config load failed and main() is about to exit non-zero. |
path, error (first 256 chars) |
error |
When a gesture crosses an SSH boundary (uxon attach --host,
uxon kill --host, uxon list --host), two events are emitted —
one per side:
- Caller side emits
*.remote.out(attach.remote.out,kill.remote.out). Nolist.remote.outexists; the local enumeration emitslist.peekinstead. - Peer side, detected by
SSH_CONNECTIONin the env, emits*.remote.ininstead of the local equivalent.attach.remote.inreplacessession.attach,kill.remote.inreplacessession.kill,list.remote.inreplaceslist.peek— never both. This keeps the cross-host audit trail at one record per side, no double-counting.
For each remote gesture the caller generates a UUIDv4 and passes it
to the peer via an internal CLI flag (--audit-correlation-id <uuid>, hidden from --help). Both sides emit it under
correlation_id, so a single journalctl … CORRELATION_ID=<uuid>
query returns the full pair across the two hosts. Older peers
without the flag reject the SSH invocation outright — silent fallback
would lose the correlation property exactly when an operator is
debugging across hosts.
Set audit.enabled = false in config.toml to silence the channel
entirely (no events, no sink detection). There is no
environment-variable override — the only kill-switch is the config
table. See configuration.md for the
[audit] table reference.
The envelope and event alphabet are versioned by the v field
(currently 1). Within a major release line uxon will not remove
events or rename fields; new events and new optional fields may be
added. Breaking schema changes bump the major version and v.