Obvious/Help Center

config.yml Reference

Published May 22, 2026 · Last updated June 18, 2026 · 8 min read

1. Overview

.obvious/config.yml is the per-repo policy contract for Obvious autobuild. It parameterizes repo-specific workflow values — default base branch, PR merge method, PR-review behaviour, preview-deploy URLs, QA guide locations, and CI "generated file" commands — so the agents that work in your repository never have to hard-code them.

The file is optional. When it is absent, every value falls back to a built-in default and autobuild still runs. You only add the fields you want to change.

Where it lives: .obvious/config.yml at your repository root (the directory that contains .git). A .obvious/config.yaml extension is accepted as a fallback; .yml is tried first.

How it's loaded & validated:

  • The config is parsed fail-soft: malformed YAML, a non-object root, or an invalid field never crashes autobuild — it is reported as a diagnostic and the affected value falls back to its default.

  • The validate command (below) returns a list of error strings and exits non-zero when any field is invalid, so you can check a config in CI before relying on it.

  • Config changes may take a short time to take effect after editing.


2. Managing the file — the obvious:config CLI

Use the obvious config CLI to create, read, edit, and validate the file rather than hand-editing YAML where possible. Run it from your repository root:

obvious config <command> [options]
CommandSignatureEffect
initinit [--cwd path] [--force]Write the skeleton config (branches.defaultBase: main). Skips if the file already exists unless --force.
getget <dotpath> [--cwd path]Resolve and print one value with a source annotation, e.g. main (config) or squash (fallback).
setset <dotpath> <value> [--cwd path]Patch a dot-path, re-validate, and rewrite canonical YAML. Refuses to write an invalid result.
validatevalidate [--cwd path] [--check-github]Parse + validate; print per-field status; exit non-zero on errors. --check-github also diffs your config against your live GitHub repo settings (requires the gh CLI).
dump-envdump-env [--cwd path]Print every REPO_POLICY_* variable as KEY=value.

Flags

  • --cwd / -C <path> — set the working directory the command operates on.

  • --force / -f — overwrite an existing file on init.

  • --check-github — enable drift checks against live GitHub settings on validate.

set coercion & restrictions

  • qa.guidePaths and qa.automationPaths accept comma-separated input, which is split into an array.

  • Numeric fields (preview.deploy.timeoutMinutes, preview.deploy.pollIntervalSeconds, reviews.botFeedback.releaseBranchMaxRounds) are coerced to numbers; non-numeric input is rejected.

  • All other paths are written as strings. After coercion, set re-runs validation and refuses to write an invalid result.

  • ci.generatedFileCommands is an array of objects and cannot be set via set — edit the YAML directly.


3. Complete Field Reference

Fields are grouped by top-level section. Type, Default, and Allowed values come from the config schema.

Source legend — when you run get, each value is annotated with its origin: config = value came from the file; fallback = built-in default used; not-configured = no value and no meaningful default (empty string).

3.1 branches (required)

Key pathTypeDefaultAllowed / notesEffect
branches.defaultBasestringrequired (fallback main)non-empty stringDefault base branch for PRs. Surfaced as REPO_POLICY_BRANCH_DEFAULT_BASE. The section and this key are the only required parts of the config.
branches.draftEarlynot settablealways truePlatform invariant, not a user field. If present, it is ignored with a diagnostic. Draft-early PR creation cannot be disabled by config.

3.2 pr

Key pathTypeDefaultAllowed / notesEffect
pr.merge.methodstringsquashsquashmerge

3.3 reviews

Key pathTypeDefaultAllowed / notesEffect
reviews.automated.enabledboolean(absent → unset)truefalse
reviews.botFeedback.releaseBranchMaxRoundsnumber5numericMax rounds of bot-feedback iteration the agent performs on release-branch PRs.
reviews.humanReview.requestPolicystringreservedDefined in the schema but not currently consumed by the runtime. Treat as reserved.
reviews.routingobjectsee 3.3.1First-match-wins routing of files → review mode.

3.3.1 reviews.routing.rules[]

reviews.routing is { rules: [...] }. Provide at least one rule and at most 20. Each rule:

Key pathTypeRequiredAllowed / notes
reviews.routing.rules[].namestringyesnon-empty; human-readable label
reviews.routing.rules[].matchstring[]yesnon-empty array of repo-relative glob patterns; first match wins across all rules
reviews.routing.rules[].modestringyesnone
reviews.routing.rules[].reviewersarray of { type }only when mode: specializedrequired & non-empty for specialized; forbidden for none / single
reviews.routing.rules[].reviewers[].typestringyes (within reviewers)built-in types: general

A catch-all rule (match: ['**'] as the sole pattern, placed last) is recommended — its absence triggers a non-blocking diagnostic, not an error.

3.4 ci

Key pathTypeDefaultAllowed / notesEffect
ci.generatedFileCommandsarray of objects[]each entry validated belowCommands that regenerate checked-in generated files when matching files change. The first entry's command is surfaced as REPO_POLICY_SNAPSHOT_COMMAND.
ci.generatedFileCommands[].namestring''optionalHuman-readable label for the command.
ci.generatedFileCommands[].filePatternsstring[][]optionalGlob patterns that trigger the command. Non-string / blank entries are filtered out.
ci.generatedFileCommands[].commandstringrequired per entrynon-emptyShell command to run. Entries without a non-empty command are skipped.

Per-entry shape is enforced at parse time: malformed entries are dropped, not errored.

3.5 preview

Key pathTypeDefaultAllowed / notesEffect
preview.primaryEnvironmentstring''key into preview.environmentsSelects which environment supplies the URL/backend templates. If omitted, the first key in environments is used.
preview.environments.<key>.urlTemplatestring''e.g. https://pr-{prNumber}.preview.example.comPreview URL template for that environment. Backs REPO_POLICY_PREVIEW_URL.
preview.environments.<key>.backendBaseUrlstring''e.g. https://api.staging.example.comBackend base URL for that environment. Backs REPO_POLICY_PREVIEW_BACKEND_URL.
preview.deploy.pollIntervalSecondsnumber30numericHow often to poll for preview-deploy readiness.
preview.deploy.timeoutMinutesnumber10numericMax wait for a preview deploy. Surfaced as REPO_POLICY_PREVIEW_TIMEOUT_MINUTES.

The REPO_POLICY_PREVIEW_URL and REPO_POLICY_PREVIEW_BACKEND_URL values are computed from primaryEnvironment + environments. You do not write them as keys in the file.

3.6 qa

Key pathTypeDefaultAllowed / notesEffect
qa.guidePathsstringstring[]''single path or list
qa.automationPathsstringstring[]''single path or list

When given as a list, both render joined by , . Setting via the CLI accepts comma-separated input and splits it into an array.

3.7 triageProjects

Key pathTypeDefaultAllowed / notesEffect
triageProjects.reviewProjectIdstring(none)Obvious project ID (prj_…)Project used to route automated PR-review work. null when absent or blank.

3.8 Reserved / not currently consumed

These top-level sections are accepted without error but are not read by any current runtime reader. Document them as reserved, not active config:

SectionStatus
notificationsAccepted; readable via get, but not currently consumed and has no REPO_POLICY_* var. Reserved.
releaseAccepted but not currently parsed. Reserved.
ownersAccepted but not currently parsed. Reserved.
runbooksAccepted but not currently parsed. Reserved.

Unknown future fields are ignored so newer configs never break older agents.

3.9 REPO_POLICY_* environment variables

These are injected into the build shell so scripts can read policy without parsing YAML. Use ${VAR:-fallback} syntax in shell.

Env varConfig source pathFallback
REPO_POLICY_BRANCH_DEFAULT_BASEbranches.defaultBasemain
REPO_POLICY_MERGE_METHODpr.merge.methodsquash
REPO_POLICY_PREVIEW_URLpreview.environments.<primary>.urlTemplate(empty)
REPO_POLICY_PREVIEW_BACKEND_URLpreview.environments.<primary>.backendBaseUrl(empty)
REPO_POLICY_PREVIEW_TIMEOUT_MINUTESpreview.deploy.timeoutMinutes10
REPO_POLICY_SNAPSHOT_COMMANDci.generatedFileCommands[0].command(empty)
REPO_POLICY_RELEASE_BASEthe repo's live default branch (not from config)(unset; use ${REPO_POLICY_RELEASE_BASE:-main})

4. Validation Rules & Constraints

Enforced by validate (and by set before writing). Each violation is a returned error string; a non-empty list fails validation.

Structural

  • The config root must be a YAML mapping (object) — not a scalar or list.

  • branches is required and must be an object; branches.defaultBase must be a non-empty string.

Type checks (only enforced when the field is present)

  • pr must be an object; pr.merge must be an object; pr.merge.method must be a string.

  • preview.deploy.timeoutMinutes and preview.deploy.pollIntervalSeconds must be numbers.

  • reviews.automated must be an object; reviews.automated.enabled must be a boolean.

  • reviews.botFeedback.releaseBranchMaxRounds must be a number.

  • ci.generatedFileCommands must be an array.

reviews.routing cross-field rules

  • routing must be an object; routing.rules is required, must be a non-empty array, ≤ 20 rules.

  • Per rule: name non-empty string; match non-empty array of non-empty strings; mode{none, single, specialized}.

  • reviewers is required & non-empty when mode: specialized and must not be set for none / single; each reviewers[].type must be a non-empty string.

Soft (diagnostic, non-blocking) rules — reported but never fail validation:

  • Any unknown top-level key → "Ignoring unknown .obvious config field: …".

  • draftEarly (top-level or under branches) → "platform invariant and is always true".

  • reviews.routing without a catch-all (match: ['**']) rule → recommendation diagnostic.

  • Malformed YAML / non-object root / unreadable file → diagnostic + full fallback policy (no crash).

Safety note: repo config is runtime guidance only. Platform safety rules, permission gates, branch protection, completion gates, and tool-layer enforcement cannot be weakened by .obvious/config.yml.


5. Example Configurations

5.1 Minimal / default

The skeleton written by init. Everything else uses built-in defaults.

branches:
  defaultBase: main

5.2 Complete annotated example

Every supported, actively-consumed field with representative values:

# ── Required ────────────────────────────────────────────────
branches:
  defaultBase: main            # string (required) → REPO_POLICY_BRANCH_DEFAULT_BASE
  # draftEarly: NOT ALLOWED — platform invariant, always true (diagnosed if set)

# ── Pull requests ───────────────────────────────────────────
pr:
  merge:
    method: squash             # squash | merge | rebase (default squash) → REPO_POLICY_MERGE_METHOD

# ── Reviews ─────────────────────────────────────────────────
reviews:
  automated:
    enabled: true              # boolean; absent ⇒ unset (workspace default, then fail-open)
  botFeedback:
    releaseBranchMaxRounds: 5  # number (default 5)
  humanReview:
    requestPolicy: on-request  # string — reserved (not currently consumed)
  routing:                     # first-match-wins; ≤ 20 rules
    rules:
      - name: API security review
        match:                 # non-empty glob list
          - src/routes/**
        mode: specialized      # none | single | specialized
        reviewers:             # required & non-empty ONLY for `specialized`
          - type: security     # general | security | performance (repos may add local types)
      - name: Docs only
        match:
          - docs/**
        mode: none             # reviewers MUST be omitted for none/single
      - name: Catch-all
        match:
          - '**'               # recommended final catch-all rule
        mode: single

# ── CI: regenerate checked-in generated files ───────────────
ci:
  generatedFileCommands:
    - name: codegen-snapshots                  # string (optional)
      filePatterns:                            # string[] (optional)
        - src/generated/**
      command: npm run generate:snapshots      # required; [0].command → REPO_POLICY_SNAPSHOT_COMMAND

# ── Preview deployments ─────────────────────────────────────
preview:
  primaryEnvironment: staging  # key into environments (else first key used)
  environments:
    staging:
      urlTemplate: https://pr-{prNumber}.preview.example.com   # → REPO_POLICY_PREVIEW_URL
      backendBaseUrl: https://api.staging.example.com          # → REPO_POLICY_PREVIEW_BACKEND_URL
  deploy:
    pollIntervalSeconds: 30    # number (default 30)
    timeoutMinutes: 10         # number (default 10) → REPO_POLICY_PREVIEW_TIMEOUT_MINUTES

# ── QA ──────────────────────────────────────────────────────
qa:
  guidePaths:                  # string | string[]
    - docs/QA.md
    - e2e/QA.md
  automationPaths:             # string | string[]
    - .github/staging-automations/

# ── Triage routing ──────────────────────────────────────────
triageProjects:
  reviewProjectId: prj_xxxxxxxx  # Obvious project ID for automated PR-review routing

Not configurable (do not add): branches.draftEarly (platform invariant, always true), branches.release.base (removed — the release base is the repo's live default branch via REPO_POLICY_RELEASE_BASE), and the computed preview URL/backend values (derived, not literal keys). notifications, release, owners, and runbooks are accepted without error but are not currently consumed.

Was this helpful?