Book a call
LESSON14mVERIFIED 2026-08-05 · CLAUDE CODE 2.1.221 · CODEX CLI 0.146.0 · GITHUB COPILOT CLI 1.0.78 · ANTIGRAVITY CLI 1.1.10 · KIMI CODE CLI 0.31.1

Reviewing each other's agent configuration

A one-character typo in a skill's frontmatter loaded fine and silently replaced the trigger with a sentence about database replicas. How to review the config nothing tests.

A one-character diff that two reviewers would approve

Here's a change we've put in front of every harness in this course. There's a typo in the description of the skill in the frontmatter and it's like this: descriptoin instead of description.

DIFF
+---
+name: db-notes
+descriptoin: Notes about the database.
+---
+
+# DB notes
+
+The primary is read-write, the replica is read-only.

Nothing rejects it. So what we did was — created an empty repository and put there just this file with the mistake and a valid skill. Then we ran the one command out of the six tools which is able to list project skills without starting a session:

TEXT
$ copilot skill list
Project skills:
  db-notes - The primary is read-write, the replica is read-only.
  release-check - Use when cutting a release - checks the changelog, tags and version bump are consistent.

— and it lists the skill, in the description field, it contains this: The primary is read-write and the replica is read-only. Which is a sentence from the body. And the exit status is 0, no warning, and the skill got loaded anyway. This is important because, as we said, description is what the model looks at to decide if this skill is relevant in a given situation. So here, it becomes a sentence about replicas which will never match anything anyone types. Just to be sure we've changed the body and run it again:

TEXT
$ copilot skill list
Project skills:
  db-notes - ZZZ first paragraph marker. Second paragraph.
  no-desc - Only a body sentence here, nothing else.
  release-check - Use when cutting a release - checks the changelog, tags and version bump are consistent.

— same thing, it just takes the first paragraph of markdown from the body, ignoring the heading, and concatenates paragraphs.

That's not specific to Copilot, btw. Claude Code's skills reference documents a fallback for the same field: "What the skill does and when to use it. Claude uses this to decide when to apply the skill. If omitted, uses the first paragraph of markdown content." So basically here, if you don't provide something, it doesn't throw an error, it replaces it with something sensible — at least in terms of how it looks like in the listing.

Nothing in your pipeline runs this file

Which is the opposite of code changes, where every piece of code is being evaluated by machines before being merged already because of the types, tests, linter, and build process. For agent config changes, there's no such thing — you can create a skill with a completely wrong description and nobody will notice (we don't have tests that it gets used when it should be used), you can write a permission rule that matches nothing and nobody will know (we don't have a compiler for the permission patterns yet), or define a new hook but make a mistake in the matcher and nobody will know (we don't have linter that warns you about it if you mistype a hook matcher). And it's because, as we said, the reviewer is not one of many gates, they're the only gate.

So for code PR-s the majority of the work is actually reading the diff; for config PR-s it's reading the diff too, but that way things slip because the file on the disk is different from what the tool loads and the errors are not being reported by design.

Render it, don't read it

So instead of reading the source, check out the branch and run there the tool with the command that shows you what it does.

To illustrate this, we've created one repository and put there AGENTS.md, .mcp.json, and .claude/settings.json containing a hook, and defined three skills — and then we've been using read-only commands of all six tools (the ones which don't require starting a session):

HarnessWhat a reviewer can runWhat came back
Claude Code 2.1.221claude doctorinstall health, plus an Invalid settings block naming the file when the JSON is broken
Claude Codeclaude mcp listthe repo's server as ⏸ Pending approval, not connected
Codex 0.146.0codex doctorconfig.toml parse ok, MCP servers 0 — all about ~/.codex, not the branch
Codexcodex mcp listNo MCP servers configured yet — it doesn't read .mcp.json
Copilot CLI 1.0.78copilot skill listevery project skill with its resolved description
Copilot CLIcopilot mcp listWorkspace servers: notes (local)
cursor-agent 2026.07.23agent mcp listNo MCP servers configured (expected in .cursor/mcp.json or ~/.cursor/mcp.json)
Antigravity 1.1.10agy agents, agy plugin listthe agents and plugins it has imported
Kimi Code 0.31.1kimi doctorOK config.toml, OK tui.toml — both under ~/.kimi-code, neither in the repo

As you can see, most of these inspect either the reviewer's machine or a file that they can find in their home directory — like kimi which, despite saying everything is good, actually validates two files which are in the home directory and not on the branch. Or codex doctor which, apart from the fact it's a 145-lines-long install report, also tells you it's a git repository, which is basically the only thing it says about the branch. So whenever you run a doctor command and it says everything is good, make sure you check what files it actually opened.

But the most important thing is that the best of these commands don't even parse anything, they just resolve the values. For example, skill listing is better than looking at SKILL.md because it shows you the description the model sees rather than the one you wanted to provide. The key is to find a command that resolves something.

Where the silence lives

For example, if we introduce a trailing comma in .claude/settings.json — then the doctor command tells us there's an error because of invalid or malformed JSON and prints the full path:

TEXT
$ claude doctor
Invalid settings
- /private/tmp/reviewing-config-check/.claude/settings.json: Invalid or malformed JSON

But the exit status is 0 so if you were to have a CI setup that only runs this command, it would accept the repository even if its settings file can't be loaded.

Or, if we make a valid JSON but use "alow" instead of "allow" — then the doctor command says everything's good, and exits with code 0:

TEXT
$ claude doctor
No installation issues found.

But there's no such key as allow for anything to consume. That's actually how every tool we pointed at a misspelled key behaved — using Copilot we got a skill with the wrong description key, Claude Code happily consumed the settings file with the wrong allow key, and so did the only tool here that checks a schema rather than only syntax (schema validation is more strict than syntax validation).

Kimi's config validator is different as it can take a file path, so we can point it to a branch file. And it indeed complained when we had a permission rule with a decision not being allow, deny, or ask:

TEXT
$ kimi doctor config ./config.toml
Kimi doctor found 1 issue.

ERROR config.toml  /private/tmp/kimi-pr-check/config.toml
  Invalid configuration in /private/tmp/kimi-pr-check/config.toml.
  Validation issues:
    permission.rules[0].decision: Invalid option: expected one of "allow"|"deny"|"ask"

But… if we hand it a file with a top-level key it doesn't know and a whole invented section, it says it's all good (and exits with code 0).

So generally speaking, if you see a key you don't know, it doesn't mean there's an error — usually, if the shape is wrong, the tool complains. If it's misspelled — it doesn't. So whenever you spot a key and can't remember what it does from the top of your head, think about it for a moment instead of just moving your eyes to the long value below.

The only exception here is Codex which has the strict-config flag which, if enabled, should throw an error if it encounters a field not supported by this version. But it seems like we weren't able to make it work so far, so it's worth trying but not worth relying on.

There's even worse kind of asymmetry though — in Claude Code, if there's a schema error the entire user, project, or local settings file is rejected instead of just the problematic entry.

So if we were to define a hook with an array value in it, the entire file would be invalid and all the deny rules defined above would stop working (until we remove this one). But if you were to deploy this to an organisation as managed settings, the tool would actually remove just this hook and keep the rest of them.

Three lines that outweigh the rest of the PR

But anyway, most of the changes in a config diff are prose that can be discussed later. There are three types of lines that can't wait:

  1. Permission rules — the pattern syntax looks very relaxed but is very strict. In Claude Code docs it says: "The space before * matters: Bash(ls *) matches ls -la but not lsof, while Bash(ls*) matches both." And for the colon — "The :* form is only recognized at the end of a pattern. In a pattern like Bash(git:* push), the colon is treated as a literal character and won't match git commands." And Copilot has its own edge cases — shell(git:*) "will match git push but not gitea", and write(.env) "matches a file named .env in any directory, not only the one in the current working directory". You need to treat it as it's written there, not what is said in the PR title; both directions are costly — too narrow does nothing, too wide does something you haven't intended.

  2. MCP servers — every single line here is actually three things:

    • a dependency that gets installed on every teammate's machine

    • a command that the tool runs as a local process

    • some tool description that appears in every session's context

    And from Anthropic's security page: "We encourage either writing your own MCP servers or using MCP servers from providers that you trust", and it "does not security-audit or manage any MCP server". So we've created one booby-trapped to write a file if anything ever starts it. And we've run both mcp list commands — and the file wasn't there, because project servers wait behind a one-time approval. And this is actually the most important part — the review isn't when you merge, it's when somebody approves the server for the first time.

  3. Hooks — they're shell commands which the tool runs on a certain event without asking. From the hooks reference: "Command hooks execute shell commands with your full user permissions. They can modify, delete, or access any files your user account can access." So make sure you actually examine the script the hook runs, not only the line that registers it. And if the script is not in the diff, say so in your review. Hooks are also silent, from Claude Code's configuration-debugging page: "A misspelled tool name produces a matcher that matches nothing, so the hook fails silently." A guardrail that doesn't fire is worse than no guardrail, because everybody believes it's there.

The instructions file is a diff against a shared prompt

And finally — instructions files. AGENTS.md or equivalents — these are being approved most casually by everybody but every team member pays for every line in every session (whether it applies to their setup or not). Here's some questions you can ask yourself:

  • is it fact or preference? If it's a fact, it belongs to the file, if it's a preference — to the personal config

  • is it long-term or short-term? Will it still be valid in a month or is it just about the migration that ends on Friday?

  • and what is the opportunity cost? As said earlier — rule twenty weakens the first nineteen

And if you run an open-source repository, there's another question:

  • what if someone were to create a PR changing this file? The model reads it as instructions, so that PR actually changes how everybody's agent behaves

From the untrusted content advice on Anthropic's security page: "Review suggested commands before approval", "Avoid piping untrusted content directly to Claude". Which is a standard piece of advice but we would say that the most important part of it is actually "review it". You should treat any instruction text as you would treat a script written by a stranger.

Ask for the run, not the explanation

And if you ever need to prove it — ask them for a before/after:

  • skill — the resolved listing

  • permission rule — the command you expected to stop being asked about, run once with the rule and once without

  • hook — the trigger that actually fires

  • and in all of these cases, the same prompt and the same repository state (with one thing changed)

This way you can easily catch this kind of things, and shift some of the work to someone who already has both the branch and the tool in front of them. Asking what they observed when running it is much more useful than any line-level comment in such scenario.

Make it a path, not a habit

To make this a routine thing, you can put config paths in CODEOWNERS. It's a special file which can be placed in .github/, repo's root, or docs/ ("the last matching pattern takes the most precedence"), and "Code owners are automatically requested for review when someone opens a pull request that modifies code that they own." Something like this:

TEXT
# .github/CODEOWNERS — the files that change how everyone's agent behaves
AGENTS.md            @your-org/agent-config
CLAUDE.md            @your-org/agent-config
.mcp.json            @your-org/agent-config
.claude/             @your-org/agent-config
.cursor/             @your-org/agent-config
.agents/             @your-org/agent-config
.github/skills/      @your-org/agent-config

The only thing you need to do additionally is to set the branch protection option GitHub calls "Require review from Code Owners", so it becomes binding.

But what's more important is the social aspect here. Make sure you create a separate PR for your config changes, don't mix it with another PR that fixes a bug — if you make a bug fix and at the same time enlarge an allowlist, it will be seen as a bug fix and the change to the allowlist will slip through because it passes the tests.

What you don't review

And last but not least — everything personal is off-limits. You can't police their local settings file, the one in the home directory, or someone's own skills. Not only they won't like it, reaching for it is also how a shared setup starts feeling like surveillance.

But there's actually a boundary in the file layout that already enforces the separation. In Claude Code, .claude/settings.json is committed as part of the setup (shared), and .claude/settings.local.json is created with an entry in gitignore (personal). Just make sure you add it yourself if you create this file manually.

IN YOUR HARNESS

Claude Code: the file layout is the review boundary

In Claude Code, what is changed in a PR defines what can be seen by the reviewer. The config files a PR can modify are these:

  • CLAUDE.md — instructions loaded into every session in this repository

  • .claude/settings.json — checked-in settings, defining permissions, hooks and environment variables for the whole team

  • .claude/settings.local.json — local settings, which can be ignored by the reviewer as they get gitignored once Claude Code writes to this file

  • .mcp.json at the repository root level — list of servers used by the project, secured with a single-approval workflow

  • .claude/skills/<name>/SKILL.md — a skill, whose description is its trigger

  • .claude/agents/ — subagents definitions

There are 2 commands we can run from the command line, which don't start sessions:

  • claude doctor — which checks if Claude Code is installed and lists any unparseable config files under an "Invalid settings" section

  • claude mcp list — which lists all the servers, with project entries prefixed by "⏸ Pending approval"

BASH
claude doctor      # install health + an "Invalid settings" block naming any file that won't parse
claude mcp list    # every server, with project ones marked ⏸ Pending approval

We ran both in a disposable repo. claude mcp list did not start the project server — it prints "⏸ Pending approval" and a note telling you to run claude to approve, then stops there, which matches its own help text: "Unapproved .mcp.json servers are shown as ⏸ Pending approval and not connected to; approved servers are health-checked." Safe to run on a branch you haven't read yet.

Mind the exit code though. With a deliberately broken .claude/settings.json, doctor printed the failure and exited 0 — so if you wire this into CI, assert on the text, not the status.

And then there's the other one: valid JSON is not valid config. We changed "allow" to "alow" and doctor said "No installation issues found", leaving behind a settings file with no allow key in it. Key names aren't checked, only shapes.

Let's take a look inside a session. There's more to see there, the documentation lists a few sections:

  • /context — what's actually been loaded

  • /permissions — resolved allow/deny rules currently in effect (based on all settings files)

  • /hooks — active hook configurations

  • /mcp — connection and approval status

  • /status — which settings sources are active

In case of a PR with permissions-related changes, we recommend running /permissions on such a branch — it displays the outcome (the merged set of allow/deny rules in effect), not just the one file you're reading.

If you want to make sure that everything's bulletproof, you can enable the ConfigChange hook. It's an event which gets emitted whenever any config file is changed during a session. Its matchers are:

  • user_settings

  • project_settings

  • local_settings

  • policy_settings

  • skills

This way we can keep track of (or reject) the changes made by a colleague in the middle of the session via git pull, for example:

JSON
{
  "decision": "block",
  "reason": "Configuration changes to project settings require admin approval"
}

The only exception is policy_settings, for which the hook gets called but its decision is ignored — in such scenarios the enterprise settings are always being used.

What we ran: Claude Code 2.1.221 on macOS, in a throwaway git repo under /tmp, no permission flags — claude doctor three times (clean settings, malformed JSON, misspelled key) and claude mcp list once, with exit codes measured directly rather than through a pipe. The slash commands, the ConfigChange event and the settings-precedence rules are quoted from the docs as of today.

Codex: almost nothing a PR can change reaches it

In terms of the tools listed, Codex is probably the one with the narrowest review surface when it comes to PRs. Worth knowing before you decide to spend your time on examining the changes that can't possibly affect the state of the tool for your teammate. If you want to see it, you can create a new repo and put a .mcp.json file there, then run:

TEXT
$ codex mcp list
No MCP servers configured yet. Try `codex mcp add my-tool -- my-command`.

That will tell you there's no servers defined and you should initialise one using the CLI. It doesn't find the configuration file. But if you run the following command, you'll get confirmation it's not finding any servers from the other perspective: codex doctor. The tool tells you it found config.toml in ~/.codex/config.toml and that there are 0 mcp servers defined. In terms of what the tool is looking for in the working dir:

  • It finds it's a git repo

  • It finds the root of the repo

  • That's it

Its Configuration section, trimmed to the rows that matter here:

TEXT
  ✓ config       loaded
      cwd                      /private/tmp/reviewing-config-check
      config.toml              ~/.codex/config.toml
      config.toml parse        ok
      MCP servers              0
  ✓ mcp          no MCP servers configured
  ✓ sandbox      restricted fs + restricted network · approval OnRequest

That means that if you use Codex, your repo is just a container with the instructions. The servers, their permissions and sandbox state are defined by your setup so they're not present in the PR to change. If you create an MCP server in .mcp.json, this doesn't mean there's any in place for your teammate on Codex.

That's why it's good to include it in the PR rather than to find out about it afterwards.

What the doctor command is useful for:

  • It's an install report rather than sth you can run per branch

  • It's pretty thorough. It checks if:

    • The runtime is sane

    • Installation steps are consistent

    • The git setup is sane

    • The state databases are up-to-date

    • It works in proper auth mode

    • The sandbox and approval policy are set correctly

    • The connectivity is good

  • So it really matters that the tool finds config.toml and can parse it. Especially when you work on changes in a teammate's personal config file

  • It's also good to have a look at the sandbox and approval policy before proceeding with an unsupervised run, as these define the posture of the session

The trap:

  • You need to know that the configuration can be provided via CLI as well. If you do codex -c key=value, the tool will use the value you set as TOML and if it's not a valid TOML, as a string

  • That means that if you see there's no changes on the config file level, you might want to check if there's no -c flag used in a wrapper script or CI step included in the diff

The only strict flag:

  • There's a --strict-config option in the help output: "Error out when config.toml contains fields that are not recognized by this version of Codex". If you think about it, this is the only tool from the list that documents such a behaviour — the misspelled-key check the others are all missing. But we couldn't get it to fire on the doctor path, so it's a flag worth trying on a config change, not a check to lean on

We were using the following setup:

  • codex CLI version: 0.146.0

  • OS: macOS

  • The mcp list and doctor commands were run in a scratch repo in /tmp with a .mcp.json file in it

  • We checked the help output to find info on override and strict-config flags

  • No session was launched, no charges were applied

Copilot: the best rendered view of the six, and it isn't only yours

The most robust view of these 6 tools is provided by Copilot, which is actually not even limited to your setup. So if you do just one branch comparison — do that one. For example, you can run: copilot skill list, and it will list skills from your projects together with their names and descriptions, for instance:

TEXT
$ copilot skill list
Project skills:
  db-notes - The primary is read-write, the replica is read-only.
  release-check - Use when cutting a release - checks the changelog, tags and version bump are consistent.

Important thing is that it shows you the resolved description of the skill — what the model sees in order to decide whether it's relevant or not — not what is actually in the file. If you look at the description of our db-notes skill, you'll notice that it's actually misspelled (descriptoin:) but Copilot doesn't complain about it and just uses the body of the file instead. It works. Exiting with code 0, no warnings.

Or have a look:

TEXT
$ copilot mcp list
Workspace servers:
  notes (local)

This lists servers available in the current workspace, here the one we configured, called notes. It lists it without starting it — we made sure of that by rigging the server to leave a file behind if anything ever ran it, and no file appeared.

The part that surprises teams.

What's more, Copilot has its own nook for skills and servers definitions, so you might want to have a look there too:

  • For project skills: .github/skills/, .agents/skills/ or .claude/skills/

  • For personal skills: ~/.copilot/skills/ or ~/.agents/skills/

That means that if you were to add a skill in your PR "for Claude Code", it would show up for the rest of the team using Copilot. The way to find out about that is to run the skill list command.

The same applies to servers, your config is in ~/.copilot/mcp-config.json and the workspace config is in .mcp.json or .github/mcp.json

Permissions aren't in the repo.

When it comes to permissions, there's no allowlist you can look into as those are set using flags and per-user config:

  • --allow-tool

  • --deny-tool

  • allowedUrls

  • deniedUrls

But what you can do is examining the patterns in places where these flags are used. Including CI. For example, shell(git:*) allows the shell tool for any git command — "Wildcard matching is performed on the stem of the command, so shell(git:*) will match git push but not gitea".

You can also use relative paths in these expressions, and they match on the trailing part of a path — "A relative path matches by trailing path components, so write(.env) matches a file named .env in any directory, not only the one in the current working directory".

The last thing to keep in mind is that "Denial rules always take precedence over allow rules, even --allow-all-tools"

What we ran.

If you want to experiment with it, here's the setup we used:

  • GitHub Copilot CLI 1.0.78 on macOS

  • A disposable repository in /tmp with three project skills and .mcp.json file

  • Running copilot skill list twice — the second time after modifying the body of one of the skills to make sure it falls back to using the body — and copilot mcp list once

The quotes are from this version's own help — copilot help permissions for the patterns, copilot skill --help and copilot mcp --help for the discovery paths, copilot help config for the URL lists.

Cursor: the frontmatter decides whether the rule exists at all

In Cursor the metadata defines if a rule is even being loaded, there are three keys — description, globs, alwaysApply — that decide it. So when you create a file in .cursor/rules/ you need to make sure they're set properly; otherwise, the file can be perfect, but not actually utilised.

Let's examine every possible combination of these keys, the docs put them in one table: The most bulletproof setup is having alwaysApply set to true, in this case the rule is "Always included. Globs and description are ignored" (so the description doesn't serve any purpose and the token cost is being incurred on every session). If we have alwaysApply set to false with no description and no globs it's "Included only when you @-mention the rule in chat", which is not a very useful setup for teams. So whenever you see such a thing in a PR, it's a red flag. It has both the rule body and the rule signature but does nothing.

Another less harmful thing is when the file has an incorrect extension. The docs state that a regular .md file placed in .cursor/rules/ is being ignored by the rules system as it doesn't have frontmatter with description, globs and alwaysApply keys — so it's just a regular md document if you were to put .md file there instead of .mdc.

For MCP servers, the CLI lists paths it looks for. It's good to check this if you're wondering if changes you've made get being reflected in the agent:

TEXT
$ agent mcp list
No MCP servers configured (expected in .cursor/mcp.json or ~/.cursor/mcp.json)

Note the path — the .mcp.json in the root of a project is being used by both Claude Code and Copilot, but not by Cursor. So if you see a PR claiming it adds a new server for everyone make sure to check if it's included in every path your team's tools look into.

Approval is the real gate. Another thing is that it's not merge that does the trick. The --approve-mcps flag (which approves all the MCP servers automatically) is listed in the help output, implying that for every person approval is being done by default rather than at the merge. Like we were saying through the whole lesson, the last line of defence is the first colleague who approves the server, not the reviewer.

The gotcha. And the last thing: it's good to keep in mind, that while you can list the servers using agent mcp list command also in an untrusted temporary directory without prompt — actually running the agent there will be different. The help output lists the --trust flag (which trusts the current workspace without a prompt) so it seems like the tool asks if it can do this for new directories. Therefore, reading the branch doesn't cost much. Running the agent in an unread branch means you're basically giving the trust to the code you haven't seen.

And the last thing are hooks — another thing that can be put in a project. It's .cursor/hooks.json file which is being used for command hooks (which are shell scripts that are being fed JSON on standard input and return JSON on standard output). The same applies here, check the script itself, not its entry in the file.

Antigravity: list what it imported, because that's the part that's real

The Antigravity CLI is a tool offering two "inventory" commands and one "validation" command to the reviewer:

  • The first one lists all the agents defined in the project

  • The second one lists all the imported plugins

  • The third one runs validations against the current directory

The following is what we see after running these commands in our throwaway test repo under /tmp:

TEXT
$ agy agents
Available agents:

$ agy plugin list
No imported plugins.

We see it's empty as expected, they are just displaying what is in the project (which is nothing, as we haven't created any agent or imported any plugin yet), not what is on the branch. The subagent defined in a pull request appears only once it lands on a path that the CLI can access.

TEXT
$ agy plugin validate .
Error: missing plugin.json: stat plugin.json: no such file or directory

It's also worth to be mentioned the "validation" command doesn't do validation in the strict sense of the word, it's not like it runs some tests etc. What it actually does is validate a plugin directory — it wants a "plugin.json" — and without one it exits with code 1, which at least makes it scriptable. So it could be useful for reviewing a plugin somebody is adding, but not for a settings or skills change.

It's also good to know there's no "mcp" subcommand: Antigravity CLI v1.1.10 has these commands: "agent", "agents", "changelog", "help", "install", "models", "plugin", "plugins", "update". There's no "mcp" one. In contrast to the other harnesses, where there's a command for listing configured servers against a branch, there's no such thing in Antigravity. This means that for a review you look at the config file itself, not a generated view which you can compare with what's on the branch. So the only place where you need to actually read the diff is here, so be careful.

Lastly, beware of this "trap" — the Antigravity CLI's "plugin import" command is for importing plugins from a Gemini or Claude setup. That means that another person can import their plugins, but there will be no changes visible in the diff, so you can be wondering why your teammate's agent works differently on the same branch, whilst the diff shows nothing.

Kimi Code: the validator that takes a path

Other tools from the course check the host machine; Kimi's one accepts a path pointing to a configuration file which allows it to point to the file in a PR. The subcommand doctor for configs takes a path as an argument.

BASH
kimi doctor config ./config.toml

It works really well in two aspects. It can point to the line and place the caret under when it encounters a parse error:

TEXT
Kimi doctor found 1 issue.

ERROR config.toml  /tmp/broken-kimi.toml
  Invalid TOML in /tmp/broken-kimi.toml: Invalid TOML document: incomplete key-value: cannot find end of key

  1:  default_model = "kimi-k2"
  2:  [providers
       ^
  3:  name = "x"

And not only that, it validates based on the schema — we've tested it with a permission rule in which we've misspelt the verdict field and it told us both what was the field and what were the valid values:

TEXT
  Validation issues:
    permission.rules[0].decision: Invalid option: expected one of "allow"|"deny"|"ask"

In both of these cases it exits with code 1 which can be used in CI out-of-the-box.

The blind spot, and it's the important half. We've handed it a file with an invented top-level key and an entire invented section. If you run it like this:

TEXT
$ kimi doctor config /tmp/kimi-unknown.toml
Kimi doctor
OK config.toml  /tmp/kimi-unknown.toml
All checked config files are valid.

It will tell you it's okay. And exit with code 0. That means there's no problems that it knows about — it still can't tell you that a key is misspelled, it can only check the ones it recognises — which isn't as strong as saying that the config is in line with what the PR describes.

Just keep in mind that if you run it without arguments, by default it will point to ~/.kimi-code/config.toml and ~/.kimi-code/tui.toml files from your machine (that's where the reviewer keeps their local files), not those from the branch. Make sure to check the paths that it prints before trusting the 0 exit code.

Skills. The flag --skills-dir is defined in the help as loading skills from a given directory instead of auto-discovered user and project directories. That means that every skill you add to your repo will be loaded automatically, while using this flag you can focus a review on a single dir at a time.

What we ran. Kimi Code CLI 0.31.1, native binary available in ~/.kimi-code/bin/kimi (not the older Python-based kimi-cli package), on macOS. These are the commands we've used: kimi doctor, and kimi doctor config against three hand-written TOML files (broken syntax, a schema-invalid permission rule, unknown keys). And these are the exit codes we've observed.

What good looks like

So, to summarise — a proper config review should be no different than any other. Small diff, separate branch, the author pasting the resolved listing, defined path ownership in CODEOWNERS — and no process document needed. The only bad habit worth avoiding is approving a file because it's similar to another one — which is what a misspelled key looks like.

THE FILEdocs/agent-config-review.md
MARKDOWN
# Reviewing a change to our agent configuration

Config in this repo changes how everyone's agent behaves, and no test we didn't write ourselves
checks it. If a pull request touches any path below, this is the review.

## Paths that trigger it

    AGENTS.md, CLAUDE.md                 instructions loaded into every session
    .mcp.json, .github/mcp.json          MCP servers, root level
    .cursor/mcp.json                     MCP servers, Cursor
    .claude/, .cursor/, .agents/         settings, skills, subagents, hooks, rules
    .github/skills/                      skills (read by more tools than you'd think)

## Ask the author for one thing

The resolved before/after — same prompt, same repo state, once with the change and once without.
There's no test here, so the run is the evidence.

## Then check, in this order

- [ ] **Is it its own PR?** Config riding along with a bug fix gets reviewed as a bug fix.
- [ ] **Every key spelled right?** A key nothing recognises was silently ignored by every tool we
      pointed one at. Wrong shape gets caught; wrong spelling doesn't. Stop on any key you can't name.
- [ ] **Rendered, not read.** Check out the branch and run the listing:
      `copilot skill list` · `claude mcp list` · `claude doctor` · `codex mcp list` ·
      `agent mcp list` · `agy plugin list` · `kimi doctor config <file>`
      Descriptions and rules are resolved values — read what the tool prints, not what the file says.
- [ ] **Permission rules read as written.** Patterns are exact, and both directions bite: too narrow
      matches nothing, too wide approves something nobody meant to approve.
- [ ] **New MCP server?** Who wrote it, is the version pinned, what credentials does it run with, and
      what does it add to every session's context. It executes on each of our machines.
- [ ] **New hook?** The script is in the diff, or the diff isn't reviewable. Hooks run shell commands
      with your full user permissions, unprompted.
- [ ] **Instructions file:** fact or preference, still true in a month, and what it displaces.
- [ ] **Scoped right?** Team-wide behaviour goes in the committed file. Personal preferences stay in
      the gitignored local one, and we don't review those.

## Owners

Add the paths above to `.github/CODEOWNERS` so this happens without anyone remembering to care.
j / k to move between lessons