runok check
runok check evaluates a command against your runok rules and reports the decision — without actually running the command. Useful for previewing what runok would do, or for integrating with external tools like Claude Code hooks.
runok check [options] -- <command> [arguments...]Any unrecognized flag before -- is rejected with an error to prevent typos from being silently absorbed into the command arguments.
When no command arguments are given, runok reads from stdin instead. The input format is auto-detected: JSON objects are parsed by field (tool_name for Claude Code hooks, command for generic checks), and anything else is treated as plaintext shell input.
Plaintext stdin is split into individual commands at top-level shell statement boundaries (newlines, ;, and & between top-level statements). Constructs that legitimately span multiple lines — HEREDOCs, multi-line quoted strings, and \ line continuations — are kept together as a single command. Compound commands joined by &&, ||, or | also stay together (the rule engine splits those further internally). Stdin that cannot be parsed as shell exits with stdin parse error.
Use --input-format claude-code-hook to force Claude Code hook parsing.
-c, --config <path>
Section titled “-c, --config <path>”See Global Flags.
--input-format <format>
Section titled “--input-format <format>”Input format for stdin. Currently supports claude-code-hook. When omitted, the format is auto-detected from the stdin content. Has no effect when command arguments are provided.
--output-format <format> [default: text]
Section titled “--output-format <format> [default: text]”Output format. Available values:
text— Human-readable single line (e.g.,deny: reason (suggestion: fix))json— Machine-readable JSON object withdecision,reason,fix_suggestion, andsandboxfields. Fields with no value are omitted.
--verbose
Section titled “--verbose”Output detailed rule matching information to stderr.
Examples
Section titled “Examples”Check a single command:
runok check -- git push --force# deny: Blocked to prevent rewriting remote history (suggestion: git push)Check with JSON output:
runok check --output-format json -- rm -rf /{ "decision": "deny", "reason": "Do not delete root", "fix_suggestion": "rm -rf ./build"}Read commands from stdin (split at shell statement boundaries):
printf "git push\nnpm publish\n" | runok check# allow# deny: Publishing is not allowedMulti-line constructs are evaluated as a single command:
cat <<'OUTER' | runok checkgit add path && git commit -m "$(cat <<'EOF'subjectbodyEOF)"OUTER# allowRead from stdin as JSON:
echo '{"command":"git push"}' | runok check# allowForce Claude Code hook format:
cat hook-input.json | runok check --input-format claude-code-hook# allow [sandbox: default]Exit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
0 | Evaluation completed successfully (regardless of the decision). |
2 | An error occurred (config error, JSON parse error, etc.). |
The exit code reflects whether the check itself succeeded, not the permission decision. A deny result still returns exit code 0. Use --output-format json to programmatically inspect the decision.
When checking multiple commands (multi-line stdin), the exit code is the highest value across all evaluations.
Hook mode (--input-format claude-code-hook)
Section titled “Hook mode (--input-format claude-code-hook)”In hook mode, the following runok-side failures exit with code 1 instead of 2:
- Config load errors (YAML syntax errors, etc.)
- Rule pattern parse errors caught during evaluation
- Unknown-flag errors for
runok check - Stdin JSON parse errors
HookInputschema mismatches (e.g. when Claude Code adds a new required field)
Claude Code treats exit 2 from a PreToolUse hook as a blocking error, so any of these would otherwise block every Bash tool call until runok or the config catches up. Exit 1 is the documented non-blocking failure mode that lets Claude Code fall back to its normal permission flow.
Direct CLI usage (without --input-format claude-code-hook) is unchanged and still exits 2 on errors.