Configuration
Global defaults live in config.json under the platform config directory.
co-maintainer set writes that file. init /
sync also record per-repo choices there (not API tokens).
Generated guides sit under repos/ in the same tree. Full paths:
Caching.
serve reads the same config.json for the GitHub App, webhook
URL, dashboard auth defaults, and CLI remote-review targets.
Precedence
For CLI commands (probe, init, sync, review):
| Layer | Examples |
|---|---|
| Highest | --token=, --auth=pat, --max-commits=500 on the command |
| Env file | Only when you pass --env=./.env (sets vars if not already set) |
| Environment | GITHUB_TOKEN, CO_MAINTAINER_TOKEN, OPENROUTER_API_KEY, OPENCODE_API_KEY, CO_MAINTAINER_AUTH, model env vars, remote review variables |
| Per-repo | repos["owner/repo"] after a successful init/sync |
| Global | set defaults in config.json |
| Lowest | Prompts when a required value is still missing |
API tokens are never stored per repo. They come from --token=, env, or
set --token=... only.
co-maintainer set
Persists global settings so later commands can skip flags and prompts.
co-maintainer set --auth=gh --ai=openrouter --token=YOUR_KEY \
--low-model=openai/gpt-oss-120b --high-model=openai/gpt-5.6-luna
co-maintainer set --github-app-id=... --github-app-private-key-file=./app.pem
co-maintainer set --github-app-id=... --github-app-private-key-path=./app.pem
co-maintainer set --remote-host=https://your-server --remote-token=cmr_...
--ai also takes openai and anthropic. There is no default model for any
provider, so both models are saved the first time. See
AI providers.
set flags
| Flag | Written to | Used by |
|---|---|---|
--token=... |
token |
AI calls for the provider in ai |
--ai-key=... |
token |
An alias for --token, named for what it is |
--ai=none|openrouter|openai|anthropic|opencode-zen|opencode-go |
ai |
CLI and dashboard jobs, see AI providers |
--low-model=... / --high-model=... |
lowModel, highModel |
CLI and dashboard jobs |
--auth=gh|pat |
auth |
CLI GitHub reads |
--github-pat=... |
githubPat |
CLI when --auth=pat |
--github-app-id=... |
githubAppId |
serve |
--github-app-private-key=... |
githubAppPrivateKey |
serve |
--github-app-private-key-file=path |
githubAppPrivateKey (contents) |
serve |
--github-app-private-key-path=path |
githubAppPrivateKeyPath (path only) |
serve |
--github-webhook-secret=... |
githubWebhookSecret |
serve webhook HMAC |
--github-oauth-client-id=... etc. |
OAuth fields | Dashboard GitHub sign-in |
--password=... |
dashboardPasswordHash (stored as a hash) |
Dashboard password sign-in |
--disable-auth=password |
passwordAuthDisabled |
Default for serve |
--enable-auth=github |
githubAuthEnabled |
Default for serve |
--remote-host=... |
remoteHost |
review --remote |
--remote-token=... |
remoteToken |
review --remote |
--review-blocking=model|severity |
reviewBlocking |
Whether a review's own severity decides a blocking finding |
--codegraph-version=SEMVER |
codegraphVersion |
The pinned codegraph version, replacing the built-in one |
--remote-byok=KEY |
remoteByok |
review --remote, your own AI key, see Remote review |
--remote-byok-default=on|off |
remoteByokDefault |
Whether review --remote sends remote-byok without being asked |
--remote-byok-policy=off|allow|require |
remoteByokPolicy |
serve, whether remote clients may send their own key |
--review-policy=everyone|trusted-auto|on-request-only |
reviewPolicy |
serve, the review policy new repositories start with |
--review-policy-file=PATH |
reviewPolicy (the parsed policy) |
The same, from a JSON file. A bad policy is refused and nothing is saved |
--repo=owner/repo |
repos[owner/repo] |
Write the repository-scoped flags below to that repository, not the global config |
--review-system-prompt=TEXT |
repos[owner/repo].reviewSystemPrompt |
A repository's own review instructions. Needs --repo. A blank value clears it |
--review-system-prompt-max-chars=N |
repos[owner/repo].reviewSystemPromptMaxChars |
Cap those instructions in characters. Needs --repo. 0 or -1 means no limit |
App private key: inline, file contents, or path
Three flags set the GitHub App private key, and they differ in what lands in
config.json:
--github-app-private-key=PEMwrites the key itself.--github-app-private-key-file=pathreads the file and writes its contents. If the file later moves or is deleted, the App keeps working.--github-app-private-key-path=pathwrites only the path. The key stays on disk and is read at startup, so the secret never entersconfig.json. If the file is missing or unreadable,servewarns at startup that the App will not work.
An inline key wins if both an inline key and a path are ever present. set
clears the other form when you pass one, so the choice is unambiguous.
Verification before saving
Whatever the provider is, set asks it whether the key works and, if a model is
given, whether the provider has that model. OpenRouter is checked against its
key and model endpoints, OpenAI and Anthropic against their model list, and
OpenCode against its public model list, which checks the model but not the
key. A bad
key or unknown model is refused with exit code 2 and nothing is written. A
network failure only warns (saved anyway), because that is not a typo. Pass
--no-verify to skip the check, for example in CI with an offline key. The
checks are listed on AI providers.
OAuth setup steps: serve: Sign in.
co-maintainer config
Reads and edits the same config.json without opening the file. set is the
short form of config set, and its output and flags are unchanged.
co-maintainer config list # key, value, and source per row
co-maintainer config get high-model # one raw value, no label
co-maintainer config set remote-host https://review.example.com
co-maintainer config set --remote-token=cmr_...
co-maintainer config unset remote-token
co-maintainer config path # the config file
co-maintainer config path --all # config, repos, and cache directories
list prints where each value came from: env (an environment variable
overrides the file at run time), file, or nothing for an unset key. Secrets
are masked: twelve characters or more keep their last four, shorter ones show
only ••••, and the dashboard password reports set or not set rather than
its hash. get prints the real value, so treat its output as a secret.
Keys are the UserConfig field names in kebab-case (high-model,
remote-host, review-blocking), and camelCase is accepted too. A name that is
not a config key is refused with exit code 2.
Config keys
Every key config.json can hold. The mask column says whether config list
hides the value.
| Key | Type | Source | Masked |
|---|---|---|---|
auth |
gh or pat |
set flag |
no |
ai |
none, openrouter, openai, anthropic, opencode-zen or opencode-go |
set flag |
no |
low-model |
string | set flag |
no |
high-model |
string | set flag |
no |
token |
string | --token or --ai-key |
yes |
github-pat |
string | --github-pat |
yes |
github-app-id |
string | --github-app-id |
no |
github-app-private-key |
PEM string | --github-app-private-key, --github-app-private-key-file |
yes |
github-app-private-key-path |
path string | --github-app-private-key-path |
yes |
github-webhook-secret |
string | --github-webhook-secret |
yes |
webhook-url |
URL string | Settings, Server | no |
github-oauth-client-id |
string | --github-oauth-client-id |
no |
github-oauth-client-secret |
string | --github-oauth-client-secret |
yes |
github-oauth-allowed-user |
string | --github-oauth-allowed-user |
no |
password-auth-disabled |
boolean | --disable-auth=password, --enable-auth=password |
no |
github-auth-enabled |
boolean | --enable-auth=github, --disable-auth=github |
no |
dashboard-password-hash |
scrypt hash | --password |
reported as set or not set |
defaults |
object | init / sync limits |
no |
max-concurrent-jobs |
number | Settings, or the field directly | no |
review-blocking |
model or severity |
--review-blocking |
no |
codegraph-version |
version string | --codegraph-version, or CM_CODEGRAPH_VERSION |
no |
remote-host |
URL string | --remote-host |
no |
remote-token |
string | --remote-token |
yes |
remote-byok |
string | --remote-byok |
yes |
remote-byok-default |
boolean | --remote-byok-default |
no |
remote-byok-policy |
off, allow or require |
--remote-byok-policy, Settings, Remote review |
no |
review-policy |
template name or policy object | --review-policy, --review-policy-file, Settings, Defaults |
no |
remote-sync-timeout-seconds |
number | Settings | no |
max-concurrent-remote-reviews-per-token |
number | Settings | no |
remote-tool-output-max-chars |
number | Settings | no |
remote-notice-shown-for |
list of hosts | Written by review --remote |
no |
repos |
object | Written by init and sync |
no |
config get prints the real value even for a masked key, so treat its output as
a secret.
CM_CODEGRAPH_VERSION overrides codegraph-version at run time, ahead of the
file. See Codegraph.
Unset
co-maintainer set --unset=token
co-maintainer set --unset=github-webhook-secret
co-maintainer set --unset=disable-auth
--unset=disable-auth clears passwordAuthDisabled so the dashboard password
is allowed again. Match --unset= to the field name or the flag name (see
co-maintainer set --help).
Per-repo memory
After each successful init or sync,
config.json gains repos["owner/repo"] with:
| Stored | Not stored |
|---|---|
auth, ai, models |
API token |
max-* limits |
GitHub PAT (global only) |
include-* flags |
|
only-request-changed-pr, pr-state |
|
review-system-prompt, review-system-prompt-max-chars |
A plain co-maintainer sync owner/repo reuses those values. Pass new flags
on sync or run init again to change includes, limits, or pull request
filters permanently. --only-request-changed-pr stays on once saved. To
collect every pull request state again, pass --pr-state=open,closed,merged.
clear owner/repo deletes this entry, so the repository
goes back to "never inited" and the next build is an init.
review-system-prompt and its cap
are the exception: init and sync never write them. Set them with
co-maintainer set --repo=owner/repo or the repository Settings page.
Guide files (SKILL.md, CODEBASE.md, review guides) live under
<config>/co-maintainer/repos/<owner>/<repo>/, not in your git clone. See
Caching. co-maintainer view prints them
locally, and view --remote prints the copies a configured review server holds
(see Remote review).
Secrets on disk
config.json may contain tokens, PATs, App private keys, and webhook/OAuth
secrets in plain text. The file is chmod 0600 where supported (not on
Windows).
| Situation | Suggestion |
|---|---|
| Personal laptop | set is fine |
| Shared CI | Prefer environment variables or --env=PATH, avoid committing config. For the GitHub Action see CI |
serve host |
Lock down the config directory like any secret store |
Dashboard Settings can also save credentials after validating them with GitHub.
Path overrides
| Variable | Effect |
|---|---|
CM_CONFIG_PATH |
Replace config.json location (tests) |
CM_REPOS_DIR |
Replace generated guides root (must match serve for server init) |
More cache and database paths: Caching.
Remote review variables
These let a job with no config file, such as CI, reach a review server.
A flag wins over the environment, and the environment wins over config.json.
An empty value counts as not set.
| Variable | Effect |
|---|---|
CM_REMOTE_HOST |
The server for review --remote and view --remote, the same as remote-host |
CM_REMOTE_TOKEN |
The remote review token, the same as remote-token. Keeps it off the command line |
CM_REMOTE_BYOK |
Your own AI key for review --remote --remote-byok, the same as remote-byok |
CM_REMOTE_BYOK_POLICY |
On the server, off, allow or require. It overrides remote-byok-policy, so a hosted deployment cannot be talked out of it from the dashboard |
config list shows env as the source of remote-host and remote-token when
their variable is set.