Last updated: v3.4.0
Indexed by ERROR_CODES. When a validator fails, look up the .code value
from its ValidationError here.
Debug flag: set DOTBABEL_DEBUG=1 to route previously-silent git-probe
catches (resolveRepoRootFromGit, getChangedFiles) to stderr tagged
[harness:git:*].
SPEC_JSON_INVALIDdocs/specs/<id>/spec.json is missing or fails to parse.
Fix: node -e "JSON.parse(require('fs').readFileSync('docs/specs/<id>/spec.json','utf8'))" to locate the parse error, or create the file.
SPEC_STATUS_INVALIDstatus is not one of draft | approved | implementing | done.
Fix: pick a valid status. Only approved|implementing|done gate PR coverage.
SPEC_ID_MISMATCHspec.json.id does not equal the directory name.
Fix: rename the directory or update id — they must match.
SPEC_MISSING_REQUIRED_FIELDA required field (title, owners, depends_on_specs, active_prs, …) is missing or wrong type.
Fix: the pointer on the error tells you which field.
SPEC_LINKED_PATH_MISSINGlinked_paths is missing, empty, or contains a non-string entry.
Fix: every entry must be a non-empty string glob or path.
SPEC_ACCEPTANCE_EMPTYacceptance_commands is empty or contains a non-string entry.
Fix: at least one command that CI can run.
SPEC_DEPENDENCY_UNKNOWNdepends_on_specs references an id that does not exist under docs/specs/.
Fix: create the dependency spec or remove the reference.
MANIFEST_ENTRY_MISSINGA .claude/skills-manifest.json entry points at a path that does not exist on disk.
Fix: remove the entry, or restore the file.
MANIFEST_CHECKSUM_MISMATCHA file’s content drifted from the recorded sha256.
Fix: npx dotbabel-validate-skills --update to recompute and accept the new content, or restore the file to its original state.
MANIFEST_ORPHAN_FILEA file under .claude/commands/ or .claude/skills/<name>/SKILL.md is not indexed in the manifest.
Fix: npx dotbabel-validate-skills --update to pick it up (add a manifest entry), or delete the file.
MANIFEST_DEPENDENCY_CYCLEThe dependencies[] graph has a cycle.
Fix: break the cycle. The error .got field shows the path A -> B -> A.
COVERAGE_UNCOVEREDA protected path changed in the PR but no approved|implementing|done spec covers it, and the PR body has no ## No-spec rationale section.
Fix: draft a covering spec (status ≥ approved) or add a rationale to the PR body.
COVERAGE_NO_SPEC_RATIONALEThe PR body has neither a ## Spec ID nor a ## No-spec rationale section, but protected files changed.
Fix: add one of the two sections.
COVERAGE_UNKNOWN_SPEC_IDThe PR body references a Spec ID: that does not exist under docs/specs/.
Fix: check the spec directory, or create the spec first.
DRIFT_TEAM_COUNTAn instruction file (CLAUDE.md, README.md) mentions N team(s) with N ≠ docs/repo-facts.json team_count.
Fix: update either the file prose or repo-facts.json.team_count.
DRIFT_PROTECTED_PATHA docs/repo-facts.json protected_paths entry is either non-string or
absent from a file listed in rule_floor_files.
Fix: add the entry to CLAUDE.md §Protected paths, regenerate generated
instruction files with dotbabel-generate-instructions, or remove it from
the facts file.
DRIFT_INSTRUCTION_FILESinstruction_files is missing/non-array, or rule_floor_files is present but
invalid in docs/repo-facts.json.
Fix: add instruction_files as a non-empty array, e.g.
["CLAUDE.md", "README.md"]. If rule_floor_files is present, make it a
non-empty array of files that must mirror protected-path rules.
DRIFT_INSTRUCTION_FILE_MISSINGAn instruction_files entry points at a path that does not exist.
Fix: create the file or drop the entry.
DRIFT_GENERATED_STALEA generated rule-floor output differs from what
dotbabel-generate-instructions would write.
Fix: run npx dotbabel-generate-instructions and commit the generated
outputs.
.dotbabel.json)CONFIG_UNKNOWN_CLIfan_out names, or cli_excluded is keyed by, a CLI that project-sync
cannot fan out to. Only codex, gemini, and copilot are supported, so a
typo such as co-pilot or github-copilot would otherwise skip that CLI’s
wiring (or exclude nothing) without failing.
Fix: correct the name in .dotbabel.json:fan_out or cli_excluded, or
drop the entry. Add
"$schema": "https://dotbabel.dev/schemas/dotbabel.config.schema.json" to the
file so your editor flags the typo before you run anything.
CONFIG_UNKNOWN_LAYOUTfan_out_layout is neither per-cli nor shared.
Fix: use "per-cli" (the default — .codex/skills/ and .gemini/skills/
are separate trees) or "shared" (both become symlinks to one .cli/skills/).
CONFIG_INVALID_EXCLUSIONcli_excluded is not an object, or one of its values is not a list of
non-empty strings. The error’s pointer names the offending key or index.
Fix: shape it as { "<cli>": ["<command-or-skill-name>", ...] }, using
command basenames without .md and skill directory ids.
QUALITY_CONFIG_INVALIDAn unknown key, an unknown rule id, an out-of-range threshold, or an unsafe
command in the quality block of .dotbabel.json or
${XDG_CONFIG_HOME}/dotbabel/quality.json.
Fix: the error’s pointer names the exact JSON path. argv must be an
array, and its first element must be a PATH name or a ./-relative path —
never an absolute path and never a shell string. The user-scope file cannot set
base_ref, baseline_file, critical_paths, components, or exceptions.
Run dotbabel quality explain to see the merged result and its provenance.
QUALITY_BASE_UNAVAILABLEThe base or head revision is not present in the local clone. A shallow CI
checkout is the usual cause.
Fix: set fetch-depth: 0 in CI, run git fetch origin main, or pass an
existing --base. Resolution order is --base → DOTBABEL_QUALITY_BASE →
quality.base_ref → origin/HEAD → origin/main → main → master. Use
--all when you want a whole-repository run with no base at all.
QUALITY_SCOPE_UNAVAILABLEA Git command needed to work out which files and lines to check failed, or produced more output than the 64 MiB read limit.
Fix: for an overflow, compare against a nearer --base, or use --all,
which resolves scope from the repository file list instead of a diff. Note that
--path filters after the diff runs, so it does not make the diff smaller.
For any other failure the message carries Git’s own last line — run the same
command in the repository to see it in full.
This is not QUALITY_BASE_UNAVAILABLE: the base resolved fine, so deepening the
checkout with fetch-depth: 0 will not help and makes the diff larger. Before
this check existed, these failures produced an empty change set and a passing
verdict, so a run could report pass having measured nothing.
QUALITY_TRUST_REQUIREDA plan needs a project-owned command and the repository is not in the trust
allowlist.
Fix: grant trust for the exact repository path locally, or pass
--allow-project-commands for one CI run — it never persists. baseline --write
needs trust and a clean worktree.
QUALITY_REPORT_INVALIDA report did not match its declared format, or a dotbabel-v1 report is
missing schema_version: 1, metrics[], or findings[].
Fix: validate the file against
../schemas/dotbabel.quality-report.schema.json.
A missing report is not this code — that makes the measurement unavailable.
QUALITY_BASELINE_INVALIDThe baseline failed to load, or --write ran against a dirty worktree.
Fix: commit or stash first, then re-run. Validate a hand-edited baseline
against ../schemas/dotbabel.quality-baseline.schema.json.
QUALITY_EXECUTION_FAILEDAn absolute executable, an executable or cwd outside the repository, or an
invalid --pass-env name.
Fix: use a PATH name or a ./-relative path inside the component root.
--pass-env names must match [A-Za-z_][A-Za-z0-9_]*.
not_configured although Stryker is installedBuilt-in detection plans ./node_modules/.bin/stryker, relative to the
component root. A git worktree has no node_modules of its own — Node and npm
resolve upward into the main checkout — so the binary is absent at that exact
path and the plan is reported as not configured.
Fix: declare the tool yourself, going through npm run so that npm’s
ancestor PATH finds the installed binary. See
Declaring the tool yourself.
The same declaration is the fix when Stryker is configured in a .js or .mjs
file and writes its report somewhere other than the default
reports/mutation/mutation.json: those config forms cannot be read without
executing them, so detection assumes the default path.
EISDIR: illegal operation on a directory, copyfileStryker copies the repository into a sandbox and does not follow a symlinked directory. dotbabel fans skills out as symlinks into each CLI’s config directory, so any of them that Stryker copies kills the run before a single mutant is scored.
Fix: add every fan-out skills directory to ignorePatterns in the Stryker
config. Derive the list from the agent registry rather than typing it out — a
hand-written list falls behind silently when a runtime is added, and the next
run fails on a path nobody changed:
import { skillDirRuntimes, projectSkillsDir } from "./plugins/dotbabel/src/agents.mjs";
ignorePatterns: [
".claude/commands",
".claude/skills",
".cli",
".github/instructions",
...skillDirRuntimes().map((runtime) => projectSkillsDir(runtime)),
],
mutation.changed_score fails with the tool’s console output as the messageThe verdict message should be a score. A wall of the tool’s own output means the
tool exited non-zero, so dotbabel never parsed its report: report parsing is
skipped for every capability except coverage when the command fails.
The usual cause is a break threshold configured on the mutation tool itself.
Fix: remove it and let mutation.changed_score apply the floor. Keep the
tool’s threshold only on a separate config used for direct runs, never on the
one the declared tool loads. See
Declaring the tool yourself.
mutation.changed_score reports not_applicable on a run you expected to scoreThe rule scores only mutants that start on a line the change touched (REL-11), so a diff-scoped run of a change that touches no mutated module has nothing to measure. That is not a failure.
Fix: to measure a module’s whole score, scope by path and pass --all:
dotbabel quality check --profile deep --all --path 'plugins/dotbabel/src/criteria/**'
not_configured, unsupported, and unavailable are measurement states, not
errors. Run dotbabel quality detect to see which applies where.
not_configured most often means two equal-authority candidates were found —
two package.json scripts, two Makefile targets, or both [tool.mypy] and
[tool.pyright] — and dotbabel refuses to guess.
Fix: pin one tool under quality.components[].tools. Note that a rule whose
on_unavailable is error produces exit 2, not exit 1, and that exit 2 is
never a pass.
SCAFFOLD_CONFLICTdotbabel-init refuses to overwrite an already-initialized repo.
Fix: pass --force to overwrite, or remove .claude/skills-manifest.json / docs/specs/ first.
SCAFFOLD_USAGEBad CLI invocation of dotbabel-init (e.g. flag without a value).
Fix: see --help.
validate-settings.sh)SETTINGS_SEC_1A *_KEY / *_TOKEN / *_SECRET field in ~/.claude/settings.json holds
a literal 20+ character value. Fix: replace with ${ENV_VAR} reference.
SETTINGS_SEC_2skipDangerousModePermissionPrompt is set. Fix: remove it.
SETTINGS_SEC_3An MCP server args include @latest. Fix: pin the version.
SETTINGS_SEC_4~/.claude/.credentials.json is not mode 600. Fix: chmod 600 ~/.claude/.credentials.json.
SETTINGS_OPS_1Settings JSON is malformed OR an MCP command / hook target / enabled plugin does not resolve. Fix: read the specific message — it names the unresolved target.
SETTINGS_OPS_2~/.claude/projects/ or ~/.claude/file-history/ exceeded its disk budget.
Fix: the warn message includes a find … -delete command to prune.
ENV_REPO_ROOT_UNKNOWNcreateHarnessContext() could not resolve a repo root.
Fix: pass --repo-root <path> or DOTBABEL_REPO_ROOT=<path>, or run inside a git worktree.
ENV_FACTS_MISSINGdocs/repo-facts.json is missing or unreadable.
Fix: scaffold with dotbabel-init or author the file (see plugins/dotbabel/templates/docs/repo-facts.json).
USAGE_UNKNOWN_FLAGAn unknown flag was passed. Exit 64. Fix: see --help.
USAGE_MISSING_POSITIONALA required positional argument is missing. Exit 64. Fix: see --help.
handoff list shows a session that handoff pull cannot findSymptom: dotbabel handoff list prints sessions, but dotbabel handoff pull <id>
answers no <cli> sessions found under <root> or <cli> session not found for
uuid: <id> for one of them.
Cause (fixed in v3.3.1, issue #329): the resolver walked session roots without
following symlinks. Redirecting CLI state to another volume is common —
~/.codex/sessions -> /mnt/storage/cli-state/codex/sessions — and the walk
returned nothing for every query shape, while list was unaffected because it
reads the directory a different way.
Fix: upgrade to v3.3.1 or later. To confirm a root is redirected:
ls -ld ~/.claude/projects ~/.codex/sessions ~/.copilot/session-state ~/.gemini/tmp
handoff pull reports “no sessions found” and the root is on another volumeExit 2 covers three cases: the root is genuinely empty, no session matched, and
the walk could not complete. A redirected root behind an unmounted volume — a
dead NFS mount, a /mnt path not mounted this boot — reports the same message
as an empty root, because the walk’s own error output is discarded.
Fix: check the root resolves and is readable before reading further into it:
readlink -f ~/.codex/sessions && ls ~/.codex/sessions >/dev/null && echo readable
These issues apply when using the bootstrap path (./bootstrap.sh) rather than
the npm CLI. They are not ERROR_CODES — they are runtime observations.
Check the symlink exists:
ls -la ~/.claude/commands/pre-pr.md
ls -la ~/.claude/skills/aws-specialist/SKILL.md
If missing: re-run ./bootstrap.sh. If present: restart the Claude Code session
(/clear or quit and reopen) — the session may have cached the pre-bootstrap state.
Skills are fanned out to ~/.codex/skills/<id>/SKILL.md and ~/.gemini/skills/<id>/SKILL.md during dotbabel bootstrap. Two reasons a skill might be missing there:
$PATH. Bootstrap only fans out to a CLI it can find. Re-run with dotbabel bootstrap --all (or ./bootstrap.sh --all) to force fan-out even when the CLI isn’t installed yet.$CODEX_HOME and $GEMINI_HOME override the default ~/.codex and ~/.gemini parents. Check the active values:echo "$CODEX_HOME" "$GEMINI_HOME"
ls -la "${CODEX_HOME:-$HOME/.codex}/skills/<id>/SKILL.md"
ls -la "${GEMINI_HOME:-$HOME/.gemini}/skills/<id>/SKILL.md"
dotbabel doctor validates these symlinks resolve when the matching CLI is on $PATH.
The session cached an older version. Run ./sync.sh pull (or dotbabel sync pull)
to fetch the latest, then restart the session.
Specialist skills (e.g. aws-specialist) activate when their trigger phrases appear
in your message. Ensure the phrase matches — e.g. write “AWS Lambda” not just
“lambda”. If still not triggering, check that the skill’s SKILL.md is present:
ls ~/.claude/skills/aws-specialist/SKILL.md
bootstrap.sh backed up files I didn’t expectBootstrap backs up any real file (not a symlink) at a target path before replacing
it. Backups are named <name>.bak-<timestamp>. Review them before deleting. This
is intentional — bootstrap never silently overwrites your existing work.
sync push refuses with “secret scan failed”The push-side scan detected a likely secret (*_KEY/*_TOKEN/*_SECRET pattern
or AWS key format). Review the flagged file and remove the secret. To bypass for
a known-safe file (e.g. a test fixture with a fake key):
HARNESS_SYNC_SKIP_SECRET_SCAN=1 ./sync.sh push
search, list, show) return “index missing”Run dotbabel index first to build the artifact index. The index is generated
from agents/, skills/, commands/, etc. and must be rebuilt after adding or
renaming artifacts.
dotbabel index
dotbabel search kubernetes
These apply to any Claude Code skill (SKILL.md), independent of the dotbabel
bootstrap. They mirror the failure modes in Anthropic’s Agent Skills guidance.
Claude Code only discovers a skill when its file is named exactly SKILL.md
(uppercase SKILL, lowercase .md) and lives inside its own named
subdirectory — skills/<name>/SKILL.md, never a bare file at the skills root.
A mismatched filename or a SKILL.md sitting directly in the skills root is
silently skipped. Surface loader errors with:
claude --debug
That is Claude Code’s own loader debugging — distinct from dotbabel’s
DOTBABEL_DEBUG=1 (documented at the top of this page), which only traces the
CLI’s git probes, not skill loading.
Skill selection is semantic, matched against each skill’s description. When two
skills have overlapping descriptions, Claude may pick the wrong one. Make each
description distinct — narrow the scope and the trigger phrases so they don’t
collide. dotbabel-validate-skills warns when two artifacts claim the same
trigger phrase, which is an early signal of this.
A higher-priority skill with the same name shadows yours. Enterprise skills
(pushed through managed settings) carry the highest priority and override
personal, project, and plugin skills of the same name — and cannot be overridden
locally. Rename your skill, or ask your administrator which managed skill is
claiming the name.
Runtime failures during execution usually trace to one of:
description so it is visible before invocation.chmod +x on any script the skill executes./) in every path, even on
Windows; backslashes break cross-platform resolution.