Conversation
…ut (Tencent#879) An entry reader took its directory, file name, activation key and failure wording from its EntryType. It can now declare them as an EntryLayout, defaulting to entryLayout(type), which gives today's values. This lets a later reader read env/secrets.yaml and env/<ns>/secrets.yaml activated by resources.env. No behaviour change: env, hooks, MCP and models resolve and report as before. Part of Tencent#875.
…second store getTeamValuesPath takes the store directory (defaulting to models/teams) and keeps its <team>-<hash>.json naming. The piped-stdin reader moves to utils/prompt.ts as readStdin; the --api-key-stdin checks and messages stay in the models command. No behaviour change. Refs Tencent#879 (S2), Tencent#875
…ate (Tencent#879) A team repo can declare the secrets its members need, with no value, in env/secrets.yaml and env/<ns>/secrets.yaml (key, optional description and url). They resolve like env.yaml: active through resources.env, a namespace entry replaces the root entry with the same key. The declarations are absent, valid or failed; a broken file fails the secrets only, is reported in secret wording by pull, env list and doctor, and env variables are still delivered. - env list and list env show each declared secret as environment or missing, never its value, --reveal included. - doctor fails "Team secrets can be resolved" on a broken file, and its notes name env/secrets.yaml, not env/env.yaml, for an override or a key repeated in legacy mode (describeEntryNotes takes the reader's layout). - push lists a changed secrets.yaml, in single-repo mode too. - docs/designs/team-secrets.md and .zh-CN.md start here, with the Tencent#818 boundary; usage guide, product overview, multi-project, management backend and the admin reference updated. Part of Tencent#875.
env add <key> [value] --secret [-d] [--url] [--role|--project] writes env/secrets.yaml or env/<ns>/secrets.yaml with no value; a value is rejected and never printed. env remove removes a declared secret when env.yaml does not set the key, and --secret removes only the declaration for a key both files carry. entryNamespaceFromFlags takes a layout so the --role warning names secrets.yaml.
…t is missing (Tencent#879) The session-start pull inherits the agent's environment, which often lacks the member's shell export, so it removed the MCP entry the interactive pull had written. A server whose only missing variables are declared secrets now keeps its entry and ownership record; it is removed when it leaves mcp.yaml or by removeAll. A failed secrets declaration keeps managed MCP state.
…MCP servers (Tencent#879) teamai env set KEY (hidden prompt, --stdin, --from-env VAR) and env unset KEY store a member's value per team repo in ~/.teamai/secrets/teams/, 0600, accepting only keys the scope declares as secrets. ${VAR} in MCP servers resolves a declared secret from that value, then from the member's own environment, which leaves out values a teamai env.sh exported (Conflict 10). A key declared as a secret and set in env.yaml resolves as the secret: its repo value leaves env.sh, the env backup, both list renderers and doctor's expected set (Conflict 13). A failed declaration leaves env.sh and the backup as they are (Conflict 14). env list shows team.
…ine (Tencent#879) env set/unset --global keep the value in ~/.teamai/secrets/machine.json. Resolution becomes team value > machine value > the member's environment, for MCP servers and env list (state `global`). In a scope --global still accepts only a declared secret; outside any scope it accepts any valid key and notes that no team declares it yet.
# Conflicts: # docs/designs/team-secrets.md # docs/designs/team-secrets.zh-CN.md # docs/usage-guide.md # docs/usage-guide.zh-CN.md # skill-data/setup/references/manage-admin.md # src/env-commands.ts # src/mcp-reconcile.ts # src/resources/secrets.ts
…encent#882) A project-scope MCP config that carries a resolved ${VAR} sat untracked and unignored in the business repo, one `git add -A` from committing the token. After the reconcile writes such a file and git would track it, teamai lists its path in the clone's .git/info/exclude inside a marked block (resolved via `git rev-parse --git-path`, so linked worktrees and submodules work). The committed .gitignore is never touched; an ignored path or a config with no resolved value adds nothing; dry runs write nothing. Project-scope uninstall removes only teamai's block, and doctor reports such a file git would still commit. The hook sits after the appliers in reconcileMcpForConfig, outside desiredMcpForTarget/applyJson/applyCodex, so it merges cleanly with Tencent#880.
…encent#879) Interactive pull, mcp list, env list and doctor print one line per declared secret with no value, naming the MCP servers that use it, `teamai env set KEY` and the declared url. doctor prints it as a note and no longer fails the MCP delivery check for a server skipped only for a missing declared secret. Pull and doctor also note a kept entry that may hold an old value and a key declared as a secret and set in env.yaml. The silent pull prints nothing. The lines come from one envAdvisories() result that later pull notices extend.
…Tencent#882) The plan now records whether the project's .git/info/exclude holds teamai's MCP config block (gitExcludeBlock). It counts toward isPlanEmpty, is listed in the summary and dry run, and gates the removal, so a plan whose only teamai leftover is the block removes it instead of reporting "Nothing to uninstall".
This was referenced Sep 28, 2026
# Conflicts: # docs/designs/team-secrets.md # docs/designs/team-secrets.zh-CN.md # docs/usage-guide.md # docs/usage-guide.zh-CN.md
|
…ther case of the key (Tencent#875)
…se, and take back each tool a write that did not happen recorded (Tencent#882)
…unnoted until the servers no record claims are noted (Tencent#882)
|
Findings
The PR description includes sufficient representative real-CLI and end-to-end verification. The other findings from earlier review passes appear resolved. |
…their formats before releasing its line (Tencent#882)
…se record a pull writes first, as with no managed-mcp.json at all (Tencent#882)
…rite unless its own records hold a resolved value there (Tencent#882)
|
Findings
The PR description contains sufficient representative real-CLI and end-to-end verification. Previously reported issues other than the signal limitation appear resolved. |
…ull and in doctor, not only an empty managed-mcp.json (Tencent#882)
…eps out of git, not only the ones a pull writes (Tencent#879)
…e name in another case (Tencent#875)
…onfig, and pin an uninstalled tool's leftover config (Tencent#882)
…d tool maps its file, and name a re-including .gitignore rule on a dry run (Tencent#882)
|
Findings
The PR description includes sufficient representative real-CLI/e2e verification. The previously reported blocking findings appear resolved. |
…n mcp list and the missing-secret notice (Tencent#875)
|
Findings
The PR description includes sufficient representative real-CLI/e2e verification. Previously reported findings other than the signal limitation appear resolved. |
…e key, and settle its notes on every format's view (Tencent#882)
…ty: team and team.git are two directories (Tencent#875)
…ts as a writer, though an installed tool maps the file (Tencent#882)
|
Findings
Resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The team declares which secrets it needs; each member keeps the value on their own machine. MCP servers and CLIs (
teamai env exec -- gh …) get it per team, and no value ever lands in the team repo.flowchart LR D["env/secrets.yaml<br/>key · description · url"] --> R{{resolve per team}} T["team value<br/>env set KEY"] --> R G["global value<br/>env set KEY --global"] --> R E["member's own environment<br/>(not what a teamai env.sh exported)"] --> R R --> M["${VAR} in MCP servers"] R --> X["env exec -- <cli>"] R --> N["missing → kept MCP entry +<br/>'Run teamai env set KEY'"]Plus: pull/doctor/
mcp list/env listname a missing secret and the command that fixes it (doctor as a note); a pull that can't find a declared secret keeps the MCP entry an earlier pull wrote; the session-start hook tells the agent which secrets exist and to useenv exec; skills forbid agents to ask for, pass or print a value.Design
docs/designs/team-secrets.md(+ zh-CN). Plan and conflicts with the code: #879.Type of Change
env.yamlvariable. A member who relied onexportgets a pull notice with the fix (teamai env set KEY).Test Plan
npx tsc --noEmitpassesnpm run lintpassesnpx vitest runpasses (351 files, 5993 passed, 1 skipped, head23ecd4b4, then feat(mcp): keep project MCP configs with resolved tokens out of git (#882) #886's last fix merged infa4c3d9ewith its test files re-run;origin/mainmerged, including fix(env,hooks,mcp,status): name the entries that are not delivered (#822) #851, fix(pull): keep a skill, rule or agent you edited instead of overwriting it (#822) #865, fix(init): seed a custom agent's configured root dir on regular init, not just self-mode (#867) #873, fix(tests): prevent model tests from overwriting Claude config (P1) #890's test isolation, fix(models): key team values by repo identity, migrating legacy slug names (#894) #895, fix(dry-run): thread { dryRun } into the queue lock, so a preview stops creating <home>/.teamai/locks/ #896, fix(test): keep CI validate from timing out on the usage lock and an unborn HEAD #897, fix(dry-run): stop dry-run and read-only commands persisting config migrations (#893) #901 and fix(dry-run): let --dry-run reach recall maintenance and recall promote (#900) #903)npm run test:e2e: 386 passed, 26 skipped (new:env-exec-signals, including a real pty).Real CLI (built
dist/index.js, sandbox HOME, local bare team repo behind a synthetic https URL, build ofa2745397; later rounds re-run by their unit tests):main)~/work/apienv.yaml~/work/api;env exec -- probegives the samepull --silentwithout the export keeps it;env execpasses his token⚠ github: GITHUB_TOKEN is not set. Run \teamai env set GITHUB_TOKEN` (https://github.com/settings/tokens).`GITLAB_HOST, different exportenv set GITLAB_HOST→ MCP,env execand a new shell use his; another team's export doesn't leak inNo fixture token appears in any teamai output, in
debug.log, or ingit log -p --allof the team repo.Not verified: a headless agent run (
claude -p) showing the session-start line reaches the agent (no authenticated host in the sandbox). Other providers and agents are left to CI.Related Issues
Closes #879
Closes #875
Includes #886 (#882, project MCP configs with resolved tokens kept out of git via
.git/info/exclude), merged inbde17ab5,2cf842dc,0d9f7fa7,2f39c372,e290adba,1392f783,38025a62,2a219f6f,7d2463f2,df1970a1,a2745397,b988bd27,173c7b3e,f8688bde,d051823f,f466dd07,83b7d12b,23ecd4b4andfa4c3d9e(exclusion before any write; removed again once a file is proven clean, judged by the manifest from before the command; no unlocked writes; one withholding path for tracked files) because this PR now writes members' tokens into project MCP configs. Once #886 lands onmain, its commits drop out of this diff.Related: #876 / #878 (shell-profile env block; merge order only), #881 / #885, #892, #893, #894 (found on the way, fixed or tracked separately).
Notes for Reviewers
Door: two-way for the code. Near one-way for members: once they move tokens into
env set, reverting leaves those values unused, and the variable-precedence change is what their MCP servers already follow.Blast Radius: env + MCP. Every team with
env.yamlvariables sees the new precedence in MCP servers; teams withoutenv/secrets.yamlsee no other change.env execcan read them. This keeps secrets out of git, not away from the member's machine or agent.env.yamlresolves as the secret; its repo value leavesenv.sh, the env backup and every listing. Teams must rotate any token ever committed.repo:claim inteamai.yaml; secret values deliberately are not: they are keyed by the URL the member configured, so a copied repo claiming another team'srepo:gets none of that team's secrets.0600at open time; an MCP config that receives a resolved value is written0600.env.yamlvariable in any case (token=TOKEN), so the repo value oftokenis ignored likeTOKEN's;env set/unset/add(variables and--secret)/remove, stored values (every case-alias replaced or removed), the member-environment check, MCP's variable table,${token}placeholders,mcp listand the missing-secret notice all compare names in any case there.9776a0c3, both the JSON and the Codex writers use a temp file andrename, which replaces the link), so no earlier pull can have put a value in the file it links to. Only this branch's own intermediate builds followed the link for Codex;0cc3fd5erestored the replace.toolPathsmappings read from the team repo's history, noted servers on a rebuilt record); what remains is listed in feat(mcp): keep project MCP configs with resolved tokens out of git (#882) #886's body.secrets.yamlfails two doctor checks (the shell-profile one is fix(env): keep the user scope's env block when a project pulls #878's area);env remove --secret;env exechonours the global--dry-run;env execforwards SIGINT/SIGQUIT only when teamai isn't the terminal's foreground group (a terminal Ctrl-C already reaches the command; forwarding it too made tools like terraform force-quit); on Windows Ctrl-C is never forwarded, since the console already delivers it andkill('SIGINT')there hard-kills. Known limit: while teamai is the terminal's foreground group, a SIGINT/SIGQUIT sent to teamai's PID alone (kill -INT <pid>from another shell) isn't passed on, because Node can't tell a terminal Ctrl-C from a direct signal and forwarding would double the Ctrl-C; send SIGTERM (forwarded) or signal the command's PID. Documented inteam-secrets.md. A shell that sourced anenv.shwritten before the provenance marker existed can still pass that value until the file is rewritten.