Skip to content

File Discovery and Merging

runok loads configuration from up to four layers, merging them in a defined order. This allows you to set organization-wide defaults globally while overriding specific settings per project. For sharing configuration across repositories, see Extends (Presets).

runok searches for configuration files in two scopes:

ScopePathPurpose
Global$XDG_CONFIG_HOME/runok/runok.ymlUser-wide defaults
Project./runok.yml (working directory)Project-specific rules

When XDG_CONFIG_HOME is not set, the global config directory defaults to ~/.config/runok/. See Environment Variables for details.

In each scope, an optional local override file is also loaded:

ScopeOverride PathPurpose
Global$XDG_CONFIG_HOME/runok/runok.local.ymlPersonal tweaks
Project./runok.local.yml (working directory)Personal project overrides

When both .yml and .yaml extensions exist, .yml takes priority:

  1. runok.yml (preferred)
  2. runok.yaml (fallback)

This applies to both the main configuration file and the local override file in each scope.

Configuration layers are merged bottom-to-top, with later layers taking higher priority:

PriorityLayerPath
1 (low)Global config$XDG_CONFIG_HOME/runok/runok.yml
2Global local override$XDG_CONFIG_HOME/runok/runok.local.yml
3Project config./runok.yml
4 (high)Project local override./runok.local.yml

After merging all four layers, the resulting configuration is validated.

Different fields use different merge strategies:

FieldStrategyBehavior
extendsAppendLists are concatenated.
rulesAppendRules from all layers are concatenated in order.
defaults.actionOverrideHigher-priority layer wins.
defaults.sandboxOverrideHigher-priority layer wins.
definitions.pathsPer-key appendValues for each key are concatenated (deduplicated).
definitions.sandboxPer-key overrideHigher-priority layer replaces the entire preset.
definitions.wrappersAppendLists are concatenated.
definitions.commandsAppendLists are concatenated.

Given a global config:

~/.config/runok/runok.yml
rules:
- allow: 'git *'
definitions:
paths:
secrets:
- ~/.ssh

And a project config:

./runok.yml
rules:
- allow: 'cargo build *'
- deny: 'rm -rf /'
definitions:
paths:
secrets:
- ~/.aws/credentials

The merged result is:

Merged result
rules:
# global rules come first, then project rules
- allow: 'git *'
- allow: 'cargo build *'
- deny: 'rm -rf /'
definitions:
paths:
secrets: # values are merged per-key
- ~/.ssh
- ~/.aws/credentials

After merging, runok validates the final configuration:

  • Each rule must have exactly one of deny, allow, or ask.
  • deny rules must not have a sandbox attribute.
  • sandbox values must reference names defined in definitions.sandbox.
  • <path:name> references in fs.deny must resolve to entries in definitions.paths.
  • <path:name> references are not allowed inside definitions.paths values.

All validation errors are collected and reported together so you can fix every issue in a single pass.