Skip to content

fix(pull): do not deliver an env, hook or MCP entry with a mistyped key (#822) - #833

Merged
jeff-r2026 merged 3 commits into
Tencent:mainfrom
SaulMoro:fix-822-namespace-followups
Sep 26, 2026
Merged

jeff-r2026 merged 3 commits into
Tencent:mainfrom
SaulMoro:fix-822-namespace-followups

Conversation

@SaulMoro

@SaulMoro SaulMoro commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

A mistyped scoping key sent an env variable, hook or MCP server to every member, and a mistyped top-level key (server: for servers:) uninstalled every team MCP server or hook for every member, silently. This fixes items 1 and 4 of #822 and the top-level-key follow-up; item 2 turns out to be intended behavior, and item 3 is #832.

 resolveEntries(reader)                              # namespaced-entries.ts
   read(file)                                        # env / hooks / mcp readers
+    no schema top-level key (mcp, hooks) -> failed  # was: read as empty, removed everything
+    unknownKeys = written keys - schema.shape       # zod strip used to hide them
   keepScopedEntry(entry)
+    unknownKeys  -> not delivered, warn "<file>: <noun> "<name>" has unknown key `role:`, so this entry is not delivered"
     projects:    -> reaches nobody, warn (unchanged)
     roles:       -> deprecated filter (unchanged)

 recall footer                                       # recall.ts
-  以上内容来自团队知识库,仅供参考。…
+  The above comes from the team knowledge base and is for reference only. …

Not asked for in #822 but needed for it: env add, env remove and remove mcp keep an unknown key when they rewrite a file, so an edit does not turn a blocked entry back into one sent to everyone. remove mcp now edits the YAML document, so it also keeps comments. env add on such a variable warns that pull does not deliver it.

A hooks or MCP file whose mapping has none of its schema's top-level keys (servers; hooks, builtin) now fails the resolution like a file that does not parse, via missingTopLevelKeyReason in namespaced-entries.ts, the same check env.yaml has had since #662. The existing keep-on-failure paths then hold: reconcileMcpForConfig returns unresolved and changes nothing, and hooks reconcile only the built-ins. An extra top-level key beside a known one is still ignored; the schemas are not .strict().

Rebased onto origin/main (fe03787, #832). Only docs/designs/multi-project-management.md conflicted: both PRs rewrote the same "Known gaps" bullet. Resolution: the mistyped-key gap is dropped (fixed here), and #832's pull --dry-run sentence describes shipped behavior rather than a gap, so it moved into the per-entry-key paragraph. src/resources/hooks.ts auto-merged; a dry run now reports the unknown-key warnings too (run below).

Type of Change

  • Breaking change (fix or feature causing existing behavior to change)

Breaking only for a team that added an extra key to an entry by hand (notes:): that entry is no longer delivered, and pull and doctor name it. The same holds for an entry key a later release adds, on members still on this version, so every member upgrades before a team uses one (CHANGELOG, design doc).

Test Plan

  • npx tsc --noEmit passes
  • npx vitest run passes at 9b542ac: 327 files, 5068 passed, 1 skipped
  • Added/updated tests for the change

New tests, red on origin/main (7c834ce):

env / hooks / mcp   entry with `role:` -> not delivered, one warning naming file, entry and key     red on main
env / hooks / mcp   namespace copy with `role:` -> root entry delivered instead                   red on main
env / hooks / mcp   entry with every known key -> delivered, no new warning                        guard
env add, remove mcp keep an unknown key when they rewrite the file                                 red on main
env add on a variable with `role:` -> warns it is not delivered                                     red before the fix
doctor              unknown-key entry named in the per-entry-key check                             red on main
recall              last line is the English note, no CJK in the output                            red on main
e2e project-scoped-delivery   pull with `role:` typo -> server not installed, warning                    red on main
mcp reconcile   `server:` for `servers:` -> 'temp' kept, changes [], one warning naming file and key red on 206c0a13
hooks reconcile `hook:` for `hooks:` -> installed hook kept, one warning naming file and key      red on 206c0a13
mcp / hooks     extra top-level key beside servers:/hooks:, builtin:-only hooks file -> still read   guard

hooks-handler.test.ts "skips the whole file on malformed yaml" expected [] for :::not yaml:::\n - broken, which YAML parses as a mapping with no hooks: key: that expectation was this bug (read as no hooks, remove every installed one). It now expects null (failed), as its sibling "not YAML at all" does.

Ablation: each part (the keepScopedEntry branch, passing the keys into the scope, each reader, the schema-derived key list, each writer, the recall line) was reverted alone, and at least one of the tests above failed.

Build and E2E: npm run build ok. E2E with --retry 0: 6 files (project-scoped-delivery, e2e, opencode-recall, remove-mcp-namespaces, namespaced-entries, roles-tags-pull), 30 passed, 22 skipped (remote-only). At 9b542ac, every e2e file that writes an MCP or hooks file (copilot-mcp, codebuddy-mcp, doctor-delivery-cli, hooks-project-isolation-issue373, mcp-uninstall, namespaced-entries, project-scoped-delivery, remove-mcp-namespaces, scope-isolation-issue85): 9 files, 39 passed. The full e2e suite was not run.

Before / After (real CLI, provider git on a local bare repo, project scope, Claude):

Team repo has Before (origin/main 7c834ce) After
MCP server typo-api with role: [frontend] installed in .mcp.json, no warning not installed, warned
env DB_URL with role: [frontend] exported in env.sh, no warning not exported, warned
teamai recall ... last line Chinese English
mcp/mcp.yaml server:, hooks/hooks.yaml hook: (typo pushed after a good pull) .mcp.json and team hook emptied, no warning both kept, warned by pull, dry run and doctor
Real CLI, top-level typo: before (206c0a1) and after (9b542ac)

Team repo: servers: [team-api] and hooks: [team-guard], pulled once (both installed), then server: / hook: pushed. Same script, two builds.

=== BEFORE (206c0a13)
$ teamai pull --dry-run
ℹ MCP: [dry-run] Would make 1 change(s) across 1 server(s)
$ teamai pull
✔ Updated teamai hooks in .../home/.claude/settings.json
ℹ MCP: 1 change(s) across 1 server(s). Restart your AI tool session to load them.
exit=0
.mcp.json servers: (none)          .mcp.json CHANGED          settings.json has team-guard: 0
$ teamai doctor                    (no failing MCP or hooks check)

=== AFTER (9b542ac2)
$ teamai pull --dry-run
⚠ hooks/hooks.yaml does not parse: it has no top-level `hooks:` or `builtin:` key, only `hook`. hooks was not applied this run, so your installed team hooks are unchanged. Fix the file in the team repo and push.
⚠ mcp/mcp.yaml does not parse: it has no top-level `servers:` key, only `server`. mcp was not applied this run, so your installed team MCP servers are unchanged. Fix the file in the team repo and push.
$ teamai pull
... same two warnings ...
⚠ Pull finished, but 2 check(s) failed:
⚠   ✖ Team MCP servers can be read
⚠   ✖ Team hooks can be resolved
exit=0
.mcp.json servers: team-api        .mcp.json unchanged        settings.json has team-guard: 1
$ teamai doctor
  ✖ Team MCP servers can be read
    → mcp/mcp.yaml does not parse: it has no top-level `servers:` key, only `server`. ...
  ✖ Team hooks can be resolved
    → hooks/hooks.yaml does not parse: it has no top-level `hooks:` or `builtin:` key, only `hook`. ...
Real CLI, after the rebase onto #832 (206c0a1)

Team repo: typo-api (MCP), typo-guard (hook) and DB_URL (env) each carry role: [frontend].

$ teamai pull --dry-run
⚠ env/env.yaml: variable "DB_URL" has unknown key `role:`, so this entry is not delivered. Correct the key or remove it.
ℹ [project] [dry-run] Would sync 1 env variable(s)
⚠ hooks/hooks.yaml: hook "typo-guard" has unknown key `role:`, so this entry is not delivered. Correct the key or remove it.
ℹ Would apply 1 team hook(s):
ℹ   [shared-guard] echo shared
⚠ mcp/mcp.yaml: server "typo-api" has unknown key `role:`, so this entry is not delivered. Correct the key or remove it.
ℹ MCP: [dry-run] Would make 1 change(s) across 1 server(s)
exit=0
(no .mcp.json, env.sh or settings.json written)

$ teamai pull --force
... same three warnings ...
ℹ Applying 1 team hook(s):
ℹ   [shared-guard] echo shared
ℹ MCP: 1 change(s) across 1 server(s). Restart your AI tool session to load them.
exit=0
.mcp.json: shared-api only    env.sh: export SHARED='shared-value'    settings.json hooks: echo shared only

$ teamai env add DB_URL new
⚠ env/env.yaml: variable "DB_URL" has unknown key `role:`, so pull does not deliver it. Correct the key or remove it in env/env.yaml.
✔ Updated env variable: DB_URL=new
$ teamai env add SHARED v2
✔ Updated env variable: SHARED=v2
Real CLI, before the rebase (92f2e05)
$ teamai pull --force
⚠ env/env.yaml: variable "DB_URL" has unknown key `role:`, so this entry is not delivered. ...
✔ [project] Synced 1 env variable(s) to .../project/.teamai/env.sh
⚠ mcp/mcp.yaml: server "typo-api" has unknown key `role:`, so this entry is not delivered. ...
ℹ MCP: 1 change(s) across 1 server(s). Restart your AI tool session to load them.
  ✖ Team env, hooks and MCP entries have no per-entry key to fix
exit=0
.mcp.json: shared-api only        env.sh: export SHARED='shared-value'

$ teamai recall "canary deploy rollout"
--- [teamai:recall:end] ---
The above comes from the team knowledge base and is for reference only. Use the Read tool to open the listed files for details.

Before, on origin/main: pull --force installed typo-api and exported DB_URL with no warning; recall ended with 以上内容来自团队知识库,仅供参考。….

Related Issues

Part of #822: fixes items 1 and 4, and the top-level-key follow-up (a hooks or MCP file with server: for servers: now fails and keeps what is installed), added to this PR because it touches the same readers; item 2 is docs-only (see Notes). Item 3 (pull --dry-run hooks/MCP warnings) is #832, now on main; this branch is rebased onto it. Item 5 (pull keeps local edits) follows separately.

Notes for Reviewers

  • Item 2 is not a bug. A subscribed tag bringing a tagged skill from an inactive namespace is what fix(pull): preserve role isolation for tag subscriptions #337 added, and e2e/roles-tags-pull.test.ts tests it. docs/usage-guide.md documents it too. Only the design doc's "Known gaps" called it a gap; it now describes it as intended. No code change.
  • Door: two-way. Blast radius: team repos whose env, hook or MCP entries carry a key outside the schema.
  • Model profiles are already .strict() and fail the file, so they are unchanged.
  • Covered now: a mistyped top-level key (server: for servers:) fails the file and keeps what is installed. Not covered: env list / mcp list / hooks list / status do not warn about a dropped entry, as for projects: today.
  • Blast radius of the top-level check: a hooks or MCP file that is a non-empty mapping with none of its keys, for example one holding only version: 1, now fails and keeps what is installed where it used to remove everything. An empty file or {} is unchanged.

@SaulMoro
SaulMoro force-pushed the fix-822-namespace-followups branch from b90ed73 to 92f2e05 Compare September 25, 2026 17:07
@SaulMoro SaulMoro changed the title fix(pull): fail closed on a mistyped entry key and warn in --dry-run (#822) fix(pull): do not deliver an env, hook or MCP entry with a mistyped key (#822) Sep 25, 2026
@jeff-r2026 jeff-r2026 self-assigned this Sep 26, 2026
@github-actions

Copy link
Copy Markdown
  • [P3 nit] src/resources/env.ts:375 — Updating an existing variable with an unknown key preserves the key, so the variable remains blocked, but teamai env add still reports Updated env variable without warning that it will not be delivered. This can mislead users into pushing an ineffective update.

The PR description includes sufficient real-CLI/E2E verification, so no testing-related blocking finding. Previously reported wording, doctor-title, upgrade-note, and duplication issues are resolved.

…ey (Tencent#822)

Item 1. Env, hook and MCP entry schemas are plain z.object, which strips
unknown keys, so a mistyped scoping key (`role:` for `roles:`) vanished and
the entry reached every member. Each reader now reports the keys an entry
was written with that its schema does not know (known keys come from the
schema's own shape), and keepScopedEntry does not deliver such an entry and
warns once, naming the file, the entry and the key, the same path the
removed `projects:` key takes. doctor's per-entry-key check is retitled to
cover it. `env add`/`env remove` and `remove mcp` keep such a key when they
rewrite the file; `remove mcp` edits the YAML document instead of
re-serializing the parsed servers.

Item 4. recall ended every result with a Chinese line; it is English now.

Item 2 is not a bug: tags reaching a tagged skill in an inactive namespace
is the behavior Tencent#337 added and roles-tags-pull tests. The design doc's
Known gaps entry now says so.

Item 3 (pull --dry-run warnings) is left to Tencent#832.
…encent#822)

Updating a variable that carries an unknown key keeps the key, so the
variable stays undelivered; env add now says so instead of only reporting
'Updated env variable'.
@SaulMoro
SaulMoro force-pushed the fix-822-namespace-followups branch from 92f2e05 to 206c0a1 Compare September 26, 2026 07:43
@github-actions

Copy link
Copy Markdown

No findings.

  • The previously reported env add warning issue is resolved at src/env-commands.ts:91; updates to blocked variables now explicitly warn that pull will not deliver them.
  • The PR description documents sufficient testing, including representative real-CLI/E2E verification for the runtime behavior change.

…o known top-level key (Tencent#822)

A hooks or MCP file with `server:` for `servers:` parsed as empty and
removed every installed team server or hook for every member, silently.
Such a file now fails like one that does not parse, naming the keys found
and the key expected. An extra key beside a known one is still ignored.
@github-actions

Copy link
Copy Markdown
  • [P3 nit] PR description — The “Not covered” note says mistyped top-level keys are still ignored, but the current diff rejects hooks/MCP files containing no recognized top-level key in src/resources/hooks.ts:75 and src/resources/mcp.ts:72. Update the note to reflect the implemented behavior.

The previously reported env add warning issue is resolved at src/env-commands.ts:95. The PR description includes sufficient representative real-CLI/E2E verification.

@jeff-r2026
jeff-r2026 merged commit b136c9c into Tencent:main Sep 26, 2026
11 checks passed
jeff-r2026 pushed a commit that referenced this pull request Sep 29, 2026
) (#851)

* fix(env,hooks,mcp,status): name the entries that are not delivered (#822)

env list, mcp list, hooks list, status and list <env|hooks|mcp> resolve the
entry types to show what reaches this directory, but dropped the resolution
notices: an entry an unknown key (a mistyped role:) or a removed key (roles:
on env, projects:) takes out of the delivered set was silently missing from
the list, and status counted around it. pull and doctor report these; now the
list commands do too, via reportEntryResolution.

env add on a variable carrying a removed per-entry key kept reporting plain
'Updated' — #833 taught it to warn for keys the schema does not know, but a
removed key is in the shape on purpose (so it can be detected), so it stayed
silent. Warn the same way for those.

* test(e2e): match the delivered DEVOPS_ONLY form, not its name in the notice

env list now reports the withheld per-entry `roles:` variable by name
(#822), so the whole-output not.toContain('DEVOPS_ONLY') assertion tripped
on the delivery notice itself. The variable stays out of the delivered
list; match the listed form `DEVOPS_ONLY=` instead.

* fix(env): point env add at the namespace file, not a key drop

The review of #851 found the update-path warning told users to remove a
per-entry `roles:`/`projects:` key in place, which delivers a root-scoped
secret to the whole team. The remediation now reuses `moveTo`, the same
remedy pull's notice names, so it points at the namespace file to move the
entry into (with the manifest declaration to add when nothing declares it).

`moveTo` and `TargetFiles` move from module-private to exported for this.

The review also found skill-data/setup/references/manage-admin.md still
said only pull and doctor report undelivered entries, while this branch
made the list commands and status report them too. It now names them, as
docs/usage-guide.md does.

* fix(entries): report notices alongside resolution failures

* fix(entries): scope list warnings and document env updates

* docs(entries): align changelog with list and env warnings

---------

Co-authored-by: ydflow <ydflow@users.noreply.github.com>
Co-authored-by: ydflow <314143294+ydflow@users.noreply.github.com>
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