🪝 yoink

Read the audit log

Merge the operator-side and on-host JSONL logs to see every state-changing op yoink performed, including deploys whose containers no longer exist.

yoink history reads container labels — once a container is docker rm'd, that line is gone. The audit log is yoink's durable record. Two files, one merged read path:

  • Operator side ($XDG_STATE_HOME/yoink/audit/events.jsonl, defaults to ~/.local/state/yoink/audit/) — fleet-wide events: RunStarted, RunFinished, validation/build failures that never reach a host.
  • Host side (/var/lib/yoink/audit/events.jsonl on each managed host) — per-host events: LockAcquired, LockReleased, ContainerStarted, ContainerCreated, DeployFailed, …

Each line carries an event_id (UUIDv7) so the merge view dedupes safely, and an origin field ("operator" or "host") so a glance at the table shows where each line came from.

Read everything from the last week

yoink audit log

Default window is 7d, default limit is 100 rows, sorted newest-first. The output:

ts                        origin    host                    deploy_id                 event                         details
2026-05-01T15:42:01.317Z  operator  —                       0190f0c7-c5b1-7c98-…      RunFinished                   up ok
2026-05-01T15:42:01.299Z  host      cax11.example.com       0190f0c7-c5b1-7c98-…      LockReleased
2026-05-01T15:41:58.612Z  host      cax11.example.com       0190f0c7-c5b1-7c98-…      ContainerCreated              docs docs-3fdc075b tag=sha-3fdc075
2026-05-01T15:41:42.005Z  host      cax11.example.com       0190f0c7-c5b1-7c98-…      LockAcquired
2026-05-01T15:41:42.005Z  operator  —                       0190f0c7-c5b1-7c98-…      RunStarted                    up services=[docs]

deploy_id is a UUIDv7 — one per yoink up invocation. Lexicographic sort orders runs by start time.

Filter by side

yoink audit log --origin operator   # only the local file
yoink audit log --origin host       # only the on-host SSH fetches

Use --origin host together with --host <ADDR> to focus on one host's local view of a deploy.

Reconstruct one run

yoink audit log --deploy-id 0190f0c7-c5b1-7c98-aa92-1d77b8e91f10
# or, since the prefix is unique:
yoink audit run 0190f0c7

Returns every event tagged with that run, across every host, ordered by timestamp. Use it after a deploy to confirm what actually happened, or when triaging a failure.

Filter by host, service, or event type

yoink audit log --host docs.example.com
yoink audit log --service api --event ContainerCreated --event DeployFailed
yoink audit log --since 30m --format json | jq 'select(.event == "DeployFailed")'

--format json emits one event per line (raw JSONL — no pretty-printing) so it pipes cleanly into jq, vector, or whatever else.

Find failed deploys

yoink audit log --event DeployFailed --since 30d

DeployFailed events carry the failed container's last log lines under log_tail, captured at the moment yoink gave up. The container is usually long gone from docker ps; the audit log is the only thing that still has the diagnosis.

Retention and rotation

Both the operator-side and per-host active files rotate at 5 MiB — renamed to events-<UTC-timestamp>.jsonl while a fresh one starts. yoink audit log reads both the active and rotated files when --since reaches further back than 24 hours.

To prune rotated files past a retention window:

yoink audit gc --keep 90d           # default retention
yoink audit gc --keep 30d --dry-run # preview before deleting
yoink audit gc --keep 30d --host docs.example.com

The active file is never touched. Schedule yoink audit gc from cron / a deploy hook if you have a strict retention requirement.

What's recorded vs. not

Recorded (every state-changing op):

  • RunStarted / RunFinished envelope on every yoink up, rollback, prune, secrets rotate.
  • LockAcquired / LockReleased per host — the gap between them is your hold time.
  • ContainerCreated / ContainerRemoved per replica — the forensic record of swaps.
  • DeployFailed with log_tail when a healthcheck never passes.
  • HookFinished with the exit code.
  • Progress events (PullStarted, NetworkReady, HealthcheckHealthy, …) for richer triage.

Not recorded:

  • Read commands (status, history, doctor, diff, tui, audit log itself, secrets show, secrets env). They emit nothing — your audit log invocation does not show up in its own output.
  • Operator identity beyond $USER@$HOSTNAME. There's no central identity provider; the field is informational only.
  • Secret values. Event payloads carry names (SecretsRotated.new_recipient), never values.

In the TUI

yoink tui opens to the dashboard; press a (or 7) to open the Audit pane. Same merged-and-deduped view, plus:

  • ↑↓ / j k — select row
  • Enter — toggle a detail panel below the table (full event_id, deploy_id, actor + git SHA, and the failed-deploy log_tail when applicable)
  • / — substring filter across host, event name, summary, deploy_id, and actor; Enter commits, Esc cancels editing
  • r — re-fetch (parallel SSH per host plus one local read for the operator log)
  • Esc — clears the active filter on first press, returns to Dashboard on second

yoink tui --mode audit jumps straight there.

Schema

See Audit event schema for the per-variant fields and JSON shape. The schema is versioned via the per-event v field; older readers can refuse newer lines instead of misreading them.

See also

On this page