Advanced recipes
Each recipe goes in ~/.config/nightporter/config.toml under [defaults], or in a repo file (repos/<forge>/<owner>/<repo>.toml) to apply to one repo. The daemon picks up changes when you save. See the configuration reference for every key.
Different levels per repo
Let it run on its own on a side project, but only suggest on the one that pays the bills:
# config.toml
[defaults]
level = "draft"# repos/github/me/dotfiles.toml
subscribed = true
level = "auto"# repos/github/acme/payments.toml
subscribed = true
level = "suggest"
[checks]
protected = ["migrations/**", "src/billing/**"]Working hours
Several windows a day, a window across midnight, and a different timezone:
[[defaults.schedule]]
days = "mon-fri"
windows = ["08:00-09:00", "17:00-22:00"]
[[defaults.schedule]]
days = "sat,sun"
windows = ["22:00-06:00"]
tz = "Europe/Zurich"Events outside the windows are queued and run when the next window opens.
Who it listens to
[defaults.trust]
roles = ["owner", "maintainer", "member", "collaborator"]
allow_users = ["alice"]
deny_users = ["mallory"]
allow_teams = ["acme/core"]
allow_bots = ["coderabbitai[bot]"]A repo file can add people to these lists, but it can’t remove a global deny. Everyone else goes to the inbox.
trust.open = true lets anyone through, and caps the level at suggest.
Only some triggers
# repos/github/acme/web.toml
subscribed = true
triggers = ["ci_failure", "merge_conflict"] # leave review comments to humansThe triggers are review_comment, review, comment, ci_failure and merge_conflict.
Limits and budgets
# config.toml
max_concurrent_jobs = 2
daily_job_cap = 50
min_free_gb = 5
[defaults]
timeout = "30m"
max_budget_usd = 2.0 # Claude Code only
max_ci_attempts = 3Agent options
[defaults]
agent = "claude"
agent_args = ["--model", "opus"]
allow_commands = ["npm test", "npm run lint"] # prefix match
prompt = "prompts/house-style.md" # relative to the config dirSandboxing
With Claude Code, every shell command the agent runs goes in the OS sandbox (Seatbelt on macOS, bubblewrap on Linux). If the sandbox can’t start, the job doesn’t run.
Commands have no network access unless you allow a domain:
[defaults]
sandbox_domains = ["registry.npmjs.org"]Tests that need Docker or DDEV sockets don’t work inside the sandbox. Turn it off for that repo only:
# repos/gitlab/project/token.toml
sandbox = falseOpenCode has no sandbox.
Environments (DDEV, Docker, …)
Start a stack before the agent runs and stop it afterwards. Each worktree gets its own:
# repos/gitlab/project/token.toml
subscribed = true
allow_commands = ["ddev phpunit", "ddev exec phpcs"]
[env]
setup = ["ddev config --project-type=drupal11 --project-name=np-{repo}-{n} --auto", "ddev start", "ddev composer install"]
teardown = ["ddev stop"]
cleanup = ["ddev delete -O -y"]
prompt = "prompts/drupal.md"teardown runs after each job, cleanup when the worktree is removed.
Maintainer mode
Work on other people’s issues and MRs, but only when a trusted person asks: with the label, or a /nightporter triage, reproduce, fix or review comment.
[maintainer]
enabled = true
label = "nightporter"Maintainer mode is capped at draft, so you always approve before anything is pushed or posted.
Replies
[defaults.reply]
style = "concise" # detailed-tech-lead, non-technical, or one from a pack
language = "German"
footer = true # "via Night Porter (Claude Code)"
resolve_threads = falsePacks
Packs add review steps, reply styles and environment presets. They’re pinned to a commit, and they can only make the rules stricter.
nightporter market list
nightporter pack add <name>
nightporter pack list[defaults]
packs = ["a11y"]To write your own, see Writing packs.
Testing a config
NIGHTPORTER_HOME=/some/dir moves the config and the state to /some/dir/{config,data}, so you can try things without touching your real setup.