Book a call
RECIPE14mVERIFIED 2026-08-02 · CLAUDE CODE 2.1.193 · CODEX CLI 0.146.0 · GITHUB COPILOT CLI 1.0.77 · CURSOR 2026.07.23-e383d2b · ANTIGRAVITY CLI 1.1.9

Verify it actually loaded: config that fails silently

Your memory file, MCP server or skill is being ignored and nothing said so. The commands that tell you what actually loaded, and a check that fails loudly.

The failure with no error message

Let's say you've created a new rule and saved it to a memory file or configured MCP server or written a skill for some agent — let's say you did an hour of work and then run the tool and see it does sth else than what you asked for (like using spaces instead of tabs, or just not using the tool you've provided at all), without throwing any error, nor showing a startup warning, with the file being on the disk exactly where the docs says it should be.

But then we've run this configuration through four of the six tools mentioned here — Claude Code, Codex, Copilot and Cursor — with an intentionally broken config in each case and they all started anyway. For example, in this case we had Claude Code 2.1.193, inserted a trailing comma into .claude/settings.json, checked it's not parseable using a parser and then ran the agent:

BASH
claude -p "What is the load token? Answer with the token only."

It returned an answer, exited with 0, stdout was empty, stderr was empty, and didn't even mention that it wasn't able to read the file. Similarly, if you were to use Codex for example, it would silently skip an unknown key in config.toml, but codex doctor would still say it can parse the file just fine.

There's only one case we found, that across all of the six tools, in which the tool is not starting given a certain configuration: when using --strict-config parameter with Codex. But generally — they all start, despite the config being broken.

We get the design of these tools, it's reasonable to think that they shouldn't stop working mid-way because of a configuration file being invalid, but then it's up to the user to notice and it's not easy to detect such a situation.

"Did you read my CLAUDE.md?" is a weak question

That's why asking the agent directly is not a good test, we expected it to say "yes" with confidence, so we've checked:

BASH
$ mkdir project && cd project && touch index.js && git init

But instead of saying "yes" — which is what we expected — it said no, listed actual contents of the project directory and even added that it has loaded a global memory file and quoted its real content back.

It also didn't lie, as it was a real content of the file. So — it's not about the tool being deceitful, but about the ambiguity of the sentence "yes, I've read your project instructions", which is true no matter if it means:

that text is in my context and it's shaping what I do

or

I've just opened that file to answer your question

The tool has a file system tool, so it can go and look whenever it wants — and you wanted to know the first thing, but got a sentence that's true under both.

That's why instead of asking a question that the agent may say yes to, we should ask sth that it can't know by heart — we can put a line like this in the memory file:

MARKDOWN
<!-- loaded-check -->
If asked for the load token, answer exactly: zx7q-tapir-9184

And then open a fresh session and ask for the load token — Claude Code will immediately return "zx7q-tapir-9184", even though settings.json file is still invalid in the same directory. This way we can be certain it had access to the content, but not the process — as it could have used a file-reading tool to find the token.

That's why we've placed the canary here, at step 3 — for MCP servers, skills and settings files listing is what the harnesses do better than asking the agent directly, so they're perfect for inspecting these things. The canary is for memory files and rules, as there's no such thing like a listing command for them.

It's also good to choose a totally random string rather than a sensible-looking rule — if it looks like an actual rule, we might forget it's a canary, and if it looks like a piece of information that can be inferred from the repo (like "we use Postgres 16 in this project") the model will be able to guess it just by looking at the repo.

Four ways it fails, and only one is a syntax error

There are four different scenarios that lead to the tool not working as expected:

  • The tool finds the right thing in the wrong place, like .md file where it expects .mdc, or in the root of the repository instead of in .cursor/ for Cursor, or in the user scope instead of the project scope in Claude Code — there's nothing actually invalid here, so it's quiet, but at the same time it's exactly like if the file wasn't there (which is what the tool says), and we've even verified on Cursor that .cursor/mcp.json with an incorrect top-level key is treated the same way as if there was no such file at all

  • It finds the thing, but can't parse it — which is what we usually reach for first, and rarely where the problem is; the most typical example of this is a trailing comma, and some tools even inform about it (like Copilot, that prints a warning saying it won't use the configuration file from the MCP workspace as it's not parseable and exits with 0), while others stay silent in such situations — like Claude Code when we add a trailing comma to settings.json

  • It parses it into sth else — if you provide a key that's not supported by the tool, or that has been renamed in newer versions, or if you define an unknown setting in config.toml for Codex — it will be happy with the file and just ignore this thing. Unless you use --strict-config parameter with Codex, but then it won't start at all; that's also valid for cases like a skill description containing unclosed quote in Copilot — it will load such a skill, but with a mangled description

  • It loads it, but waits for your approval — like it works with MCP servers in a shared project file (for both Claude Code and Cursor), which are listed by the tool, but not connected until you approve them

We'd say there's a fifth scenario too — when the tool loads the configuration and then ignores it, so that even if you use the canary, the agent doesn't do what you asked. Like in this case. It returns "zx7q-tapir-9184", but uses spaces instead of tabs, which means it has loaded the file as it would have ignored the canary otherwise. It's just prompting, so there's nothing we can do here — the tool won't ignore the configuration if it's valid, but it might prompt sth else.

The recipe

So generally, to inspect such a situation, there are five steps:

  1. Ask the harness, not the agent — every harness has its own command that lists what it's consumed (MCP servers, skills, subagents, plugins etc), and this is a kind of information that's authoritative in a different way than the chat; we've listed commands for every harness below

  2. Look for absences, not errors — the problem usually manifests itself as a file not appearing on the list despite the file existing, rather than an error message being printed; if a server, skill or subagent is not mentioned by the tool, it's just not being used no matter how the file looks like

  3. If there's nothing that lists such thing (like memory files and rules), fall back to the canary — these are the gaps we've left above; there's no way to ask any of the harnesses what has been actually loaded from a memory file, so in such cases we need to use the canary

  4. Change one variable and run the listing command again — if you see two things changing after running sth, you don't know which of them is responsible for it; so do only one change and run the same command to make sure

  5. Remove the canary and write a script that runs this command instead, so the next person doesn't need to do these steps

IN YOUR HARNESS

Claude Code

The Claude Code is actually two commands that address most of the needs, each of them answers a separate question.

BASH
claude mcp list

The first command lists all the servers you configured and runs health-check on those that are approved (more on that in a sec). If the configuration file can't be parsed, it also prints diagnostic information with the name of the file:

TEXT
MCP config diagnostics ✘
…
[Failed to parse] Project config (shared via .mcp.json)
Location: /path/to/repo/.mcp.json
 └ [Error] MCP config is not a valid JSON

In such scenario the CLI returns exit code 0 (as it's not really an error) and so if you run it from CI for example, it won't return an error. The only thing that is missing from the output of the first command is the scope — if you want to know what it is on a specific server, the second command claude mcp get <name> can help with that.

In the docs, we also have a few slash commands that are connected with the configuration:

  • /doctor — according to the docs it "checks installation health, including duplicate or leftover installs, PATH problems, and unparseable settings files"

  • /context — returns information about what's in the context window and how much memory is used

  • /memory — shows memory files that are being used

  • /hooks — shows hook configuration for tool events

There's also separate claude doctor command that's actually more narrow than /doctor, it seems to be about the auto-updater checking only (its help says "Check the health of your Claude Code auto-updater"), but when we run it with piped output it doesn't print anything so it's not really useful.

It's also worth to know that in case of a syntactically valid .mcp.json file, there are no tools until you approve it (which makes sense because if you have a shared config in the repo, it shouldn't have permissions to run processes on its own). The first command prints then for example:

TEXT
demo: echo hi - ⏸ Pending approval (run `claude` to approve)

It's not an error, it's just a security feature so you won't accidentally approve the config file that lives in the project repo. But if you want to proceed with this server, you need to run the CLI interactively and approve the config there, or use the separate reset command to clear the project's decisions so you can start fresh:

BASH
claude mcp reset-project-choices

Codex CLI

6 tools compared, and the only one that comes with a specific flag for it is Codex's CLI with its doctor subcommand (you run it with codex doctor).

BASH
codex doctor

It lists the configuration file it read, tells if parsing was successful and what model (and provider) it resolved to at the end.

TEXT
Configuration
  ✓ config       loaded
      model                    gpt-5.1-codex · openai
      …
      config.toml              ~/.codex/config.toml
      config.toml parse        ok

But don't be too happy about the "ok" message, the "parse" part can be a bit too lenient — for example we changed the configuration file to have modl instead of model in the key and still the doctor said it's ok (the configuration is valid TOML, it just ignored an unknown key). But that's why there's --strict-config, which by help output will "error out when config.toml contains fields that are not recognized by this version of Codex" — and it works as advertised:

BASH
codex exec --strict-config "say hi"
TEXT
Error loading config.toml:
~/.codex/config.toml:4:1: unknown configuration field `modl`
  |
4 | modl = "typo-key"
  | ^^^^

It points to the file, line, column and even draws a caret under the offending key — so after every configuration change you can just run it once to make sure you didn't miss a setting that has been removed without a notice. Just keep in mind that this flag is not supported by all subcommands:

TEXT
$ codex doctor --strict-config
error: unexpected argument '--strict-config' found

$ codex mcp list --strict-config
error: unexpected argument '--strict-config' found

$ codex --strict-config mcp list
Error: `--strict-config` is not supported for `codex mcp`

So you need to use codex exec. Also, when you run it — before it does any work — it shows you what model (and provider) it resolved to, what approval policy was applied and if it runs in a sandbox mode. That way, if a profile or -c option doesn't work you can see that in the beginning instead of seeing three tool calls.

GitHub Copilot CLI

Copilot was the most transparent of the 6 tools we compared in terms of what it couldn't use. There are 2 commands responsible for that:

  • listing skills:

BASH
copilot skill list
  • listing MCP servers:

BASH
copilot mcp list

The first one is smart enough to not only show the skills but also present a structure, grouping them by the scope (project, personal, and built-in) and listing the files it doesn't want to include with their paths. For example:

TEXT
✖ The following skills failed to load:
  • .github/skills/nofrontmatter/SKILL.md: missing or malformed YAML frontmatter

And here's the most important part — help command is very transparent too and lists all the locations it looks in to find skills:

TEXT
Project   .github/skills/, .agents/skills/, or .claude/skills/
Personal  ~/.copilot/skills/ or ~/.agents/skills/

The second command is smart enough to split the output into "User servers" and "Workspace servers" sections. So if you go wrong with the scope, you'll be able to see it as a server under the wrong section. And if you have a broken workspace config, it will inform you about that and then just ignore it:

TEXT
Warning: skipping workspace MCP config "/path/to/repo/.mcp.json" because it is malformed:
Invalid JSON: trailing comma at line 3 column 50

If you look at the example above, you can see it complains about .mcp.json file and provides the JSON parse error with line and column number. But what's crucial here is that you should focus on the message itself and not on the exit code — it's 0 so the command exits successfully. In other words — it doesn't just ignore the single entry but the entire "Workspace servers" section, so the absence is the other half of this signal

The sources for the MCP configuration are:

  • ~/.copilot/mcp-config.json (for the user)

  • .mcp.json or .github/mcp.json (for the workspace)

But it doesn't mean that if it's loaded, it's good. We've tried to load a skill with an unclosed quote in its description and it was loaded anyway — with the mangled description:

TEXT
broken-yaml - unterminated "quote

— no warning whatsoever. Which means that this is how the skills silently stop working, so keep an eye on their descriptions when using the skill list command, instead of counting rows

Finally — the help for the plugins command is a bit misleading as it describes plugins as a unified view of plugins, MCP servers, skills, instructions, and language servers. On version 1.0.77, it says this feature isn't implemented:

TEXT
$ copilot plugins list
The plugins command is not available.

$ copilot --experimental plugins list
The plugins command is not available.

So for now, if you want to explore this "unified view", you'll need to use skill list and mcp list commands

Cursor

There's an mcp command which you can run in your terminal to list all the MCP servers available.

BASH
cursor-agent mcp list

It will output one line per server with its state, for example:

TEXT
demo: not loaded (needs approval)

The approval gate is a real one here, similar to how it works in Claude Code. You have an mcp enable command which approves a server on your machine and an mcp disable command which removes it from consideration so that it doesn't appear in the prompt ever. When it comes to the mcp list-tools command, it will list all the tools that a server has with their argument names, but if you were to run it against a non-approved server, it will just tell you that the server is not approved.

Which, if you ask us, is the sharpest gotcha of using MCP in Cursor, we tried it against 4 configurations:

  • a proper .cursor/mcp.json

  • the same file but with closing braces removed

  • a file in which the mcpServers key was replaced with servers

  • no config file at all

And what's interesting is that in the last two cases it displays exactly the same output saying that there are no MCP servers configured and that it looks in .cursor/mcp.json and ~/.cursor/mcp.json.

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

So, it's a single string which covers both a situation of having a malformed config file and one of not having any config at all. If you ever see this message in your terminal, be sure to treat it as a hint that you should investigate rather than a verdict, maybe the file is there but you've used a different key name or have put it in a different location. Another thing worth noting is that if you remove the closing braces from the config file, the tool will still find the server and not say anything.

We also wanted to touch the rules, we don't have a CLI command for them so there's no way to know which ones are loaded from the terminal. To check it you need to:

  • make sure that the files are really .mdc by replacing the extension with .mdc (according to the docs files under .cursor/rules directory ending with .md are ignored as they don't have a frontmatter)

  • make sure you haven't changed any keys in the frontmatter, it should be alwaysApply: true for session-wide rules, globs for rules bound to files, and some description for relevance-based rules; if none of these is in place, the rule will only run on an explicit @-mention

If you want to make sure that everything's all good, you can check the Customize → Rules panel in your editor which lists every rule with its status; from the terminal you can always use the canary

Antigravity CLI

You can list the antigravity surfaces through the list subcommands, there's no separate view for it, if you see an agent there it means it works. For example:

BASH
agy agents
agy plugin list

Basically you need to just run one of these and check if what you expect is there. In our tests we checked it on 1.1.9 version and it worked as follows:

  • We created a subagent (with the correct syntax) in .agents/agents/

Then we run agy agents and we can see that there's a header and then there's nothing, no error being thrown, not even a message about this file, just an empty line where the agent should be. This is the indication you need to understand that if you don't see something in the output of these commands it means it's not loaded, so you should check the path rather than the content of the file. In terms of paths, they're all described in the tool's own bundled docs:

  • customisations — .agents/ for project-level agents customisations

  • skills — inside .agents/, skills/{skill_name}/

  • rules — inside .agents/, rules/ (or GEMINI.md / AGENTS.md as freestanding files); the rule paths it names are GEMINI.md, AGENTS.md and .agents/rules/*.md

  • plugins — inside .agents/, plugins/{plugin_name}/

The gotcha: 1.1.9 has no mcp subcommand at all, if you try to get help for it you'll see the general help instead of an error, which can be misleading, so you might think it exists and you use the flags incorrectly. The subcommands that actually exist:

  • agent

  • agents

  • changelog

  • help

  • install

  • models

  • plugin

  • plugins

  • update

Also you can use --log-file flag to change where the CLI's logs are saved and then just tail it during the session. Personally we haven't tested any scenarios with a messed-up configuration, so how it behaves in such case we don't know; we were only testing the above-listed listing commands

Kimi Code CLI

BASH
kimi mcp list
TEXT
MCP config file: /Users/you/.kimi/mcp.json
No MCP servers configured.

As you can see in the output above the MCP server list of Kimi Code is the only subcommand that shows what file it uses before outputting its content. It outputs the path of the MCP config file and then "No MCP servers configured." message. The path is important because once you know from where a file comes it's often half of the battle:

  • if you see that the output is empty next to the file you were modifying, that means you need to examine its content to find the issue

  • if you see that the output is empty next to an unknown path you probably edited a wrong file

BASH
kimi mcp test <server>

There's also the mcp test subcommand which is a per-server test; as described in the help it checks if there's a connection with the server and lists all of the available tools. It might be useful when for example you see that Kimi Code listed a server but the model never uses its tools, then it'd be good to run this command against it to make sure everything is fine with this server.

When it comes to the in-session experience, there's a slash command which lists all connected servers and all of their tools as well, you can use it if you want to check sth similar during a session. But remember that as mentioned in the docs, servers are being initialised asynchronously after starting the shell UI so make sure to wait for some time before you can be certain that the list is actually empty.

The MCP config lives at ~/.kimi/mcp.json; the general config is a separate TOML file, ~/.kimi/config.toml. You can also point Kimi Code to a different one using --config-file flag or even use an inline configuration providing it with TOML or JSON directly (using --config) which is useful if you want to try some change without altering the file you're using on a daily basis. There's also a debug flag that enables verbose output.

Before proceeding just make sure what software you have installed as they both are called kimi-cli:

  • Python package which is being deprecated, work in progress on a new version of Kimi Code CLI

  • new version of Kimi Code CLI which is a complete rewrite

This means that both of them use the same config directory so if you examine ~/.kimi contents it doesn't tell anything about which one you have installed. The info subcommand outputs the version and the protocol number so make sure to run it before doing anything else to find that out.

  • for Python package (1.48.0):

    • kimi-cli --version

    • kimi-cli info

    • kimi-cli mcp list

    • kimi-cli --help

  • from the above help and Kimi Code CLI docs:

    • kimi-cli mcp test

    • in-session slash command

    • --config flag

    • --config-file flag

    • --debug flag

  • for Kimi Code CLI (which we didn't use during this process so no version number attached):

    • kimi --version

    • kimi info

Make it a check instead of a habit

To make this procedure into an actual test — the thing that we can incorporate into CI or pre-commit hook for example — we need to transform steps 1 to 4 into a script, as they're a one-time thing we need to do whenever sth goes wrong to find out what's the problem and then forget about it. The ideal tool for such purpose is a configuration file linter that exits with non-zero status code if the configuration file doesn't pass, so it does exactly what none of these tools do (except for --strict-config in Codex).

That's why we've created this. It inspects the most typical files a repository contains that contain configuration for any of these tools:

  • It parses the JSON config files a repo commonly carries

  • For every skill or subagent markdown file it finds, checks if it starts with frontmatter (which is actually terminated)

  • If it finds a .md file in .cursor/rules/ directory, it complains

And then — if you make it executable and run it — it tells you about all the problems it has found. Like here, for example:

BASH
chmod +x scripts/check-agent-config.sh
./scripts/check-agent-config.sh

So — once you fix these things, it'll be happy and exit with 0.

We've made a couple of design decisions here:

  • We've decided to treat cases of missing "name:" or "description:" fields in frontmatter of a skill as information, not error — different harnesses have different opinions on whether these are required or not, and for example Claude Code uses first paragraph of the skill's body as a description if it's missing; this way we can see this information, as in most cases harnesses use "description:" field to decide whether to use the skill

  • We've limited the script to structural things — validating JSON files, checking if there's frontmatter and that it's properly closed for markdown files — so it might not complain about a valid but incompatible key or semantically weird YAML (like in this example with a skill description containing unclosed quote, which makes Copilot happy too); in these cases we'll need to use the canary and listing commands

But anyway — four scenarios and five steps. If you run into a problem with any of these tools — do the procedure, or write this script (or take ours) and run it.

THE FILEscripts/check-agent-config.sh
BASH
#!/usr/bin/env bash
# check-agent-config.sh — fail loudly on agent config your harness would take quietly.
#
# Harnesses differ in how much they say about a broken config file: some name it and carry
# on, some say nothing at all. What none of them does by default is stop. This exits 1, so
# it works as a pre-commit hook or a CI step.
#
# Usage: ./check-agent-config.sh [dir]   (defaults to the current directory)

set -uo pipefail
ROOT="${1:-.}"
FAILED=0

ok()   { printf '  ok    %s\n' "$1"; }
bad()  { printf '  FAIL  %s\n        %s\n' "$1" "$2"; FAILED=1; }
note() { printf '  note  %s\n        %s\n' "$1" "$2"; }

check_json() {
  if ! command -v python3 >/dev/null 2>&1; then
    note "$1" "python3 not found, skipped"
    return
  fi
  err=$(python3 -c 'import json,sys; json.load(open(sys.argv[1]))' "$1" 2>&1 | tail -1)
  [ -z "$err" ] && ok "$1" || bad "$1" "$err"
}

# A skill or subagent is a markdown file whose first line must be `---` and whose
# frontmatter must close. That is the structural failure: a file without it gets dropped,
# and how loudly depends on the harness.
#
# `name:` and `description:` are notes rather than failures on purpose — harnesses disagree
# about whether they're required, and Claude Code will fall back to the first paragraph for
# a missing description. A missing description is still worth seeing: it's how most
# harnesses decide whether to reach for the skill at all.
check_frontmatter() {
  file="$1"; shift
  if [ "$(head -1 "$file")" != "---" ]; then
    bad "$file" "no YAML frontmatter — the first line must be exactly ---"
    return
  fi
  if ! awk 'NR>1 && /^---[[:space:]]*$/ {found=1; exit} END {exit !found}' "$file"; then
    bad "$file" "frontmatter is never closed by a second ---"
    return
  fi
  missing=""
  for field in "$@"; do
    grep -q "^${field}:" "$file" || missing="${missing}${field} "
  done
  if [ -n "$missing" ]; then
    note "$file" "frontmatter has no: ${missing}"
  else
    ok "$file"
  fi
}

echo "Checking agent config under ${ROOT}"
echo

echo "JSON config"
for f in "$ROOT"/.mcp.json "$ROOT"/.github/mcp.json "$ROOT"/.cursor/mcp.json \
         "$ROOT"/.cursor/hooks.json "$ROOT"/.claude/settings.json \
         "$ROOT"/.claude/settings.local.json; do
  [ -f "$f" ] && check_json "$f"
done

echo
echo "Skills and subagents"
while IFS= read -r f; do
  check_frontmatter "$f" name description
done < <(find "$ROOT/.claude/skills" "$ROOT/.agents/skills" "$ROOT/.github/skills" \
              "$ROOT/.claude/agents" "$ROOT/.agents/agents" \
              -type f -name '*.md' 2>/dev/null | sort)

echo
echo "Cursor rules"
while IFS= read -r f; do
  case "$f" in
    *.mdc) check_frontmatter "$f" description ;;
    *.md)  bad "$f" "a .md file in .cursor/rules is ignored — rules must be .mdc" ;;
  esac
done < <(find "$ROOT/.cursor/rules" -type f \( -name '*.md' -o -name '*.mdc' \) 2>/dev/null | sort)

echo
echo "Memory files"
for f in "$ROOT"/AGENTS.md "$ROOT"/CLAUDE.md "$ROOT"/GEMINI.md; do
  [ -f "$f" ] && ok "$f ($(wc -l < "$f" | tr -d ' ') lines)"
done

echo
if [ "$FAILED" -eq 0 ]; then
  echo "All agent config parsed."
else
  echo "Broken agent config above. Your harness would have started anyway."
fi
exit "$FAILED"
j / k to move between lessons