Audit Log JSON Schema
This page is the field-by-field reference for the JSON objects produced by runok audit --json (one object per line, JSONL). Use it to write jq queries against audit logs without reading the runok source.
The output contains two record types, distinguished by the kind field:
- Decision entries (Top-Level Object) have no
kindfield. One is written per evaluation. - Ask resolution records (Ask Resolution Record) have
kind: "ask_resolution". One is written when the user approves anaskdecision in the agent’s permission dialog (requires the opt-in PostToolUse hook).
jq queries that only want decision entries can filter with select(.kind == null).
Top-Level Object
Section titled “Top-Level Object”Every decision-entry line of runok audit --json output is one AuditEntry object with the fields listed below.
Example entry:
{ "timestamp": "2026-03-13T19:31:00.090565+00:00", "command": "git push -f origin main", "action": { "type": "deny", "detail": { "message": "force push is forbidden", "fix_suggestion": "git push origin main" } }, "sandbox_preset": null, "default_action": "ask", "metadata": { "endpoint_type": "hook", "session_id": "abc-123", "cwd": "/home/user/project", "tool_name": "Bash", "hook_event_name": "PreToolUse", "tool_use_id": "toolu_01AbCdEfGh" }, "command_evaluations": [ { "command": "git push -f origin main", "action": { "type": "deny", "detail": { "message": "force push is forbidden", "fix_suggestion": "git push origin main" } }, "matched_rules": [ { "action_kind": "deny", "pattern": "git push -f|--force *", "matched_tokens": ["origin", "main"] } ], "eval_type": "primary", "argv": ["git", "push", "-f", "origin", "main"] } ]}timestamp
Section titled “timestamp”RFC 3339 timestamp in UTC, with sub-second precision and a +00:00 offset (e.g. 2026-03-13T19:31:00.090565+00:00), recording when the evaluation was performed. Note that the offset is written as +00:00, not Z — jq literal-string comparisons (select(.timestamp >= "...")) need to use the same form.
Type: str
Always present: Yes
command
Section titled “command”The input command string exactly as runok received it, before any shell parsing or compound-command splitting. For compound input this is the whole expression (a && b); for single input this is the same string as command_evaluations[0].command.
Type: str
Always present: Yes
action
Section titled “action”Final evaluation result for the input as a whole. For compound input, this is the aggregated decision across all branches (the strictest result wins: any deny makes the whole input deny, etc.). See Action Object for the shape.
Type: Action
Always present: Yes
sandbox_preset
Section titled “sandbox_preset”Name of the sandbox preset that was applied to this evaluation. null when no sandbox was applied or when multiple presets were merged (compound input where different branches matched different presets — the merged policy has no single canonical preset name). The preset name, when present, corresponds to a key under definitions.sandbox. See Sandbox merging for compound commands.
Type: str | null
Always present: Yes (may be null)
default_action
Section titled “default_action”The configured defaults.action value at the time of evaluation. null when no default was configured. See defaults.action for the possible values.
Type: "allow" | "ask" | "deny" | null
Always present: Yes (may be null)
metadata
Section titled “metadata”Session and context information about the invocation. See Metadata Object.
Type: Metadata
Always present: Yes
command_evaluations
Section titled “command_evaluations”Per-branch evaluation records, in source order. One entry per shell command extracted from command:
- A non-compound input (e.g.
git status) produces exactly one entry witheval_type: "primary". - A compound or pipelined input (e.g.
a && b,a || b,a ; b,a | b) produces one entry per branch, all witheval_type: "compound". - An input with no runnable command (comment-only, parse error) produces an empty array.
Each entry carries the rule-evaluation result (action, matched_rules) and the shell-level parse result (env, argv, redirects, pipe) side by side, so audit consumers can filter on the actual binary in one jq line:
runok audit --json | jq 'select(.command_evaluations[].argv[0] == "helmfile")'See CommandEvaluation Object for the shape of each entry.
Type: list[CommandEvaluation]
Always present: Yes (may be empty)
approved
Section titled “approved”Whether the ask was approved in the agent’s permission dialog, joined the same way as the ask ✓ marker in text mode (see Ask Resolution Record). Not part of the on-disk audit-log schema — computed only for this command’s output.
Type: bool
Present when: action.type is "ask". Omitted for every other entry.
recheck
Section titled “recheck”The entry’s command, re-evaluated against the config currently in effect (as opposed to action / command_evaluations, which are a snapshot from when the entry was decided). Not part of the on-disk audit-log schema — computed only for runok audit --recheck output. See Recheck Object.
Type: Recheck
Present when: the --recheck flag was passed. Omitted otherwise.
Action Object
Section titled “Action Object”Represents an evaluation result. The type field is a discriminator; detail is omitted for allow, and present (with type-specific keys) for deny and ask.
// allow{ "type": "allow" }
// deny{ "type": "deny", "detail": { "message": "force push is forbidden", "fix_suggestion": "git push origin main" }}
// ask{ "type": "ask", "detail": { "message": "are you sure?" } }When no rule matches, the configured default_action is applied directly: type is "allow", "deny", or "ask" accordingly. There is no separate "default" discriminator in the audit-log JSON.
The kind of action.
Type: "allow" | "deny" | "ask"
Always present: Yes
| Value | Meaning |
|---|---|
allow | The command is permitted. |
deny | The command is rejected. |
ask | The command requires user confirmation. |
detail.message
Section titled “detail.message”Optional message attached to the rule. For deny actions this is the rule’s message (see Denial Feedback); for ask actions this is the prompt shown to the user.
Type: str | null
Present when: type is deny or ask. The value may be null when the rule did not set message.
detail.fix_suggestion
Section titled “detail.fix_suggestion”Optional fix-suggestion attached to a deny rule. See Denial Feedback.
Type: str | null
Present when: type is deny. The value may be null when the rule did not set fix_suggestion.
Metadata Object
Section titled “Metadata Object”{ "endpoint_type": "hook", "session_id": "abc-123", "cwd": "/home/user/project", "tool_name": "Bash", "hook_event_name": "PreToolUse", "tool_use_id": "toolu_01AbCdEfGh"}endpoint_type
Section titled “endpoint_type”Which runok subcommand recorded this entry. Audit consumers can use this to distinguish hook invocations from explicit runok exec runs.
Type: "exec" | "hook"
Always present: Yes
| Value | Source |
|---|---|
exec | The user invoked runok exec directly. |
hook | An AI coding agent’s tool-use hook (e.g. Claude Code PreToolUse) invoked runok. |
runok check is a dry-run evaluator and does not write audit log entries, so it never appears here.
session_id
Section titled “session_id”Session identifier supplied by the calling environment, when available. For hook invocations from Claude Code, this is the Claude Code session ID. null when no session ID was provided.
Type: str | null
Always present: Yes (may be null)
Working directory at the time of evaluation. null when the working directory could not be determined.
Type: str | null
Always present: Yes (may be null)
tool_name
Section titled “tool_name”Hook-specific: name of the tool the agent was about to run (e.g. Bash, Read). null when endpoint_type is not hook.
Type: str | null
Always present: Yes (may be null)
hook_event_name
Section titled “hook_event_name”Hook-specific: name of the hook event (e.g. PreToolUse). null when endpoint_type is not hook.
Type: str | null
Always present: Yes (may be null)
tool_use_id
Section titled “tool_use_id”Hook-specific: ID of the tool call this evaluation belongs to, as supplied by the agent. The agent sends the same tool_use_id to the PreToolUse and PostToolUse hooks of one tool call, so this is the correlation key between an ask decision entry and its Ask Resolution Record. null when endpoint_type is not hook, and in entries written before this field existed.
Type: str | null
Always present: Yes (may be null)
Ask Resolution Record
Section titled “Ask Resolution Record”Written when the user approves an ask decision in the agent’s permission dialog. Recording these requires the opt-in PostToolUse hook (see Claude Code Integration); without it, no ask_resolution records appear.
Denials cannot be observed (see Claude Code Integration for why), so an ask entry without a resolution record means “denied or not yet decided”, not “denied”.
{ "kind": "ask_resolution", "timestamp": "2026-03-13T19:32:10.512345+00:00", "outcome": "approved", "tool_use_id": "toolu_01AbCdEfGh", "session_id": "abc-123", "cwd": "/home/user/project", "command": "terraform apply", "executed_command": "runok exec --sandbox restricted -- 'terraform apply'"}Record type discriminator. Always the literal "ask_resolution". Decision entries have no kind field.
Type: "ask_resolution"
Always present: Yes
timestamp
Section titled “timestamp”RFC 3339 timestamp in UTC of the approval (when the PostToolUse hook fired), in the same format as the decision-entry timestamp.
Type: str
Always present: Yes
outcome
Section titled “outcome”How the ask was resolved. Currently always "approved".
Type: "approved"
Always present: Yes
tool_use_id
Section titled “tool_use_id”Tool use ID of the approved tool call, matching metadata.tool_use_id of the correlated ask decision entry. null when neither the hook input nor the correlated entry carried a tool_use_id; correlation then fell back to session + command matching.
Type: str | null
Always present: Yes (may be null)
session_id
Section titled “session_id”Session ID from the PostToolUse hook input.
Type: str | null
Always present: Yes (may be null)
Working directory from the PostToolUse hook input.
Type: str | null
Always present: Yes (may be null)
command
Section titled “command”The original command, copied from the correlated ask decision entry. The record is self-contained: aggregating approvals by command needs no join back to decision entries.
Type: str
Always present: Yes
executed_command
Section titled “executed_command”The command the agent actually executed (tool_input.command of the PostToolUse input). Differs from command when the PreToolUse response rewrote the command via updatedInput — with runok that happens for sandbox wrapping, producing the runok exec --sandbox <preset> -- '<command>' form.
Type: str
Always present: Yes
CommandEvaluation Object
Section titled “CommandEvaluation Object”One entry per shell command extracted from the input. Higher-level shaping (resolving binary vs subcommand, normalising mise shims, classifying -n as boolean vs value-taking) is intentionally not done here because those rules differ per CLI and belong to the audit consumer.
{ "command": "FOO=x echo hi > /tmp/log", "action": { "type": "allow" }, "eval_type": "compound", "env": [{ "name": "FOO", "value": "x" }], "argv": ["echo", "hi"], "redirects": [ { "redirect_type": "output", "operator": ">", "target": "/tmp/log", "descriptor": null } ], "pipe": { "stdin": false, "stdout": true }}When variable resolution rewrote the branch, original_command carries the verbatim source:
{ "command": "git push --force", "original_command": "git push $F", "action": { "type": "deny", "detail": { "message": null, "fix_suggestion": null } }, "matched_rules": [ { "action_kind": "deny", "pattern": "git push --force*", "matched_tokens": [] } ], "eval_type": "primary", "argv": ["git", "push", "--force"]}command
Section titled “command”The branch command as runok extracted it, with redirects stripped but the inline env prefix kept. This is the text rule evaluation actually used, so when variable resolution rewrote a $X / ${X} reference to its statically known value, this is the expanded text — see original_command for the verbatim source. For eval_type: "primary" entries where nothing was expanded, this is identical to the top-level command; when a rewrite did happen, it’s original_command (not command) that matches the top-level value.
Type: str
Always present: Yes
original_command
Section titled “original_command”The verbatim source text of this branch, before variable resolution rewrote command to its expanded form.
Type: str
Omitted when nothing was resolved (command already is the original text).
action
Section titled “action”Rule-evaluation result for this branch. See Action Object.
Type: Action
Always present: Yes
matched_rules
Section titled “matched_rules”Rules that matched for this branch, in match order. See RuleMatch Object.
Type: list[RuleMatch]
Omitted when empty.
eval_type
Section titled “eval_type”How this branch was extracted from the input.
Type: "primary" | "compound"
Always present: Yes
| Value | Meaning |
|---|---|
primary | Non-compound input. The single entry covers the whole input. |
compound | One branch of a compound or pipelined input (a && b, a || b, a ; b, a | b, etc.). |
Inline KEY=VALUE env prefix attached to this branch. See EnvVar Object.
Type: list[EnvVar]
Omitted when empty.
Command name plus arguments, with shell quoting resolved. argv[0] is the binary as written. Empty (and therefore omitted) when shell parsing could not produce an argv (AST leaf-text fallback path).
Type: list[str]
Omitted when empty.
redirects
Section titled “redirects”Redirect operators attached to this branch. See Redirect Object.
Type: list[Redirect]
Omitted when empty.
Pipeline position of this branch. See Pipe Object.
Type: Pipe
Omitted when both stdin and stdout are false (i.e. the branch is not part of a pipeline).
RuleMatch Object
Section titled “RuleMatch Object”{ "action_kind": "deny", "pattern": "git push -f|--force *", "matched_tokens": ["origin", "main"]}action_kind
Section titled “action_kind”The kind of rule that matched.
Type: "allow" | "ask" | "deny"
Always present: Yes
pattern
Section titled “pattern”The rule pattern string as written in runok.yml. See Pattern Syntax.
Type: str
Always present: Yes
matched_tokens
Section titled “matched_tokens”Tokens the wildcard portion of the pattern captured. For example, the pattern git push -f|--force * matched against git push -f origin main yields ["origin", "main"]. Empty for patterns with no wildcards.
Type: list[str]
Always present: Yes (may be empty)
EnvVar Object
Section titled “EnvVar Object”{ "name": "FOO", "value": "x" }Variable name.
Type: str
Always present: Yes
Variable value with shell quotes resolved. null for the bare KEY= cmd form (which clears the variable in the child process’s environment).
Type: str | null
Always present: Yes (may be null)
Redirect Object
Section titled “Redirect Object”Captures redirect operators (> file, 2>&1, <<< here-strings, << here-docs, etc.). The here-doc delimiter and body are not captured — only the operator itself.
{ "redirect_type": "output", "operator": ">", "target": "/tmp/log", "descriptor": null}redirect_type
Section titled “redirect_type”Redirect category.
Type: "input" | "output" | "dup"
Always present: Yes
| Value | Examples |
|---|---|
input | <file, <<EOF, <<-EOF, <<<"string" |
output | >file, >>file, &>file |
dup | 2>&1, >&2 (file-descriptor duplication) |
operator
Section titled “operator”The redirect operator text.
Type: str
Always present: Yes
target
Section titled “target”Redirect target. A filename for file redirects, an fd reference like &1 for dup redirects, or an empty string for << / <<- here-docs (the delimiter is not captured).
Type: str
Always present: Yes (may be empty)
descriptor
Section titled “descriptor”Explicit file descriptor when the redirect specifies one (e.g. 2 in 2>file). null when the redirect uses the default fd (stdin for input, stdout for output).
Type: int | null
Always present: Yes (may be null)
Pipe Object
Section titled “Pipe Object”{ "stdin": true, "stdout": false }true when this branch’s stdin comes from a preceding pipe (i.e. there is a ... | this upstream).
Type: bool
Always present: Yes
stdout
Section titled “stdout”true when this branch’s stdout feeds a following pipe (i.e. there is a this | ... downstream).
Type: bool
Always present: Yes
Recheck Object
Section titled “Recheck Object”Present only in runok audit --recheck output, as the top-level recheck field. Serialises as one of two shapes, distinguishable by which keys are present — there is no type/status discriminator field.
On success:
{ "action": { "type": "allow" }, "command_evaluations": [ { "command": "terraform apply", "action": { "type": "allow" }, "matched_rules": [ { "action_kind": "allow", "pattern": "terraform *", "matched_tokens": ["apply"] } ] } ]}On failure (the entry has no recorded metadata.cwd, its config failed to load, or re-evaluation itself errored — see --recheck):
{ "error": "failed to load config: ..." }action
Section titled “action”The current evaluation result for command as a whole, in the same Action Object shape as the top-level action field — consumers can reuse the same parsing code for both.
Type: Action
Present when: re-evaluation succeeded.
command_evaluations
Section titled “command_evaluations”Per-branch re-evaluation results, in the same order as command_evaluations. See RecheckCommandEvaluation Object.
Type: list[RecheckCommandEvaluation]
Present when: re-evaluation succeeded.
Why the entry could not be re-evaluated.
Type: str
Present when: re-evaluation failed.
RecheckCommandEvaluation Object
Section titled “RecheckCommandEvaluation Object”{ "command": "terraform apply", "action": { "type": "allow" }, "matched_rules": [ { "action_kind": "allow", "pattern": "terraform *", "matched_tokens": ["apply"] } ]}command
Section titled “command”The branch command, in the same form as CommandEvaluation.command.
Type: str
Always present: Yes
action
Section titled “action”Current re-evaluation result for this branch. See Action Object.
Type: Action
Always present: Yes
matched_rules
Section titled “matched_rules”Rules that currently match this branch, in the same RuleMatch Object shape as CommandEvaluation.matched_rules. A branch with action.type: "ask" and this omitted (empty) is resolved for that branch purely via defaults.action fallback, not an explicit ask rule. The ask-def NOW-column label (and the --recheck example jq query) combines this with action.type across all branches, rather than reading a single branch’s matched_rules in isolation — a branch matched by an allow rule still has non-empty matched_rules, so a compound command escalated to ask by a sandbox contradiction (all branches matched an allow rule, none matched ask) is correctly excluded from ask-def.
Type: list[RuleMatch]
Omitted when empty.
Related
Section titled “Related”runok audit— The CLI command that produces this JSON.- Configuration Schema —
audit— How to configure audit logging. - Pattern Syntax — Syntax of the
patternstrings insidematched_rules.