Skip to content

HKS-01..05 false negative when the scan root is itself a .claude directory #46

Description

@marcelo-lima-clickbus

Check ID

HKS-01

What went wrong

False negative — check failed but the artifact is actually there

Repository / minimal reproduction

mkdir -p repro/.claude && cd repro
printf '# Repro\n' > CLAUDE.md
cat > .claude/settings.json <<'EOF'
{
  "hooks": {
    "PreToolUse": [
      { "matcher": "Bash", "hooks": [{ "type": "command", "command": "echo gate" }] }
    ],
    "PostToolUse": [
      { "matcher": "Edit", "hooks": [{ "type": "command", "command": "echo feedback" }] }
    ]
  }
}
EOF

npx harness-score .          # Hooks & Guardrails: 14/14
npx harness-score .claude    # Hooks & Guardrails:  0/14

Expected vs. actual

Same settings.json, two scan roots, opposite results.

Scan root Hooks & Guardrails
repro/ 14/14
repro/.claude/ 0/14

Scanning the .claude directory as the root fails HKS-01 through HKS-05. The config is valid, has PreToolUse (gate) and PostToolUse (feedback), and passes every check when scanned one level up.

Cause is in packages/cli/src/harness/registry.ts:88:

{ toolId: 'claude-code', kind: 'hooks', pathRegex: /(^|\/)\.claude\/settings\.json$/ }

The pattern requires a literal .claude/ segment. When .claude is the scan root, paths are relative to it and the file is just settings.json, which never matches:

NOMATCH  settings.json
MATCH    .claude/settings.json
MATCH    some/nested/path/.claude/settings.json

There is a second-order effect worth noting. readNormalizedHooks (harness/hooks.ts:153) picks whichever collected config declares the most events. With the root file excluded from collection, any unrelated nested .claude/settings.json (a vendored repo, a cached job artifact, a test fixture) becomes the only candidate and wins by default. In my case the reported evidence pointed at a scratch directory buried several levels down while the real config sat at the root, so the failure message named the wrong file rather than reporting nothing found.

A repo-root settings.json would fix the primary case, though it may be worth deciding whether a nested .claude/ inside vendored or cached content should be a scoring candidate at all.

harness-score version

1.5.2 (built from main at clone time)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions