Skip to content

fix(config): release only Claude's MCP servers on a root move; read the recorded root in import and skill tracking - #775

Merged
jeff-r2026 merged 1 commit into
Tencent:mainfrom
Rererr:fix/tool-roots-followup
Sep 24, 2026
Merged

jeff-r2026 merged 1 commit into
Tencent:mainfrom
Rererr:fix/tool-roots-followup

Conversation

@Rererr

@Rererr Rererr commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Two follow-ups to #728, from the review's third pass. A re-init that moves the Claude Code root released the teamai-managed MCP servers of every MCP-capable tool, not only Claude's: reconcileMcpForConfig(…, { removeAll: true }) walks every tool of the config it is handed, so Codex, Cursor and the rest lost their servers until the next pull put them back. The release now hands it a team config narrowed to Claude. And two readers still hard-coded ~/.claude: import --from-claude scanned ~/.claude/rules, and skill-use tracking (skillExistsOnDisk) only knew the static ~/.claude/skills, so a relocated Claude Code's rules were not importable and its skills were rejected as not installed. Both now read the recorded root: import through one helper, resolveMemberToolRoots (the resolution the local agent already used — project config governing the directory, else user scope — moved into config.ts so the readers agree), and tracking through the same helper, given the hook's directory, so a skill under the root that governs the project the hook fired in counts.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature causing existing behavior to change)
  • Documentation only
  • Refactor / internal cleanup

Test Plan

  • npx tsc --noEmit passes
  • npx vitest run passes (4500 tests)
  • Added/updated tests for the change

New/updated tests: init.test.ts (the MCP release is handed a team config whose toolPaths hold Claude only), usage-tracking.test.ts (+2: a skill installed only under the given toolRoots.claude counts as present and not otherwise; a slash command from a hook whose directory's project config records a relocated root is tracked when the skill lives only there; a project config without a record follows the user-scope one; a hook that reports a directory that no longer exists — a deleted worktree — still records, under user scope), import-local-relocated-root.test.ts (new, 1: import --from-claude scans the recorded root's rules and not ~/.claude/rules).

mcp-reconcile.test.ts (+1: removeAll on a config narrowed to Claude removes Claude's server only — Cursor's file and manifest rows stay — the runtime counterpart of the init assertion).

Mutation checks: handing the full team config to the MCP release fails 1 test; dropping the recorded root from skillExistsOnDisk fails 1; not passing the resolved roots from trackSlashCommand fails 1; dropping the root from the import scan fails 1; probing a missing hook directory without the existence guard fails 1 (Cannot use simple-git on a directory that does not exist).

Real-CLI end-to-end (built dist/index.js, isolated HOME, local bare repo bridged from a synthetic HTTPS URL, Claude and Codex both installed, a team MCP server in mcp/mcp.yaml, seed toolPaths declaring mcp for both tools):

  • main (55b71ef): init → pull lands the server in ~/.claude.json and ~/.codex/config.toml (1 each). Re-init with CLAUDE_CONFIG_DIR=~/.claude-a prints Removed 2 teamai-managed MCP server(s) and, before the next pull, Codex's count is 0; the pull restores it.
  • This branch: the same re-init prints Removed 1, Codex stays at 1 throughout, ~/.claude.json goes 1 → 0 and ~/.claude-a/.claude.json gets the server on the next pull. Same on the second move (~/.claude-a → ~/.claude-b) and on the blank-variable clear.

Related Issues

Follow-up to #728 (third review pass, findings 1 and 5).

Notes for Reviewers

  • Why narrow the team config rather than the reconciler. The reconciler's removeAll contract ("every tool of this config") is what uninstall relies on; the release is the one caller that means a single tool, so the narrowing belongs at the call site.
  • resolveMemberToolRoots keeps the local agent's resolution order (project config governing the directory, else user scope) and only moves it into config.ts; the local agent's behavior is unchanged. A project config without a record follows user scope on purpose: it is the rule init applies when it fills a project config in, and a user-scope init may record the root after the project was set up. Tracking calls it with the hook's directory (resolveHookCwd), so the hook's directory, not the process cwd, decides.
  • Not in this PR, left from the same review pass: the release still keys the root change on the settings path alone (unset → explicit ~/.claude skips the MCP move), only releases delivered model config in user scope, and resolves the old root from dirname(settings). Each has a narrow trigger (an explicit default, HTTP mode in project scope, a customized nested settings path); they are separate changes if wanted.
  • Behavior change in the local agent, small and intended: memberToolRoots now goes through the same helper, so a workspace path that no longer exists resolves to the user-scope record instead of throwing from simpleGit. Existing directories behave as before. A project config that is present but unreadable still falls back to user scope here (as the inline code did); resolveConfigForDir treats that case as "no config" — left as is, noted for a later pass.
  • Missing hook directory. resolveMemberToolRoots checks that the directory exists before probing for a project config, the way resolveConfigForDir already does: simpleGit throws on a directory that is gone (a deleted worktree), and a slash command from such a hook must still be recorded.
  • Docs: one sentence in both usage guides; the --from-claude option text in src/index.ts names the recorded root and skill-data/core/references/commands.md is regenerated from it (npx vitest run commands-reference -u). The setup skill already describes the recorded root.

🤖 Generated with Claude Code

…he recorded root in import and skill tracking

A re-init that moved the Claude Code root handed the full team config to
reconcileMcpForConfig({ removeAll }), which walks every MCP-capable tool,
so Codex, Cursor and the rest lost their teamai-managed servers until the
next pull. The release now narrows the team config to Claude.

import --from-claude scanned ~/.claude/rules and skill-use tracking only
knew the static ~/.claude/skills; both now resolve the recorded root. The
resolution (project config governing the directory, else user scope) moves
into resolveMemberToolRoots so the local agent, import and tracking agree;
tracking resolves it from the hook's reported directory. The helper checks
that the directory exists before probing, as resolveConfigForDir does, so a
hook from a deleted worktree still records — this also stops the local
agent from throwing on a missing workspace path.

Follow-up to Tencent#728 (third review pass, findings 1 and 5).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
  • [P2 non-blocking] src/config.ts:366 skips the existence check when dir is undefined. If a hook omits cwd while the process is inside a deleted worktree, resolveConfigForDir() correctly falls back to user scope, but resolveMemberToolRoots() then calls detectProjectConfig(undefined) and simple-git throws. Resolve dir ?? process.cwd() first, then apply pathExists() to that target.

The PR description includes a sufficient test plan and real-CLI end-to-end verification record.

@jeff-r2026
jeff-r2026 self-requested a review September 24, 2026 07:01
@jeff-r2026
jeff-r2026 merged commit a2f93ae into Tencent:main Sep 24, 2026
11 checks passed
jeff-r2026 pushed a commit to jeff-r2026/teamai-cli that referenced this pull request Sep 24, 2026
…#404)

Second review round on PR Tencent#780. Findings on config.ts / usage-tracker.ts
remain false positives — this branch's diff does not touch either file
(git blame attributes those lines to upstream Tencent#775). The rest are fixed:

- Close the single-provider gate on the compat entry points too (review
  Tencent#5): `source add-http` and `init --http` now refuse to stand up the
  legacy singleton when a named HTTP provider exists, and
  `provider migrate-legacy` refuses when one already exists — so a named
  provider and a legacy singleton can never be dispatched together.
- `provider sync` reports a failed remote command as a failure (review
  Tencent#5-cmd): processCommands now records a per-command failure into the
  SyncOutcome (still isolated — it neither throws nor aborts the loop),
  so the adapter returns ok:false instead of a false success.
- Roll back injected hooks on a failed `provider add` (review #3): the
  rollback now runs the adapter's full teardown(), not just state +
  registry removal, so hooks init already wrote are not orphaned.
- Make legacy migration genuinely resumable (review Tencent#4): a registry
  entry whose endpoint matches the legacy one is treated as this same
  interrupted migration and resumed (legacy stays authoritative until
  the marker); only a different-endpoint entry is a foreign conflict.
- Reject Windows reserved names with an extension too — CON.txt, LPT1.foo
  (review Tencent#8) — and surface every invalid name as a clean error + exit,
  never an uncaught throw.

Tests: reserved-name-with-extension, resumable migration after a
registry write, migrate-legacy gate, and clean-exit on invalid name.
jeff-r2026 pushed a commit to jeff-r2026/teamai-cli that referenced this pull request Sep 24, 2026
)

Third review round on PR Tencent#780. config.ts / usage-tracker.ts findings
remain false positives (git blame attributes those lines to upstream
Tencent#775; this branch's diff for both files is empty). The rest are fixed:

- Restore migrate-legacy idempotency (review #3, a regression from the
  round-2 gate): the single-provider gate now rejects only a
  DIFFERENTLY-named provider, so re-running after a completed migration
  is a clean no-op and a crash-interrupted migration can still resume
  through the store's recovery path.
- Fail `provider add` when init injected zero hooks (review Tencent#4):
  injectHooksToAllTools now returns an attempted/succeeded tally, and a
  named-provider init throws when hooks were attempted but none landed,
  so the add rolls back instead of reporting a false success.
- teardown removes the built-in teamai hooks too (review Tencent#5): a named
  provider's removeLocalAgentHttp now clears the dispatch hooks its own
  init injected (via reconcileHooksToAllTools removeAll), gated on no
  other teamai install still needing them — so `provider remove` and
  failed-add rollback leave no orphaned hooks.
- Serialize `provider add` under a machine lock (review Tencent#6): the
  single-provider check-and-write now holds ~/.teamai/providers/.add.lock
  so two concurrent adds cannot both pass the empty-registry gate.
- Sync the bilingual management-backend design doc (review Tencent#7): it now
  points at providers/http/adapters/clawpro/client.ts and states the
  Tencent#404 phase 1-2 abstraction has landed.

P2 (unused GitResourceProvider / registry class / adapter routes) is
kept as the phase-1 abstraction contract by maintainer preference.

Tests: migrate idempotency-after-success, init-injected-no-hooks
rollback.
jeff-r2026 pushed a commit to jeff-r2026/teamai-cli that referenced this pull request Sep 24, 2026
…#404)

Fourth review round on PR Tencent#780. config.ts / usage-tracker.ts findings
stay false positives (empty diff vs origin/main; upstream Tencent#775 owns
those lines). The rest are fixed:

- Don't count an uninstalled adapter tool as a successful hook injection
  (review #3): injectHooksToAllTools now gates the pi/openclaw/hermes/
  opencode/omp branches on the tool actually being installed, so a
  named-provider init that lands zero real hooks is detected and fails
  (was masked by adapter no-op "successes").
- teardown's built-in-hook removal now checks for ANY other teamai
  install — user scope AND every project-scope partition — not just the
  user config (review Tencent#4), so removing a named provider never strips
  hooks a project-only Git install still needs.
- A re-written legacy singleton clears the stale migrated-to marker
  (review Tencent#5): initLocalAgentHttp (legacy path) calls
  clearLegacyMigrationMarker, so migrate → remove → source add-http /
  init --http reactivates the singleton instead of leaving it dormant.
- teardown keeps the provider home + manifest when any resource
  uninstall fails (review Tencent#6), throwing so cleanup can be retried rather
  than orphaning resources with a destroyed ownership manifest.
- Serialize migrate-legacy under the same machine lock as provider add
  via a shared withProviderLock helper (review P3).
- source list no longer shows a migrated legacy snapshot as an active
  HTTP source (review P2).

P2 (unused GitResourceProvider / registry class) kept as the phase-1
abstraction contract by maintainer preference.

Tests: injection tally excludes uninstalled tools, marker reactivation.
jeff-r2026 pushed a commit to jeff-r2026/teamai-cli that referenced this pull request Sep 24, 2026
…nt#404)

Sixth review round on PR Tencent#780. config.ts / usage-tracker.ts stay false
positives (empty diff vs origin/main; upstream Tencent#775 owns those lines).
The rest:

- Redact the migration token in staging BEFORE publishing the provider
  home (review #3): the credential is written to its isolated 0600 file
  and stripped from the staged config.json, then the dir is atomically
  moved — so the published config.json never contains the token, even in
  a crash window.
- teardownAllPlugins reports an aggregate success flag (review Tencent#4): it
  swallowed each plugin's uninstall error and returned void, so the
  caller's uninstallFailed never tripped. removeLocalAgentHttp now checks
  the return value and keeps state (plugins.json) on failure.
- provider-add rollback keeps state when its teardown could not fully
  clean up (review Tencent#5): it drops only the registry entry (so dispatch
  won't load a broken provider) and preserves the home for a retriable
  `provider remove`, instead of destroying the ownership manifest.
- `teamai uninstall` warns when a named provider's teardown was
  incomplete before deleting ~/.teamai (review Tencent#6), so orphaned external
  hooks/plugins are surfaced rather than silently stranded.
- teardown's other-install check now also detects a legacy in-tree
  <project>/.teamai config governing the cwd (review Tencent#7 residual).
- Fix the credential path in join-member.md — it is
  ~/.teamai/credentials/<name>, not the provider home (review P2 doc).

P2 (source list showing a migrated snapshot) was already fixed in the
prior round and is verified hidden; P2 (unused abstraction) and P3
(compat entries not under the provider lock) are as previously decided.
jeff-r2026 pushed a commit to jeff-r2026/teamai-cli that referenced this pull request Sep 24, 2026
…t#404)

Seventh review round on PR Tencent#780. config.ts / usage-tracker.ts remain
false positives (empty diff vs origin/main — upstream Tencent#775 owns those
lines; asked maintainer to dismiss). The genuinely new findings — all
flaws in the previous round's own fixes — are addressed:

- Give the kept-on-failure state a real CLI retry path (review #3): a
  failed add that keeps its home now also writes provider.json there, and
  `provider remove` recovers a provider from its home config when the
  registry entry is gone, then keeps the home again if teardown still
  can't finish. The retry `provider remove <name>` the message promises
  now actually works.
- `teamai uninstall` no longer deletes ~/.teamai when a provider teardown
  was incomplete (review Tencent#4): it keeps the dir (and its ownership
  manifests) and tells the user to resolve the issue and re-run, so the
  leftover external hooks/plugins stay recoverable instead of stranded.
- Stop stripping the SHARED built-in dispatch hooks on a plain
  `provider remove` (reviews Tencent#5/Tencent#7): they serve every teamai install and
  no-op without config, so a leftover is harmless while an erroneous
  removal breaks a coexisting install — and another install can't be
  enumerated reliably (in-tree <project>/.teamai lives anywhere). Full
  built-in-hook removal now happens only under `teamai uninstall`
  (TEAMAI_UNINSTALL=1) when no other install remains.

Tests: clean rollback vs. keep-on-teardown-failure, and provider remove
recovering a registry-less home.
jeff-r2026 pushed a commit to jeff-r2026/teamai-cli that referenced this pull request Sep 24, 2026
…over (Tencent#404)

Ninth review round on PR Tencent#780.

- Run the legacy singleton fallback ONLY when no named provider is
  configured (review #3): once a named provider exists it owns delivery,
  so during the migration window (registry entry published, legacy dir
  not yet deleted) the hook dispatcher no longer runs both — which had
  caused duplicate report / command execution / resource writes.
- Migration removes the shared legacy ~/.teamai/token when it was the
  credential source (review P2): the token now lives only in the isolated
  0600 credential file, so the old plaintext copy is deleted instead of
  stranded after a later `provider remove`.

Not changed:
- Tencent#4 (provider remove leaving shared built-in dispatch hooks): kept per
  the maintainer's explicit decision — those hooks serve every install
  and no-op without config; full removal stays with `teamai uninstall`.
- #1/#2 (config.ts / usage-tracker.ts): false positives, not in this
  PR's diff (upstream Tencent#775); asked the maintainer to dismiss.
- P3 (compat entries not under the provider lock): they refuse when a
  named provider exists, so they cannot create a second active backend.

Tests: legacy ~/.teamai/token removed on migration.
jeff-r2026 pushed a commit to jeff-r2026/teamai-cli that referenced this pull request Sep 24, 2026
Tenth review round on PR Tencent#780 (P2 cleanup).

- Remove the `--priority` flag from `provider add http` and
  `provider migrate-legacy`, and the parsePriority helper. With a single
  provider it never affects behavior, so exposing and persisting it is
  the unused/speculative config the review rules warn against. The
  `priority` data-model field stays (defaulted to 50, reserved for the
  later arbitration phase) but is no longer user-configurable. Docs and
  the generated command reference updated to match.

Not changed (as previously decided): #1/#2 config.ts/usage-tracker.ts
are false positives outside this PR's diff (upstream Tencent#775); provider
remove intentionally leaves the shared built-in dispatch hooks
(maintainer decision); the compat entries refuse a second backend so
they need no shared lock.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants