Skip to content

.mergejudge.yml reference ​

The file is read from the base branch tip (falling back to the PR's base commit), never from the PR head. Every key is optional. Values shown are defaults unless marked example.

yaml
provider:
  type: none                     # anthropic | openai | zai | openai-compatible | workers-ai | none
  base_url: https://api.openai.com/v1   # example
  model: gpt-4o-mini                    # example
  api_key_env: OPENAI_API_KEY           # name of the env var holding the key
  api: chat                             # openai only: chat (Chat Completions) | responses (Responses API)
  endpoint: paygo                       # zai only: paygo | coding (coding-plan endpoint)

lanes:
  auto: ["docs/**", "**/*.md", "**/*.css", "public/**"]
  protected: [".github/**", "deploy/**", "**/auth/**", "**/migrations/**", "wrangler.toml", "_headers", "**/.env*"]
  max_auto_lines: 200
  code_auto:                     # opt-in: small, clean code changes the AI may mark auto (off = code always needs review)
    enabled: false
    paths: []                    # globs a code file must match; [] = every code path that is not protected
    max_lines: 30                # changed lines per file
    max_total_lines: 100         # changed lines over all candidate files in the PR
  trusted_authors:               # opt-in: the auto lane is only for listed GitHub logins (off = no author check)
    enabled: false
    users: []                    # e.g. ["gauravsharmaGG"] (case-insensitive)
    applies_to: code_auto        # code_auto | all_auto (all_auto also covers the docs/style auto paths)
    agents: as_author            # as_author (an agent PR counts as its author) | untrusted (agent PRs never get auto)

labels:
  block: [do-not-merge]
  review: [needs-review]

pr_template:                     # opt-in: the PR description must really answer the configured sections
  enabled: false
  sections: [Testing, Risk, "How to revert"]   # headings or `**Testing:**`-style labels; the three defaults
                                               # accept aliases ("Tests", "Test plan", "Rollback", ...)
  min_chars: 15                  # real text under each heading; HTML comments, placeholders and
                                 # checkbox-only lines do not count
  apply_to: agents               # agents = only PRs with the agent marker (label or body trailer) | all
  missing_lane: review           # review | block; only ever raises a lane

review:
  mode: diff                     # diff | context | deep (deep: hosted only)

context:                         # used when review.mode: context
  max_bytes: 40000               # total context sent, in priority order (see below)
  max_files: 25                  # max files read per review (bounds API calls)
  max_symbols: 3                 # changed symbols searched for callers
  max_callers: 3                 # callers kept per symbol
  max_file_bytes: 12000          # larger files are sent as excerpts around the changed lines / call sites

deep:                            # used when review.mode: deep
  max_steps: 6                   # tool rounds (up to 4 calls each), then one tool-less answer
  max_tokens: 60000              # input+output over all calls
  max_seconds: 120
  max_tool_output_bytes: 6000    # per tool result; larger outputs are cut

index:
  policy: stored                 # stored (R2 + Vectorize) | none (temporary, nothing persisted)

budget:
  max_tokens_per_pr: 28000       # estimated prompt + reply size (~chars/4); patches are truncated to fit
  # max_output_tokens: 2500      # unset = 8000 for reasoning models (glm-5.x, gpt-5*, o-series, ...), 2500 otherwise;
                                 # set = always used (clamped to the PR budget). A cut-off reply is retried once at 2x.

diff_exclude:                    # kept out of the LLM prompt, still laned by rules
  - "**/package-lock.json"       # …plus other lockfiles, build output, minified files, images, fonts, generated/**

policy_files: [REVIEW_POLICY.md, APPROVAL_POLICY.md, AGENTS.md, CLAUDE.md]   # [] disables
policy_exclude: []               # globs to skip
policy:
  max_each_bytes: 8192
  max_total_bytes: 8192

rubrics:
  enabled: true
  packs: [security, correctness] # also: maintainability
  custom: []                     # up to 20 extra criteria, see Rubric packs
  notes: {}                      # criterion id -> extra guidance for the model (learning proposals add these)
# shorthand: rubric_packs: [security]   (rubric_packs: [] turns rubrics off)

approval:
  enabled: false                 # nothing happens unless true
  mode: none                     # none | auto-merge-only | bot-approve
  merge_method: squash           # squash | merge | rebase (for native auto-merge)
  human_approvers: { auto: 0, review: 1, block: 0 }
  bot_approve: { auto: true, review: false, block: false }

learning:
  enabled: true
  propose_after: 10              # lessons before a proposal PR is opened
  branch: mergejudge-learning    # must not be your default branch
  exclude_actors: []             # logins (case-insensitive) whose merges and label changes are never recorded as outcomes
  proposal:                      # when the proposal PR may edit files (below these it is a report only)
    edits: true                  # false = proposals never edit files
    min_prs: 3                   # "too strict" on a criterion: this many different PRs ...
    min_actors: 2                # ... and this many different people
    min_path_events: 2           # "too lenient" on a path: this many reverts / fix follow-ups of auto PRs

runner:                          # only for repos that review on a runner on your machine
  engine: null                   # claude-code | codex | glm (null = the runner's default)
  model: null                    # e.g. gpt-5 (null = the engine's default). The panel setting wins.
  code_source: worker            # deep mode: worker (Worker reads the repo via the GitHub API) | local (runner reads its own clone)

Review modes ​

  • diff (default): the model sees the changed hunks with line numbers. Cheapest and fastest.

  • context: the model also gets read-only context, in this order until context.max_bytes is used up:

    1. each changed file in full at the PR head,
    2. local files those files import (relative JS/TS and Python imports),
    3. files that use a changed or exported function, class or constant (GitHub code search).

    diff_exclude matches and binary files are never sent. Everything is inside the untrusted-data guard and labelled as context. Findings still point only at diff lines; context is used to justify them, for example "this new parameter breaks the call in src/cart.js". Code search indexes the default branch, so callers added in the same PR are already covered as changed files, and very new files may be missed. Expect roughly 2–5× the tokens of diff on small PRs; the footer shows a separate context count.

  • deep (hosted): context plus read-only tools (read_file, search_symbols, find_callers, semantic_search) over a repo index, used in a bounded loop. With a provider that can't call tools, it falls back to context and says so. See Review modes.

PR template and trusted authors ​

Two opt-in, raise-only rules that gate the light paths on who wrote the PR and how well it is described:

  • pr_template checks the PR description for the configured sections (default Testing, Risk, How to revert). A heading (## Testing) or a label line (**Testing:** / Testing:) opens a section, case-insensitively, and the three defaults also accept aliases (Tests, Test plan, How was this tested, Risks, Risk level, Rollback, Revert plan, Revert, How to roll back). Only real text counts: HTML comments, placeholders and checkbox-only lines are stripped before the min_chars check. A missing section raises the PR to missing_lane with a label-style reason ("PR description is missing: Testing, How to revert (pr_template)"). With apply_to: agents (default) only PRs carrying the agent marker are checked; editing the PR body re-runs the check without a new model call (the LLM result is cached per head SHA). The body is untrusted, but the rule can only raise a lane, so a forged body gains nothing.
  • lanes.trusted_authors makes every auto path (with applies_to: code_auto, only lanes.code_auto candidates; with all_auto, docs/style auto paths too) available only to the listed GitHub logins. The author is pull_request.user from GitHub, so it cannot be faked in the body. Bots (a login ending in [bot], or user type Bot) are untrusted unless listed exactly. With agents: as_author (default) an agent-marked PR is judged by the human who directed the agent; with untrusted, agent-marked PRs never get the auto lane. An untrusted author sees the reason "author <login> is not on the trusted list (lanes.trusted_authors)". The Worker's server floor re-applies both rules to runner results, so an old or modified runner cannot skip them.

Agents and learning outcomes ​

MergeJudge learns from what people do with its lanes (merging over a review lane, reverting, labelling). Bots never count. A coding agent that acts through a human's login would look human, so there are three ways to exclude it:

  • learning.exclude_actors: logins whose merges and label changes are never recorded.
  • The PR label mergejudge:agent on the PR when it is merged or closed. Adding or removing this label is never an outcome, and label changes on a PR that has it are ignored too.
  • A line MergeJudge-Actor: agent in the PR body or in the merge commit message: gh pr merge 123 --merge --body "MergeJudge-Actor: agent".

If MergeJudge cannot read the merge commit message when it needs it, it records nothing for that merge.

Notes ​

  • Token use. The comment footer shows a token breakdown (diff, policy, rubric). To save tokens: set policy_files: [], trim rubrics.packs, lower policy.max_total_bytes, or extend diff_exclude.
  • Invalid config fails the run with a message naming the bad key, so mistakes are visible.

The MergeJudge runner is open source (MIT). MergeJudge never merges pull requests itself. MergeJudge is not affiliated with or endorsed by GitHub. Sign in · Impressum · Privacy · Terms