Skip to content

codev doctor: findWorkspaceRoot accepts packages/codev as an instance marker, silently auditing the wrong tree #1246

Description

@waleedkadous

codev doctor silently audits the wrong directory tree when run from anywhere under packages/, because findWorkspaceRoot() accepts a bare directory named codev as a codev-instance marker — and this monorepo has a package called packages/codev.

Found while running codev doctor during work on #1238 (PR #1243). Reproduces on main.

Repro

It is entirely about cwd:

cd <repo-root>/packages/codev && codev doctor   # ⚠ "Architect state files ... are not gitignored"
cd <repo-root>                && codev doctor   # ✓ Project structure OK

.gitignore:15-16 correctly carries codev/state/*.md + !codev/state/*_thread.md, and git check-ignore -q codev/state/__doctor-probe__.md exits 0 from either repo root. The warning is bogus — but auditStateFileIgnore is not at fault; it is being handed the wrong root.

Note the trap: cd packages/codev is the documented place to work (CLAUDE.md → Directory Map: "pnpm build / pnpm test → run from packages/codev/"), so this is the normal cwd for anyone building or testing, not an exotic one.

Mechanism

findWorkspaceRoot() (packages/codev/src/commands/doctor.ts:443-455) walks up from cwd:

while (current !== dirname(current)) {
  if (existsSync(resolve(current, 'codev'))) return current;   // ← the bug
  if (existsSync(resolve(current, '.git')))  return current;
  current = dirname(current);
}

Starting at packages/codev:

  1. packages/codev/codev — absent, packages/codev/.git — absent → walk up
  2. packages/resolve('packages', 'codev') matches packages/codev, the npm package directory, not a codev instance → returns <repo-root>/packages

Every downstream check then receives workspaceRoot = <repo-root>/packages. auditStateFileIgnore runs its probe with cwd=packages/, so the path resolves to packages/codev/state/__doctor-probe__.md, which the root-anchored codev/state/*.md rule correctly does not match → exit ≠ 0 → warning.

Evidence

Calling the built auditStateFileIgnore directly with explicit roots (with node_modules installed, so this is not an artifact of an unbuilt tree):

workspaceRoot passed result
<worktree> []
<worktree>/packages ⚠ warns
/…/cluesmith/codev (main checkout) []
/…/cluesmith/codev/packages warns — reproduces on main

Blast radius — wider than the one warning

The bogus gitignore warning is just the visible symptom. workspaceRoot from findWorkspaceRoot() feeds every project-scoped check in doctor(), all of which then silently audit packages/ instead of the repo:

These mostly fail quietly — a check that finds nothing to audit reports ✓. So codev doctor run from packages/ can print a clean bill of health for a tree it never looked at. That is worse than the false warning, because it is invisible.

Fix direction

Don't accept a bare codev directory as the instance marker. Two complementary guards, plus a worktree caveat:

1. Require an instance signature. A real codev instance contains at least one of:

specs/  plans/  reviews/  resources/  protocols/  roles/  consult-types/

Verified against the two directories in question:

real codev/ packages/codev/
specs/, plans/, reviews/, resources/, roles/, consult-types/, protocols/ present absent
templates/ present also present
package.json, src/, dist/, tsconfig.json absent present

Two traps worth encoding:

  • Do not use templates/ as a signaturepackages/codev/templates/ exists, so it collides and would not fix the bug.
  • Do not require protocols/ — per the four-tier resolver, a project that customizes no protocol legitimately has no codev/protocols/ on disk (CLAUDE.md calls this out explicitly). It belongs in an any-of set, never an all-of.

2. Add a negative signal. A candidate codev/ containing package.json is an npm package, not an instance — reject it outright. Cheap and decisive here.

3. Prefer the .git root when both markers match, as suggested — but note .git is a file, not a directory, inside a git worktree (.builders/<id>/.git is a 71-byte gitlink). The current existsSync handles that correctly; any refactor to statSync(...).isDirectory() would silently break every builder worktree, which is most of where Codev runs.

Ordering that resolves this case correctly: prefer a directory that has both a signature-passing codev/ and .git; else the nearest signature-passing codev/; else the .git root. From packages/codev, step 1 skips packages/ (fails the signature) and lands on the repo root — the right answer.

Scope

Deliberately not fixed in PR #1243 (#1238's session-log retention) to keep that PR scoped; filing separately at the architect's direction.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/scaffoldArea: Install path — codev init/adopt/update/doctor, codev-skeleton, four-tier resolver

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions