dotbabel

Troubleshooting

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 errors

SPEC_JSON_INVALID

docs/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_INVALID

status is not one of draft | approved | implementing | done. Fix: pick a valid status. Only approved|implementing|done gate PR coverage.

SPEC_ID_MISMATCH

spec.json.id does not equal the directory name. Fix: rename the directory or update id — they must match.

SPEC_MISSING_REQUIRED_FIELD

A 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_MISSING

linked_paths is missing, empty, or contains a non-string entry. Fix: every entry must be a non-empty string glob or path.

SPEC_ACCEPTANCE_EMPTY

acceptance_commands is empty or contains a non-string entry. Fix: at least one command that CI can run.

SPEC_DEPENDENCY_UNKNOWN

depends_on_specs references an id that does not exist under docs/specs/. Fix: create the dependency spec or remove the reference.


Manifest errors

MANIFEST_ENTRY_MISSING

A .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_MISMATCH

A 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_FILE

A 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_CYCLE

The dependencies[] graph has a cycle. Fix: break the cycle. The error .got field shows the path A -> B -> A.


Coverage errors

COVERAGE_UNCOVERED

A 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_RATIONALE

The 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_ID

The PR body references a Spec ID: that does not exist under docs/specs/. Fix: check the spec directory, or create the spec first.


Drift errors

DRIFT_TEAM_COUNT

An 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_PATH

A 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_FILES

instruction_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_MISSING

An instruction_files entry points at a path that does not exist. Fix: create the file or drop the entry.

DRIFT_GENERATED_STALE

A generated rule-floor output differs from what dotbabel-generate-instructions would write. Fix: run npx dotbabel-generate-instructions and commit the generated outputs.


Project-config errors (.dotbabel.json)

CONFIG_UNKNOWN_CLI

fan_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_LAYOUT

fan_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_EXCLUSION

cli_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 errors

QUALITY_CONFIG_INVALID

An 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_UNAVAILABLE

The 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_UNAVAILABLE

A 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_REQUIRED

A 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_INVALID

A 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_INVALID

The 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_FAILED

An 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_]*.

Mutation testing reports not_configured although Stryker is installed

Built-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.

Stryker dies with EISDIR: illegal operation on a directory, copyfile

Stryker 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 message

The 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 score

The 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/**'

Measurement states are not error codes

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 errors

SCAFFOLD_CONFLICT

dotbabel-init refuses to overwrite an already-initialized repo. Fix: pass --force to overwrite, or remove .claude/skills-manifest.json / docs/specs/ first.

SCAFFOLD_USAGE

Bad CLI invocation of dotbabel-init (e.g. flag without a value). Fix: see --help.


Settings-validator errors (validate-settings.sh)

SETTINGS_SEC_1

A *_KEY / *_TOKEN / *_SECRET field in ~/.claude/settings.json holds a literal 20+ character value. Fix: replace with ${ENV_VAR} reference.

SETTINGS_SEC_2

skipDangerousModePermissionPrompt is set. Fix: remove it.

SETTINGS_SEC_3

An 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_1

Settings 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 + usage

ENV_REPO_ROOT_UNKNOWN

createHarnessContext() could not resolve a repo root. Fix: pass --repo-root <path> or DOTBABEL_REPO_ROOT=<path>, or run inside a git worktree.

ENV_FACTS_MISSING

docs/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_FLAG

An unknown flag was passed. Exit 64. Fix: see --help.

USAGE_MISSING_POSITIONAL

A required positional argument is missing. Exit 64. Fix: see --help.


Handoff

handoff list shows a session that handoff pull cannot find

Symptom: 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 volume

Exit 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

Skills & commands (dotfile users)

These issues apply when using the bootstrap path (./bootstrap.sh) rather than the npm CLI. They are not ERROR_CODES — they are runtime observations.

A skill or command isn’t available in Claude Code

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.

A skill isn’t available in Codex / Gemini CLI

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:

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.

A skill runs but uses outdated behavior

The session cached an older version. Run ./sync.sh pull (or dotbabel sync pull) to fetch the latest, then restart the session.

A specialist skill doesn’t auto-activate

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 expect

Bootstrap 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

Taxonomy commands (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

Skill authoring & loading

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.

A skill isn’t loading at all

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.

The wrong skill activates

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 skill is ignored even though it’s installed

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.

A skill errors mid-run

Runtime failures during execution usually trace to one of: