Skip to content

Latest commit

 

History

458 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

runok

CI GitHub Release License: MIT

runok is a command permission tool for LLM coding agents.

📖 Documentation · 🚀 Quick Start · 🔧 Configuration


Why runok?

Even with allow rules configured, Claude Code asks for confirmation in cases like these:

# Claude adds a comment before the command -- no longer matches your allow rule
⏺ Bash(# check logs
      git log --oneline -5)
  ⎿  Running…

Command contains newlines that could separate multiple commands

Do you want to proceed?
❯ 1. Yes
  2. No
# Text-based parsing flags safe commands as suspicious
⏺ Bash(git log --oneline -5 && echo "---" && git status)
  ⎿  Running…

Command contains quoted characters in flag names

Do you want to proceed?
❯ 1. Yes
  2. No

runok parses commands with tree-sitter-bash, so comments, compound commands (&&, |, ;), and wrapper commands (sudo, bash -c, xargs) are all handled correctly. Each sub-command is evaluated independently against your rules.

And that's just the start. Claude Code's built-in permissions have other limitations too:

Denied commands give no explanation. The agent has no idea why a command was blocked. With runok, deny rules include a message and a suggested fix -- the agent reads it and self-corrects:

# runok.yml
- deny: 'git push -f|--force *'
  message: 'Force push is not allowed.'
  fix_suggestion: 'git push --force-with-lease'

Global flags break matching. Claude sometimes adds flags like -C before the subcommand. git -C /path commit does not match Bash(git commit *). runok handles this with optional groups and order-independent matching:

# runok.yml
- allow: 'git [-C *] commit *'
# matches: git commit -m "fix"
# matches: git -C /path/to/repo commit -m "fix"

No recursive parsing of wrappers. Claude Code does not inspect sudo, bash -c, or $(). runok recursively unwraps them to evaluate the inner command:

# runok.yml
definitions:
  wrappers:
    - 'sudo <cmd>'
    - 'bash -c <cmd>'

rules:
  - deny: 'rm -rf /'
# "sudo bash -c 'rm -rf /'" -> unwrap sudo -> unwrap bash -c -> deny

JSON only, no comments. settings.json cannot be annotated. runok uses YAML:

# runok.yml
rules:
  # read-only git commands are always safe
  - allow: 'git status'
  - allow: 'git diff *'

  # allow push, but not force push -- rewrites shared history
  - deny: 'git push -f|--force *'
    message: 'Use --force-with-lease instead.'
  - ask: 'git push *'

See Why runok? for a full comparison table.

Features

Flexible command parsing

  • tree-sitter-bash AST parsing -- comments, pipes, &&, ; are understood, not treated as opaque strings
  • sudo, bash -c, xargs are recursively unwrapped so rules apply to the inner command

Flexible rule configuration

  • Wildcards, flag alternation (-f|--force), optional groups, argument-order-independent matching
  • Conditional when clauses with CEL expressions for environment-aware decisions
  • OS-level sandboxing (macOS Seatbelt / Linux Landlock) for file and network restrictions

And more -- official presets for instant setup, rule testing, audit logging, preset sharing, denial feedback, extension protocol

Quick start

Install

Homebrew

brew install fohte/tap/runok

From source with Cargo

cargo install --git https://github.com/fohte/runok.git

Pre-built binaries are also available on GitHub Releases. See Installation for all options.

Configure

The fastest way to get started is with the interactive setup wizard:

runok init

This creates a runok.yml, and if you have Claude Code configured, migrates your Bash permissions to runok rules and registers the PreToolUse hook automatically.

You can also configure manually. Create ~/.config/runok/runok.yml:

# Start with official presets -- covers common read-only commands,
# Git, GitHub CLI, and wrapper definitions out of the box
extends:
  - 'github:fohte/runok-presets/base@v1'

rules:
  # Add your own rules on top of the presets
  - ask: 'git push *'
  - deny: 'git push -f|--force *'
    message: 'Force push is not allowed'

defaults:
  action: ask

And add runok as a PreToolUse hook in .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "runok hook --agent claude-code"
          }
        ]
      }
    ]
  }
}

See Claude Code Integration for sandbox setup and advanced configuration.

Verify

runok check -- git status        # => allow
runok check -- git push -f main  # => deny
runok check -- git push main     # => ask

Use the Claude Code plugin (optional)

The runok Claude Code plugin lets you manage your runok.yml from inside Claude Code in natural language -- adding rules, updating presets, and answering questions about pattern syntax.

/plugin marketplace add fohte/runok-claude-code-plugin
/plugin install runok@runok-claude-code-plugin

Full Documentation

See runok.fohte.net

Feedback

Feature requests and bug reports are welcome on GitHub Issues.

About

Command execution permission framework for LLM agents

Resources

Stars

9 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages