Configuration reference

Configuration reference

Bubo reads one runtime config file: config/env.toml under BUBO_ROOT.

Start from the packaged template:

bubo init
# or, from a source checkout:
cp config/env.example.toml config/env.toml

config/env.toml is local operator state. Do not commit real tokens.

String values support environment-variable interpolation:

token = "${GITLAB_TOKEN}"                         # required; fails if unset
url   = "${GITLAB_URL:-https://gitlab.com}"       # fallback when unset

Use $$ for a literal dollar sign.

Runtime requirements

ResourceMinimumRecommendedNotes
CPU2 vCPU4 vCPUGit checkout, JSON parsing, and agent subprocesses.
RAM2 GB4 GBIncrease with review concurrency.
Disk10 GB20 GB+Worktrees, SQLite state, logs, reports, and agent installs.
Networkoutbound HTTPSoutbound HTTPSSCM host and LLM provider access. No inbound port is required for polling.

Required tools:

ToolRequired for
Python 3.14+Bubo runtime.
Node.jsCodex or Claude CLI installs.
gitRepository checkout over tokenized HTTPS.
Codex, Claude, or another CLI agentReview execution.

[scm]

KeyTypeDefaultDescription
providerstring"gitlab"SCM backend. Allowed: "gitlab", "github". BUBO_PROVIDER overrides this for one run.

[gitlab]

Used when [scm].provider = "gitlab".

KeyTypeDefaultDescription
urlstring"https://gitlab.com"GitLab web URL. Use your self-hosted GitLab URL when needed.
api_urlstring"<url>/api/v4"GitLab REST API URL for MRs, diffs, and outcomes.
bot_usernamestringunsetBot username used to separate bot comments from developer replies.
tokenstringunsetGitLab token, api scope. Exported as GITLAB_TOKEN, GITLAB_PERSONAL_ACCESS_TOKEN, and GLAB_TOKEN, and used as the credential for the HTTPS git clone — sent as a per-call auth header, never written to .git/config.

[github]

Used when [scm].provider = "github".

KeyTypeDefaultDescription
api_urlstring"https://api.github.com"GitHub REST API base. Use https://<host>/api/v3 for GitHub Enterprise Server. The clone host is derived from it (api.github.comgithub.com; GHES → its own host).
bot_usernamestringunsetBot username used to separate bot comments from developer replies.
tokenstringunsetGitHub token with pull-request read/write access. Exported as GITHUB_TOKEN, GITHUB_PERSONAL_ACCESS_TOKEN, and GH_TOKEN, and used as the credential for the HTTPS git clone — sent as a per-call auth header, never written to .git/config.

[review]

Controls polling, filtering, and review-output policy.

KeyTypeDefaultDescription
dry_runbooleantrueStore planned findings without posting SCM comments.
max_merge_requests_per_pollinteger5Maximum MRs/PRs queued by one poll cycle.
max_findings_per_merge_requestinteger5Maximum findings accepted from one review. Also fills {{MAX_FINDINGS_PER_REVIEW}} in the prompt.
timeout_secondsinteger1800Per-review worker timeout.
min_confidencenumber0.85Minimum finding confidence required to plan or post.
category_min_confidencetable{}Per-canonical-category confidence floors. Only raises the global floor.
calibrate_confidencebooleanfalseDerive category floors from repository outcome history.
calibrate_max_confidencenumber0.97Maximum derived category floor.
allowed_kindsstring array[]Optional allowlist matched against finding severity, category, or type. Empty means no kind filter.
tonestring"terse"Comment voice. Allowed: "terse", "collaborative", "socratic", "formal", "casual".
modestring"collaborate"Surface mode. Allowed: "collaborate", "gate".
suppress_disputed_classesbooleanfalseSuppress finding categories repeatedly disputed on the same repository.
dispute_suppress_thresholdnumber0.5Dispute rate required before a category can be suppressed.
dispute_suppress_min_samplesinteger5Minimum outcome rows required before suppression can apply.
verify_findingsbooleanfalseRe-check surviving findings before planning/posting.
verify_lensesstring array["correctness", "in_diff", "reproduce"]Verification lenses. Each lens is one verifier call per finding.
verify_min_votesinteger2Number of verifier votes required for a finding to survive.
verify_confidence_floornumber0.6Minimum verifier confidence for a real vote to count.
verify_max_findingsinteger5Maximum findings verified per change. Findings beyond the cap post unverified.
verify_timeout_secondsinteger300Timeout for each verifier call.
verify_commandstring array[]Verifier command. Empty reuses [agents].reviewer_command.

Canonical categories for category_min_confidence: correctness, security, concurrency, resource, error_handling, performance, style, docs, test, design, naming, other.

[governance]

Controls AI-code provenance and advisory governance signals.

KeyTypeDefaultDescription
capture_provenancebooleanfalseCapture per-change AI-provenance bands for audit.
ai_trailer_patternsstring arraybuilt-in patternsRegexes matched against commit-message lines for declared AI assistance.
sensitive_path_globsstring array[]fnmatch globs for sensitive paths, such as payments/** or *.pem.
rigor_modulationbooleanfalseAdd heightened-scrutiny prompt context when a change escalates.
escalate_bandsstring array["likely_ai", "collaborative"]Provenance bands that escalate. Allowed bands: unknown, likely_ai, collaborative.
rigor_require_sensitivebooleantrueRequire a sensitive-path match before escalation.
policy_modestring"off"Advisory policy mode. Allowed: "off", "report-only", "soft".

[poller]

Controls state location and single-MR debug targeting.

KeyTypeDefaultDescription
state_dirstring"var"Runtime state directory. Relative paths resolve under BUBO_ROOT.
interval_secondsinteger900Suggested interval for long-running wrappers. Cron/systemd can use their own interval.
target_merge_request_iidintegerunsetDebug filter for one GitLab MR IID. Leave unset in production.

[agents]

Controls the review agent command and agent-facing environment.

KeyTypeDefaultDescription
reviewer_commandstring array["codex", "--ask-for-approval", "never", "exec", "--profile", "bubo", "--skip-git-repo-check"]Command prefix used to run the review agent. The review prompt is appended as the final argument.
llm_modelstring"gpt-5.5"Review model. Exported as LLM_MODEL; bubo init templates it into the agent profile (so it actually drives the model) and the model metric label reads it. Re-run bubo init after changing.
llm_model_effortstring"medium"Reasoning effort — "low", "medium", or "high". Exported as LLM_MODEL_EFFORT; bubo init templates it into the agent profile. Falls back to the deprecated reasoning_effort key.
llm_api_keystringunsetLLM API key, exported as LLM_API_KEY. By default the review agent authenticates via its own login (written by bubo init), so the key is not injected into the agent environment. The one exception is llm_base_url, which reads the key from the environment at request time.
llm_base_urlstringunsetOptional OpenAI-compatible endpoint (in-house gateway, proxy, local server). When set, bubo init points the Codex profile at it via a [model_providers] block, and the agent reads LLM_API_KEY from its environment. Exported as LLM_BASE_URL. Security: this is the one mode that exposes the key to the agent — leave unset unless you need it.
llm_api_key_envstringunsetDeprecated. Named an extra env var to expose the key under; the agent now authenticates via its own login. Still honored when set, but prefer removing it.
dry_runbooleantrueExported as REVIEW_DRY_RUN for the agent. Posting is controlled by [review].dry_run.
codex_profilestring"bubo"Exported as CODEX_REVIEW_PROFILE. Used by Codex-based reviewer commands.
codex_sandboxstring"read-only"Exported as CODEX_SANDBOX. Used by Codex-based reviewer commands.
post_no_findings_commentbooleantruePost one change-level acknowledgement when a review finds no actionable issues. Honors [review].dry_run.
no_findings_comment_bodystring"Automated review ran — no issues found."Body for the no-findings acknowledgement. Empty disables the acknowledgement.

Prompt path note: the runtime reads prompts/00-meta.md under BUBO_ROOT. Set BUBO_PROMPT_SOURCE to use another prompt file.

[telemetry]

Controls OpenTelemetry and cost estimation.

KeyTypeDefaultDescription
enabledbooleanfalseExport metrics and spans to an OTLP collector. SQLite state is written either way.
service_namestring"bubo"OTel service name.
environmentstring"dev"Environment label, such as dev, staging, or prod.
otlp_endpointstring""OTLP/gRPC collector endpoint.
otlp_protocolstring"grpc"Only grpc is supported.
export_interval_secondsinteger30Metric export interval.
emit_finding_eventsbooleantrueEmit per-finding lifecycle metrics.
emit_outcome_syncbooleantrueEmit outcome-sync metrics.
input_per_1mnumber0.0Estimated input-token price per 1M tokens.
output_per_1mnumber0.0Estimated output-token price per 1M tokens.
cached_input_per_1mnumber0.0Estimated cached-input price per 1M tokens.

[analytics]

Anonymous, aggregate usage analytics — on by default, opt-out. No code, diffs, findings, tokens, repo names, or paths are ever sent.

KeyTypeDefaultDescription
enabledbooleantrueSend anonymous usage counts. Set false to opt out.
endpointstringbuilt-inOTLP log-ingestion endpoint. Blank disables sending.
api_keystringbuilt-inProject key for the endpoint. Blank disables sending.

Two environment kill-switches override the config: BUBO_ANALYTICS=0 (also false/no/off) and the cross-tool DO_NOT_TRACK=1 convention. Either one disables analytics regardless of enabled.

[mcp_server]

Controls the bubo-mcp server. See MCP server for client setup.

KeyTypeDefaultDescription
transportstring"stdio"Allowed: "stdio", "http".
hoststring"127.0.0.1"HTTP bind address. Ignored for stdio.
portinteger8765HTTP bind port. Ignored for stdio.
bearer_tokenstringunsetRequired for HTTP transport. Clients send Authorization: Bearer <token>.

[[projects]]

One block per repository.

KeyTypeDefaultDescription
pathstringrequiredRepository path, such as group/repo, group/subgroup/repo, or owner/repo.
enabledbooleantrueInclude or skip the repository during polling.

Direct environment overrides

VariableDescription
BUBO_ROOTInstall root. Defaults to ~/.local/share/bubo.
BUBO_BASE_DIRRuntime state directory. Usually derived from [poller].state_dir.
BUBO_PROVIDEROverrides [scm].provider for one run.
BUBO_PROMPT_SOURCEOverrides the prompt template path.
BUBO_MCP_TRANSPORTOverrides [mcp_server].transport.
BUBO_MCP_HOSTOverrides [mcp_server].host.
BUBO_MCP_PORTOverrides [mcp_server].port.
BUBO_MCP_BEARER_TOKENOverrides [mcp_server].bearer_token.

Runtime files

PathDescription
$BUBO_ROOT/config/env.tomlOperator config.
$BUBO_BASE_DIR/state/reviewer.sqliteReview state, findings, and outcomes.
$BUBO_BASE_DIR/work/Per-change worktrees.
$BUBO_BASE_DIR/reports/Agent transcripts.
$BUBO_BASE_DIR/log/JSON-line logs.
$BUBO_BASE_DIR/rendered-prompts/Rendered prompt files.
$BUBO_ROOT/prompts/00-meta.mdDefault prompt template.
$BUBO_ROOT/skills/code-reviewer/Bundled review skill.
~/.codex/config.tomlCodex client config written by bubo init, unless skipped.
~/.claude/settings.jsonClaude settings written by bubo init, unless skipped.

Sample config/env.toml

This sample uses GitLab, Codex, dry-run reviews, no HTTP MCP server, and local SQLite state. Change only the provider block and project paths for GitHub.

[scm]
provider = "gitlab"
 
[gitlab]
url          = "https://gitlab.com"
api_url      = "https://gitlab.com/api/v4"
bot_username = "bubo"
token        = "${GITLAB_TOKEN}"
 
[github]
api_url      = "https://api.github.com"
bot_username = "bubo"
# token      = "${GITHUB_TOKEN}"
 
[review]
dry_run                        = true
max_merge_requests_per_poll    = 5
max_findings_per_merge_request = 5
timeout_seconds                = 1800
min_confidence                 = 0.85
category_min_confidence        = {}
calibrate_confidence           = false
calibrate_max_confidence       = 0.97
allowed_kinds                  = []
tone                           = "terse"
mode                           = "collaborate"
suppress_disputed_classes      = false
dispute_suppress_threshold     = 0.5
dispute_suppress_min_samples   = 5
verify_findings                = false
verify_lenses                  = ["correctness", "in_diff", "reproduce"]
verify_min_votes               = 2
verify_confidence_floor        = 0.6
verify_max_findings            = 5
verify_timeout_seconds         = 300
verify_command                 = []
 
[governance]
capture_provenance    = false
sensitive_path_globs  = []
rigor_modulation      = false
escalate_bands        = ["likely_ai", "collaborative"]
rigor_require_sensitive = true
policy_mode           = "off"
 
[poller]
state_dir        = "var"
interval_seconds = 900
# target_merge_request_iid = 123
 
[agents]
reviewer_command = [
  "codex",
  "--ask-for-approval",
  "never",
  "exec",
  "--profile",
  "bubo",
  "--skip-git-repo-check",
]
llm_model                  = "gpt-5.5"
llm_model_effort           = "medium"
llm_api_key                = "${LLM_API_KEY}"
# llm_base_url             = "https://llm.internal.example/v1"  # OpenAI-compatible endpoint
dry_run                    = true
codex_profile              = "bubo"
codex_sandbox              = "read-only"
post_no_findings_comment   = true
no_findings_comment_body   = "Automated review ran — no issues found."
 
[telemetry]
enabled                 = false
service_name            = "bubo"
environment             = "prod"
otlp_endpoint           = "http://127.0.0.1:4317"
otlp_protocol           = "grpc"
export_interval_seconds = 30
emit_finding_events     = true
emit_outcome_sync       = true
input_per_1m            = 0.0
output_per_1m           = 0.0
cached_input_per_1m     = 0.0
 
[analytics]
# enabled = false   # anonymous usage analytics are on by default; uncomment to opt out
 
[mcp_server]
transport = "stdio"
host      = "127.0.0.1"
port      = 8765
# bearer_token = "${BUBO_MCP_TOKEN}"
 
[[projects]]
path    = "group/repo"
enabled = true
 
[[projects]]
path    = "group/another-repo"
enabled = false
MountainOwlMountainOwl
Bubo · MIT licensed · © 2026