agentdoctor

Rule reference

Every rule, with the reasoning behind it - 72 in total. agentdoctor --explain <rule-id> prints any of these from the CLI, and --list-rules prints the catalogue.

Correctness

Config the harness is silently ignoring. These are the findings where you believe something is configured and it is not.

correctness/invalid-json

Config file is not valid JSON  ·  error

The harness cannot read this file, so every setting in it is silently ignored — including any permission rules you thought were protecting you.

correctness/unknown-settings-key

Unrecognised settings key  ·  warning

Unknown keys are ignored without warning, so a typo means the setting never applies.

correctness/unknown-permission-key

Unrecognised key under permissions  ·  warning

Valid keys are: allow, deny, ask, defaultMode, additionalDirectories, disableBypassPermissionsMode.

correctness/permissions-wrong-type

Permission bucket is not an array  ·  error

allow, deny and ask must each be an array of rule strings. A string or object here means the rules never load.

correctness/permission-unknown-tool

Permission rule names an unknown tool  ·  warning

Tool names are case-sensitive. A rule naming a tool that does not exist never matches anything, so a deny rule written this way protects nothing.

correctness/permission-non-string

Permission rule is not a string  ·  error

Each entry must be a string like "Bash(npm test:*)".

correctness/duplicate-permission

Duplicate permission rule  ·  info

Harmless, but usually a sign of a merge that went wrong or a rule that was meant to be edited rather than added.

correctness/allow-deny-conflict

Same rule in both allow and deny  ·  warning

Deny wins, so the allow entry is dead config. Remove it so the intent is unambiguous to the next reader.

correctness/unknown-hook-event

Unknown hook event  ·  error

Valid events: PreToolUse, PostToolUse, UserPromptSubmit, Notification, Stop, SubagentStop, PreCompact, SessionStart, SessionEnd. Events are case-sensitive and a misspelled one never fires.

correctness/hook-malformed

Hook entry has the wrong shape  ·  error

Each event maps to an array of { matcher, hooks: [{ type: "command", command: "..." }] }. A near-miss shape is dropped silently.

correctness/hook-matcher-ignored

Matcher set on an event that has no tool  ·  info

Only PreToolUse, PostToolUse, PreCompact use a matcher. Elsewhere it is ignored, which can look like the hook is scoped when it is not.

correctness/hook-matcher-invalid-regex

Hook matcher is not a valid pattern  ·  error

Matchers are treated as regular expressions. An invalid pattern means the hook silently never matches.

correctness/hook-matcher-unknown-tool

Hook matcher names no existing tool  ·  warning

Check the spelling and casing of the tool name. A matcher that matches nothing is a hook that never fires.

correctness/invalid-model

Unrecognised model name  ·  warning

Use an alias (opus, sonnet, haiku) or a full model id. An unknown value falls back to the default without telling you.

correctness/agent-missing-frontmatter

Subagent definition has no frontmatter  ·  error

A subagent file needs a --- delimited frontmatter block with at least name and description. Without it the agent is not registered.

correctness/agent-missing-field

Subagent is missing a required field  ·  error

Both name and description are required. The description is what the orchestrating model reads to decide whether to delegate, so an empty one means the agent is never chosen.

correctness/agent-name-mismatch

Subagent name does not match its filename  ·  warning

Keep the frontmatter name and the filename in sync; mismatches make agents hard to find and, depending on the harness version, can shadow each other.

correctness/agent-unknown-tool

Subagent grants a tool that does not exist  ·  warning

Tool names in the tools list are case-sensitive. An unknown entry is dropped, so the agent quietly runs without the capability you meant to give it.

correctness/duplicate-agent-name

Two subagents share a name  ·  error

Names must be unique; the loser is unreachable. Project-scope agents shadow user-scope agents with the same name.

correctness/skill-name-mismatch

Skill name does not match its directory  ·  error

A skill is invoked by its directory name, so a mismatched frontmatter name makes the skill impossible to invoke by the name it advertises.

correctness/skill-missing-field

Skill is missing a required field  ·  error

name and description are both required. The description is the only thing the model sees when deciding whether to load the skill.

correctness/duplicate-skill-name

Two skills share a name  ·  error

Only one wins. Rename one, or move it under a directory-scoped path if the collision is deliberate.

correctness/mcp-server-incomplete

MCP server has no way to start  ·  error

A server needs either "command" (stdio) or "url" (SSE/HTTP). Without one the server fails to connect on every session start.

correctness/mcp-server-toggled-both-ways

MCP server both enabled and disabled  ·  warning

Remove it from one of the two lists so the intended state is obvious.

correctness/statusline-malformed

statusLine is misconfigured  ·  warning

statusLine must be an object with type "command" and a command string.

correctness/env-non-string-value

Environment value is not a string  ·  warning

Environment variables are strings. Numbers and booleans here may be dropped or coerced unpredictably — quote them.

Security

The config surface is an execution surface. These rules find the places where it is wider than intended.

security/unrestricted-bash

Blanket Bash allow rule  ·  error

Replace the wildcard with the specific commands you actually want unattended, e.g. "Bash(npm test:*)" or "Bash(git status)". A blanket allow means any command the model proposes runs without asking you.

security/destructive-allow

Destructive command pre-approved  ·  error

Move this rule to permissions.ask so you still get a prompt, or narrow it to the safe subset of the command.

security/bypass-permissions-default

Permission checks disabled by default  ·  error

Use "default" or "acceptEdits" for day-to-day work and opt into bypass explicitly per session. Committing bypassPermissions applies it to everyone who checks out the repo.

security/hooks-globally-disabled

All hooks disabled  ·  warning

If hooks were disabled to work around one noisy hook, remove that hook instead. disableAllHooks also silences hooks your team relies on for guardrails.

security/hook-remote-code

Hook downloads and executes remote code  ·  error

Vendor the script into the repo and run it from a pinned path. Hooks run automatically with your full user privileges and no confirmation, so whoever controls that URL controls your machine.

security/hook-unpinned-path

Hook command resolves through PATH or cwd  ·  warning

Use an absolute path or "$CLAUDE_PROJECT_DIR/.claude/hooks/name.sh". A bare name resolves via PATH, so a same-named file earlier in PATH — or in a repo you clone — runs instead.

security/hook-dangerous-command

Hook runs a destructive command  ·  warning

Hooks fire automatically with no confirmation step. Anything irreversible belongs in a command you invoke deliberately, not in a hook.

security/secret-in-config

Credential hardcoded in agent config  ·  error

Move the value to a secret manager or an untracked env file and reference it indirectly. Config files are committed, synced and shared far more often than people expect.

security/dangerous-env-var

Loader-influencing environment variable set  ·  warning

Set these per-command instead of session-wide. Anything defined in settings.env applies to every process the agent spawns for the whole session.

security/broad-additional-directory

Filesystem root granted as a working directory  ·  error

List only the specific sibling directories the agent needs. Granting "/" or your home directory hands it every SSH key, browser profile and other project on the machine.

security/unrestricted-egress

Unrestricted network egress pre-approved  ·  warning

Scope WebFetch to the domains you actually need, e.g. "WebFetch(domain:docs.example.com)". An open fetch rule is a one-step path for anything in your context to leave the machine.

security/sensitive-read-allowed

Credential file explicitly readable  ·  error

Remove the rule and add the path to permissions.deny instead. Secrets read into context end up in transcripts, logs and any tool call the model makes next.

security/missing-secret-denies

No deny rules protecting secrets  ·  info

Add a deny list such as ["Read(./.env*)", "Read(/.ssh/)", "Read(**/*.pem)", "Read(**/.aws/credentials)"]. Deny rules are the only guardrail that survives an accepted prompt, since they are checked before anything runs.

security/mcp-unpinned-package

MCP server runs an unpinned remote package  ·  warning

Pin the exact version, e.g. "@scope/server@1.4.2". With "@latest" or no version, every session silently installs whatever was published most recently, including a compromised release.

security/mcp-auto-enable-all

Project MCP servers auto-enabled without review  ·  warning

Leave this off and enable servers explicitly via enabledMcpjsonServers. Otherwise cloning a repo is enough to run its MCP servers on your machine.

security/mcp-plaintext-url-credential

Credential embedded in MCP server URL  ·  error

Move the token into a header sourced from the environment. URLs land in logs, crash reports and shell history.

security/world-writable-config

Agent config writable by other users  ·  error

Run "chmod go-w" on the file. Any user who can write your agent config can add a hook, and hooks execute automatically as you.

security/hook-script-not-executable

Hook script is world-writable or missing  ·  warning

Keep hook scripts inside the repo, owned by you, and not group-writable.

security/apikeyhelper-inline-secret

apiKeyHelper echoes a literal key  ·  error

Point apiKeyHelper at a script that reads from your OS keychain or secret manager, rather than embedding the key in the command.

security/deny-bucket-empty-with-broad-allow

Broad allow list with no deny list  ·  warning

Pair permissive allow rules with explicit denies. Deny is evaluated first and is the only rule class the model cannot talk its way past.

security/bypass-mode-not-locked

Bypass mode not disabled for the project  ·  info

Set permissions.disableBypassPermissionsMode to "disable" in committed project settings to stop anyone opting out of prompts in this repo.

security/invalid-permission-mode

Unknown permission mode  ·  error

Use one of: default, acceptEdits, plan, bypassPermissions. An unrecognised mode is ignored, so you silently fall back to the default.

Cost

Memory files and tool schemas are re-sent on every request, so their size is a recurring charge. These rules quantify it.

cost/memory-file-too-large

Memory file is large enough to cost real money  ·  warning

Move reference material into a skill or a linked file that gets read on demand. Memory files are prepended to every request, so their size multiplies by every turn you take.

cost/total-memory-budget

Combined always-on context is heavy  ·  warning

Aim to keep the always-loaded total under a few thousand tokens. Everything here competes with the actual task for the model attention you are paying for.

cost/duplicated-memory-instructions

The same instruction appears in several memory files  ·  info

Keep each instruction in exactly one file. Duplicates cost tokens twice and, worse, drift apart until they contradict each other.

cost/many-mcp-servers

Many MCP servers enabled at once  ·  warning

Enable servers per project rather than globally. Every connected server contributes its tool schemas to the context window on every request, whether or not you use it.

cost/vague-skill-description

Skill description gives the model nothing to match on  ·  warning

Write descriptions as trigger conditions: "Use when the user asks to X, mentions Y, or is working on Z." The description is the only signal the model has, so a vague one means the skill you wrote is never used.

cost/vague-agent-description

Subagent description will not attract delegation  ·  info

State what the agent is for and when to pick it. Orchestrators route on this string alone.

cost/memory-contains-generated-content

Memory file contains content that belongs in a file, not in context  ·  warning

Reference the file by path instead of pasting it. The agent can read a path in one tool call; pasted content is paid for on every single request forever.

cost/no-cleanup-period

Transcript retention never trimmed  ·  info

Set cleanupPeriodDays to something like 30. Old transcripts are dead weight on disk and, if they contain customer data, a growing liability.

Hygiene

Legal, safe config that will still cause avoidable confusion or leak personal settings between machines.

hygiene/local-settings-not-ignored

Local settings file is not gitignored  ·  error

Add ".claude/settings.local.json" to .gitignore. That file is where personal overrides and machine-specific paths go, and committing it pushes your permissions onto everyone else.

hygiene/empty-config

Config file has no effective content  ·  info

Delete it, or fill it in. An empty file reads as "configured" to the next person who opens the repo.

hygiene/skill-body-empty

Skill has frontmatter but no instructions  ·  warning

The body is what the model actually follows once the skill loads. Frontmatter alone advertises a capability that does nothing.

hygiene/agent-body-empty

Subagent has no system prompt  ·  warning

The body of an agent file is its system prompt. Without one the subagent behaves like a default agent with a narrower toolset.

hygiene/no-project-memory

No project memory file  ·  info

A short CLAUDE.md covering build/test commands and project conventions removes the same handful of questions from every session.

hygiene/absolute-home-path

Committed config contains a machine-specific path  ·  warning

Use $CLAUDE_PROJECT_DIR or a relative path so the config works on every machine. Hardcoded home directories break for every other contributor.

hygiene/settings-scope-conflict

Local settings silently override project settings  ·  info

Not a bug, but worth knowing: this key differs between the committed project config and your local override, so your session behaves differently from your teammates.

hygiene/keybindings-duplicate

Two actions bound to the same key  ·  warning

One of the two bindings will not fire. Pick a different chord for the loser.

Policy

Enforcement of a written standard across more than one repository. These rules activate when an agentdoctor.policy.json is committed and are silent otherwise.

policy/missing-required-deny

Required deny rule is absent  ·  error

Add the rule to committed project settings. It is mandated by your agentdoctor.policy.json.

policy/forbidden-allow

Allow rule forbidden by policy  ·  error

Remove the rule or get the policy amended. Policy exists so this decision is made once, centrally, instead of per repo.

policy/unapproved-mcp-server

MCP server not on the approved list  ·  error

MCP servers run code and see your context. Add the server to allowedMcpServers in policy once it has been reviewed.

policy/required-hook-missing

Mandated guardrail hook is missing  ·  error

Policy requires this hook event to be configured. Copy it from your organisation template.

policy/memory-budget-exceeded

Always-on context exceeds the policy budget  ·  error

Trim the memory files or raise maxMemoryTokens deliberately. A context budget is the only thing that stops CLAUDE.md growing without limit.

policy/forbidden-permission-mode

Permission mode forbidden by policy  ·  error

Change defaultMode to a mode your policy permits.

policy/permission-drift

Local overrides widen the committed permission set  ·  warning

Local settings are invisible in review. If a rule is genuinely needed, put it in project settings so the team sees it; if it is personal, keep it narrow.

policy/file-invalid

Policy file could not be read  ·  error

A policy that fails to parse enforces nothing, which is the most dangerous state for a guardrail to be in.


Suppress any rule for one file with a comment in that file:

agentdoctor-disable <rule-id>