Skip to content

Configuration

Settings come from config files (YAML), environment variables, and CLI flags.

Priority (highest wins): CLI flags → env vars → --config FILE (or --config - for stdin) → .junior.{yaml,yml} (project) → ~/.config/junior/settings.{yaml,yml} (global)

Env vars override nested config-file group fields too — e.g. HARNESS beats a config file’s llm.harness.

Configuration flags work through one override channel: the environment. Every scalar flag except the primary selectors is an alias for --env KEY=VALUE--model X exports MODEL=X for the run, which is why flags beat both exported env vars (the export overwrites them) and config files, and why a flag’s value is inherited by the harness subprocess and script-runbook commands. The two primary selectors, --runbook and --harness, stay process-local: they choose what this invocation runs and are never exported (a nested junior run inside a script-runbook command must not inherit them). --env itself supplies any var inline (repeatable); junior config env lists which vars your harness + runbook combination needs.

[!NOTE] User-facing output goes to stdout as human-readable presentation (the review renders as pretty Markdown in a TTY, raw Markdown when piped or redirected), while errors, status, warnings, and structlog logs all go to stderr. stdout stays pipe-safe: junior run > review.md yields clean Markdown.

Junior settings split into three mutually-independent groups, plus a couple of top-level keys (runbook, log_level):

GroupWhat it controls
ContextWhat to review — source mode, prompts, extra instructions, MR metadata
LLMHow to call the model — harness, model, API keys, system prompt, runtime limits
OutputWhere to send — local file path, publish toggle, platform tokens, CI vars

In a config file the three groups are nested explicitly under context: / llm: / output:, with runbook:, log_level: and local_runbooks: at the top level. In env vars they are flat — the names are the field names uppercased (HARNESS, MODEL, SOURCE, PUBLISH, RUNBOOK, LOCAL_RUNBOOKS, …) plus industry-standard aliases (GITLAB_TOKEN, CI_*, GITHUB_*, ANTHROPIC_API_KEY) and not prefixed with the group.

local_runbooks (top-level, default false). Opt-in: load runbooks from <project>/.junior/runbooks/ (see adding runbooks). It executes Python shipped in the repo, so enable it only in repos you trust.

[!NOTE] Deprecated alias: --backend / env BACKEND and the config key backend are accepted as a deprecated alias for harness (kept for one version). Prefer harness / HARNESS / --harness.

[!TIP] Top-level shorthands. The run-shaping knobs you set most often — harness, model, publish, output_file (plus runbook and log_level) — may live at the config root as shorthand. harness: codex is exactly llm: {harness: codex}; output_file: review.md is output: {output_file: review.md}. Both forms work; if you write both in one file the top-level value wins. junior init writes the flat form.

[!WARNING] Other group fields must be nested. A flat top-level scalar that isn’t a shorthand — e.g. source: staged (instead of context: {source: staged}) — is ignored, and Junior logs ignoring unknown config key 'source' — did you mean context.source?.

Junior config files are YAML. Auto-discovery prefers .yaml over .yml when both exist in the same place.

FileScopeCreated by
~/.config/junior/settings.{yaml,yml}Global (all projects)junior init (choose “global”) writes settings.yaml
.junior.{yaml,yml}Project-local — found in the current directory or any parent up to the repo root (the first directory containing .git), so running junior from a subdirectory sees the same configjunior init (choose “local”), or by hand
--config FILEExplicit override — any path, any nameManual
--config -Read YAML from stdin (CI / scripted runbooks)n/a
Terminal window
junior init # interactive setup → global or local config
junior --config security.yaml run # explicit override
cat preset.yaml | junior --config - run # from stdin
junior config path # show which files were found

A “preset” (e.g. security checks only) is just a YAML file you keep around and pass via --config — there is no special template registry. Project layouts often look like:

my-repo/
.junior.yaml # team default
.junior/
security.yaml # ad-hoc preset, pass with --config .junior/security.yaml
docs.yaml
prompts/ # files referenced via file://./prompts/...
security.md
docs.md
runbook: local_review # required: local_review, github_pr_review, gitlab_pr_review, bitbucket_pr_review
context:
prompts:
- file://./.junior/prompts/security.md # path is relative to THIS config file
- file://./.junior/prompts/logic.md
- "Also flag any new TODO comments" # inline text is fine too
context_files:
spec: SPEC.md
llm:
harness: pydantic # the harness: claudecode (default), pydantic, codex, deepagents
model: anthropic:claude-opus-4-6
anthropic_api_key: sk-ant-... # prefer env var — see warning below
max_file_size: 100000 # skip files larger than this (bytes)
output:
output_file: review.md
publish: false # true → post to the runbook's platform

file://... URIs inside context.prompts are resolved against the config file’s own directory at load time, so multiple presets in .junior/*.yaml can each reference file://./prompts/foo.md without ambiguity.

For the common case you don’t need the groups at all — the run-shaping shorthands (harness, model, publish, output_file) sit at the root. This is what junior init writes:

runbook: gitlab_pr_review
harness: pydantic
model: anthropic:claude-opus-4-6
publish: true
output_file: review.md

Reach for the context: / llm: / output: groups only when you need their other fields (prompts, API keys, source, max_file_size, CI vars, …). Tip: junior config show --harness X --runbook Y lists exactly which of those apply to your setup.

Terminal window
export RUNBOOK=local_review
export PROMPTS='["file:///abs/path/security.md","Find security vulnerabilities"]'
export HARNESS=pydantic
export MODEL=anthropic:claude-opus-4-6
export ANTHROPIC_API_KEY=sk-ant-...
export MAX_FILE_SIZE=100000
export OUTPUT_FILE=review.md
export PUBLISH=false

[!WARNING] Keep API keys in env vars

Never commit anthropic_api_key / openai_api_key to a config file. The wizard does not write them, and they’re listed as env vars only in this doc — the field exists in the schema for completeness, not as a recommendation.

VariableCLI flagDefaultDescription
SOURCE--sourceautoauto, staged, commit, branch
BASE_SHA--base-shaDiff against this commit. Wins over CI auto-vars
PROJECT_DIR (or CI_PROJECT_DIR)--project-dir.Path to git repository
INPUT_TEXT[INPUT] (positional)Free-form task input handed to the runbook’s collect step — the collector decides how to use it
CI_MERGE_REQUEST_TARGET_BRANCH_NAME--target-branchmainTarget branch for diff
PROMPTS (JSON)--prompt TEXT / --prompt-file FILE[]One list, each entry is inline text or file://.... JSON array in env (PROMPTS='["..."]'); CLI flags both append to config
CONTEXT (JSON)--context KEY="text"{}Extra prompt instructions (KEY=text). Repeatable on CLI
CONTEXT_FILES (JSON)--context-file KEY=path{}Data files to attach. Repeatable on CLI
CI_MERGE_REQUEST_TITLEMR/PR title (auto-set by CI; passed to LLM)
CI_MERGE_REQUEST_DESCRIPTIONMR/PR description (auto-set by CI; passed to LLM)
CI_MERGE_REQUEST_SOURCE_BRANCH_NAMESource branch name (auto-set by CI)

Config key: llm:. The harness is how the LLM is invoked. The runbook (collect → render → LLM → publish, including the platform) is selected separately via the top-level runbook key.

VariableCLI flagDefaultDescription
HARNESS--harnessclaudecodeHarness: claudecode, pydantic, codex, deepagents, pi
MODEL--modelper providerAccepts provider:model (e.g. anthropic:claude-opus-4-6) or bare model
OPENAI_API_KEYOpenAI API key
ANTHROPIC_API_KEYAnthropic API key
SYSTEM_PROMPT (JSON)[]Role/identity layer merged on top of the runbook’s own system prompt. Each entry is inline text or a file://... URI (like context.prompts)
MAX_TOKENS_PER_AGENT0Response token cap, 0 = no limit (pydantic harness only)
MAX_FILE_SIZE100000Skip file content above this size, bytes (collection + pydantic)

[!NOTE] BACKEND / --backend and the config key backend remain accepted as a deprecated alias for HARNESS / --harness / harness (kept for one version).

VariableCLI flagDefaultDescription
OUTPUT_FILE-oWrite review to file instead of stdout
RECORD--no-record (to disable)trueWrite a machine-readable JSON run record to <project_dir>/.junior/output/{timestamp}.json after a successful run. Disable with --no-record or output.record: false
PUBLISH--publish / --no-publishfalseRun the runbook’s custom publish. local_review renders pretty Markdown locally; platform runbooks post to the PR/MR and require their tokens (see below). Without it, every runbook emits raw output instead
GITLAB_TOKENGitLab token with api scope
GITHUB_TOKENGitHub token
CI_SERVER_URLhttps://gitlab.comGitLab instance URL
CI_PROJECT_IDGitLab project ID (auto-set by runner)
CI_MERGE_REQUEST_IIDMR number (auto-set by runner)
CI_MERGE_REQUEST_DIFF_BASE_SHABase SHA for inline comments (auto-set by runner)
CI_COMMIT_BEFORE_SHAPrevious HEAD on push events (auto-set by runner)
CI_COMMIT_SHACurrent commit SHA (auto-set by runner)
GITHUB_REPOSITORYowner/repo format (auto-set by Actions)
GITHUB_EVENT_NUMBERPR number (you may need to export this manually)
GITHUB_EVENT_BEFOREPrevious HEAD on push events (export from ${{ github.event.before }})
BITBUCKET_URLBitbucket Data Center base URL, e.g. https://bitbucket.example.com (HTTPS only)
BITBUCKET_TOKENBitbucket DC HTTP access token (sent as a Bearer header)
BITBUCKET_PROJECTBitbucket project key
BITBUCKET_REPOBitbucket repository slug
BITBUCKET_PR_IDPull request id (set it in your CI — Bitbucket DC provides no pipeline vars)
VariableCLI flagDefaultDescription
RUNBOOK--runbook— (required)Which runbook to run: local_review, github_pr_review, gitlab_pr_review, bitbucket_pr_review, or pkg.module:ClassName. Selected explicitly — no auto-detection, no implicit default
LOG_LEVEL-vINFODEBUG, INFO, WARNING, ERROR. -v sets DEBUG

[!NOTE] Platform tokens and CI auto-vars are typically set as environment variables by the runner — you almost never list them yourself. See CI Setup.

Picking a model — one flag for everything

Section titled “Picking a model — one flag for everything”

MODEL (or --model) accepts two forms:

  • provider:model — explicit provider, e.g. anthropic:claude-opus-4-6, openai:gpt-5.4-mini.
  • model — provider is inferred from whichever API key is set (OPENAI_API_KEY → openai, ANTHROPIC_API_KEY → anthropic).

The legacy --provider flag is removed in 0.2.0 — encode it in --model instead.

Terminal window
junior run --model anthropic:claude-opus-4-6 # explicit provider
junior run --model gpt-5.4-mini # provider from $OPENAI_API_KEY
ANTHROPIC_API_KEY=sk-ant-xxx junior run --harness pydantic # default model for provider

A harness is the LLM driver (llm.harness / --harness / env HARNESS). All five implement the same complete() call; they differ in how they reach a model and whether they read repo files themselves. Install only the one you run (junior config list harnesses shows install state + readiness; junior config env shows the vars below).

HarnessInstallReads files itself (file_access)API keyAuthenticates via
claudecode (default)core — no extra✅ yesoptional — ANTHROPIC_API_KEY switches the CLI to API mode (--bare)the claude CLI; run claude once to log in (Claude subscription)
codexjunior[codex]✅ yesoptional — OPENAI_API_KEY as fallbackthe codex CLI; authenticate it once (OAuth)
pydanticjunior[pydantic]❌ no — diff is inlinedrequiredOPENAI_API_KEY / ANTHROPIC_API_KEYa provider API key (matches your --model provider)
deepagentsjunior[deepagents]❌ no — context is inlinedrequiredOPENAI_API_KEY / ANTHROPIC_API_KEYa provider API key
picore — no extra✅ yesper provider — or none for local models (~/.pi/agent/models.json)the pi CLI; env key, ~/.pi/agent/auth.json, or a local model
  • file_accessclaudecode/codex/pi explore the repo with their own tools, so the runbook does not inline the full diff into the prompt. pydantic/deepagents get the diff inlined (they also have read-only file tools for extra exploration).
  • Honored config fields — every API harness reads llm.model and llm.max_file_size; only pydantic additionally honors llm.max_tokens_per_agent (response-token cap). claudecode/codex ignore model unless you set it explicitly (the CLI picks otherwise).
  • No required env for the CLI harnessesclaudecode/codex carry their own auth; junior config env --harness pydantic (or deepagents) lists the provider key. Setting both OPENAI_API_KEY and ANTHROPIC_API_KEY is fine — pass --model anthropic:... / --model openai:... to disambiguate.

Per-harness deep dives: Harnesses.

A runbook runs collect → render → LLM → publish, and the platform (local / GitHub / GitLab) is part of the runbook — not auto-detected from token presence. Pick one explicitly via the top-level runbook key, the RUNBOOK env var, or --runbook — with none set, junior run exits 2 (no implicit default). The code-review runbooks share one base and differ only in where they collect from and where they publish; weather_advice is a non-code example proving the framework generalizes.

RunbookCollects fromneeds_git--publish doesRequired env (publish only)Optional env
local_reviewlocal git diffrenders pretty Markdown locally
github_pr_reviewGitHub PR + diffposts PR review commentsGITHUB_TOKEN, GITHUB_REPOSITORY, GITHUB_EVENT_NUMBERGITHUB_EVENT_BEFORE
gitlab_pr_reviewGitLab MR + diffposts MR note + inline threadsGITLAB_TOKEN, CI_PROJECT_ID, CI_MERGE_REQUEST_IIDCI_MERGE_REQUEST_DIFF_BASE_SHA, CI_COMMIT_SHA
bitbucket_pr_reviewBitbucket DC PR + diffposts PR comment + inline commentsBITBUCKET_URL, BITBUCKET_TOKEN, BITBUCKET_PROJECT, BITBUCKET_REPO, BITBUCKET_PR_ID
weather_advice (example)live weather (no git)prints a Rich terminal panel
  • needs_git — gates the preflight .git check. Code-review runbooks require a repo; weather_advice (and most local runbooks) run in any directory.
  • Honored config fields — the code-review runbooks read context.source, context.base_sha, context.target_branch, and llm.max_file_size; gitlab_pr_review additionally reads output.ci_server_url, and bitbucket_pr_review reads output.bitbucket_url. weather_advice declares none.
  • Required env applies only when --publish is set — without it, every runbook emits its raw output to stdout/-o and needs no token. Many CI_* / GITHUB_* vars are auto-provided by the CI runner; junior config env --runbook X lists exactly what your combination needs.
  • Beyond these you can run an external runbook by import path (--runbook pkg.module:ClassName) or a repo-local one from .junior/runbooks/ (opt-in local_runbooks). See Adding runbooks & harnesses.

[!NOTE] Setting both GITLAB_TOKEN and GITHUB_TOKEN is no longer an error — the runbook you select decides which one is used.

output.publish: true (or --publish / --no-publish to override) runs the runbook’s custom publish instead of emitting the raw result. Each platform runbook validates its own targets when publishing — the required vars are in the runbook table above.

Terminal window
junior --config gh.yaml run --runbook github_pr_review --publish
RUNBOOK=gitlab_pr_review junior run --publish

Anchor the diff at a specific commit (SHA...HEAD) in auto mode. Wins over every CI auto-var.

When unset, junior falls back to CI variables in this order, skipping the 40-zero placeholder GitLab/GitHub emit on first push:

  1. CI_MERGE_REQUEST_DIFF_BASE_SHA — GitLab MR pipelines
  2. CI_COMMIT_BEFORE_SHA — GitLab push events
  3. GITHUB_EVENT_BEFORE — GitHub Actions push events (must be exported from ${{ github.event.before }})

This lets one junior run --publish step cover both MR/PR and push-to-main runbooks.

Terminal window
junior run --base-sha v1.2.0 # everything since the v1.2.0 tag
junior run --base-sha $CI_COMMIT_BEFORE_SHA # new commits in this push

Junior ships no built-in prompts. You supply them three ways and they stack:

SourceShapeWhen
--prompt TEXTRepeatable CLI flagAd-hoc tweaks for a single run
--prompt-file FILERepeatable CLI flag, .md filesReusable instructions checked into the repo
Configcontext.prompts: list[str] — each entry is inline text or a file://... URIStable baseline per project or globally

CLI values append to config values — config holds the baseline, CLI adds extras on top. --prompt-file FILE is just a shortcut: it’s converted to file://<abs> and merged into the same list.

file:// URIs inside a config file are resolved against the config file’s directory; URIs from --prompt / --prompt-file are resolved against the current working directory.

Example reference prompts live in examples/prompts/ — copy them into your repo and reference the local copies.

context:
prompts:
- file://./prompts/security.md # next to this config file
- "Find any new TODO comments" # inline
Terminal window
junior run \
--prompt "Quick smoke review for PR #123" \
--prompt-file ./prompts/security.md
Terminal window
# Env-var form — JSON-encoded list
PROMPTS='["file:///abs/security.md","Quick smoke review"]' junior run

If no prompts come from any source, the LLM still gets the diff, MR metadata, prior discussion, project instructions from AGENT.md / CLAUDE.md, and code_review’s built-in base rules (severity scale, focus on changed code).

  • MAX_FILE_SIZE (default 100000) — files larger than this have their content skipped; only the diff is included.
  • MAX_TOKENS_PER_AGENT (default 0, pydantic only) — response token cap; 0 = no limit.

Write the rendered review to a local file. Independent from --publish: you can set both — -o always writes locally, --publish always posts to the platform.

Terminal window
junior run --prompt "Quick review" -o review.md
junior run -o review.md --publish # both: file + platform

Every successful junior run writes a machine-readable, secret-free JSON record to <project_dir>/.junior/output/{timestamp}.json. It captures the runbook, harness, model, source, usage, errors, summary, blocking status, and the full structured output — handy for auditing, dashboards, or post-processing.

It’s on by default. Disable it with the --no-record flag or output.record: false (field OutputSettings.record, default true). The .junior/output/ directory should be gitignored.

Terminal window
junior run # writes .junior/output/2026-06-06T12-00-00.json
junior run --no-record # no record written