Skip to content

Audit Log JSON Schema

This page is the field-by-field reference for the JSON object produced by runok audit --json (one object per line, JSONL). Use it to write jq queries against audit logs without reading the runok source.

Every 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"
},
"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"]
}
]
}

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 Zjq literal-string comparisons (select(.timestamp >= "...")) need to use the same form.

Type: str
Always present: Yes

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

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

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)

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)

Session and context information about the invocation. See Metadata Object.

Type: Metadata
Always present: Yes

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 with eval_type: "primary".
  • A compound or pipelined input (e.g. a && b, a || b, a ; b, a | b) produces one entry per branch, all with eval_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:

Terminal window
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)

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

ValueMeaning
allowThe command is permitted.
denyThe command is rejected.
askThe command requires user confirmation.

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.

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.

{
"endpoint_type": "hook",
"session_id": "abc-123",
"cwd": "/home/user/project",
"tool_name": "Bash",
"hook_event_name": "PreToolUse"
}

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

ValueSource
execThe user invoked runok exec directly.
hookAn 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 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)

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-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)

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 }
}

The branch command as runok extracted it, with redirects stripped but the inline env prefix kept. For eval_type: "primary" entries this is identical to the top-level command.

Type: str
Always present: Yes

Rule-evaluation result for this branch. See Action Object.

Type: Action
Always present: Yes

Rules that matched for this branch, in match order. See RuleMatch Object.

Type: list[RuleMatch]
Omitted when empty.

How this branch was extracted from the input.

Type: "primary" | "compound"
Always present: Yes

ValueMeaning
primaryNon-compound input. The single entry covers the whole input.
compoundOne 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.

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).

{
"action_kind": "deny",
"pattern": "git push -f|--force *",
"matched_tokens": ["origin", "main"]
}

The kind of rule that matched.

Type: "allow" | "ask" | "deny"
Always present: Yes

The rule pattern string as written in runok.yml. See Pattern Syntax.

Type: str
Always present: Yes

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)

{ "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)

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 category.

Type: "input" | "output" | "dup"
Always present: Yes

ValueExamples
input<file, <<EOF, <<-EOF, <<<"string"
output>file, >>file, &>file
dup2>&1, >&2 (file-descriptor duplication)

The redirect operator text.

Type: str
Always present: Yes

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)

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)

{ "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

true when this branch’s stdout feeds a following pipe (i.e. there is a this | ... downstream).

Type: bool
Always present: Yes