Skip to content

Doctor never verifies the global copy of a pack configured in both scopes #379

Description

@bguidolim

Summary

When a pack is configured both globally and in the current project, mcs doctor checks it exactly once — under the project scope — and never verifies its global artifacts at all.

DoctorRunner.resolveCheckScopes builds the second scope as:

let globalOnlyIDs = globallyConfiguredPackIDs.subtracting(projectPackIDs)

The deduplication is deliberate (PR #175, so a pack in both scopes is not checked twice), but it drops the global half rather than merging it. The surviving project scope carries effectiveProjectRoot set and the project's exclusions, so nothing in the run ever looks at:

  • ~/.claude/skills|hooks|commands|agents/<destination> — the global copies of the pack's files
  • the bash ~/.claude/hooks/... hook entry in the global settings.json (a different command string from the project entry, per Constants.HookCommand)
  • the pack's section in ~/.claude/CLAUDE.md
  • settings keys and value hashes recorded on the global PackArtifactRecord

Why it is easy to miss

FileExistsCheck carries a fallbackPath to the global location, so a project-scoped file check reports .pass when only the global copy exists. Combined with the scope subtraction, doctor cannot distinguish "installed in the project" from "installed globally" for copyPackFile artifacts, and reports a clean run either way.

Symptom

Silent under-reporting. A globally-installed hook script that was deleted, a global CLAUDE.md section that drifted, or a global settings entry that was hand-edited goes unreported for any pack that also happens to be configured in the current project. Running mcs doctor --global, or running outside a project, does check them — so the gap only appears from inside a project that shares a pack with the global scope.

Proposed fix

Give a both-scopes pack a second CheckScope with effectiveProjectRoot: nil, the global scope's excludedComponentIDs, and its artifactsByPack record, rather than subtracting it out. Two things need attention:

  • Check names would collide in the output (the same pack contributing two entries per component), so the scope label needs to reach the rendered name or the section grouping, otherwise a reader cannot tell which copy failed.
  • resolveCheckScopes currently returns at most two scopes and callers assume the project one is unique; adding a third changes that shape.

Worth doing alongside #371's ScopeDuplicationCheck, which already loads both scopes' state and could share the resolution.

Acceptance criteria

  • With a pack configured in both scopes, deleting ~/.claude/hooks/<script> is reported by mcs doctor run inside the project
  • Failures name which scope the artifact belongs to
  • A pack configured in only one scope still produces exactly one set of checks

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Medium prioritybugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions