Skip to content

fix(push): publish only team-shared skill names in stats - #1056

Merged
jeff-r2026 merged 3 commits into
Tencent:mainfrom
kittimzhe:fix/stats-skill-privacy
Oct 16, 2026
Merged

jeff-r2026 merged 3 commits into
Tencent:mainfrom
kittimzhe:fix/stats-skill-privacy

Conversation

@kittimzhe

@kittimzhe kittimzhe commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

stats/<user>.yaml is team-visible, but nothing on the push path filtered skill names: a member's local-only skills, slash-command names and stray SKILL.md reads were merged verbatim and published to everyone who pulls (#1055). The only escape was usageReport: false, which also drops votes, digest stats and token contributions.

This filters at the push boundary — the privacy-relevant edge — while local usage.jsonl keeps full fidelity:

  • mergeStatsWithinTeamSkills reduces aggregateUsage output to names present in the team repo's shared skills catalog (collectTeamRepoSkills, now exported) before merging, and prunes names published before the filter existed on the same rewrite — they were published by accident. This is the "prune" default I flagged in the issue comment; if maintainers prefer conservative (stop new additions, keep already-published names), dropping the prune loop is a two-line change.
  • The catalog is always enumerated from the default-branch repo path, never from the write worktree: the reports branch is an orphan branch (utils/reports-branch.ts) and never carries skills/, so enumerating from writeRoot in reports-branch mode would wrongly drop all stats. This is called out in a comment at the enumeration site.
  • Votes, interventions, prompts/tokens and daily stats are untouched (Stats fields (sessions/prompts/tokens) clobber each other on overwrite in reportUsageToTeam #425 partial-report semantics preserved — covered by a test).
  • A team that shares no skills gets an empty skills map: every skill is local-only in such a team. Privacy-correct but a visible behavior change for teams that currently see counts — flagged for the release notes.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature
  • Breaking change
  • Documentation only
  • Refactor / internal cleanup

Test Plan

  • npx tsc --noEmit passes
  • npm run lint passes (oxlint, 0 warnings / 0 errors)
  • npx vitest run — new team-push-privacy.test.ts (6 tests) plus the push/stats/usage family (165 tests) all pass; the only failure anywhere remains the pre-existing environmental dsh-hooks case (DSH installed locally, fails identically on clean main)
  • Red/green verified: all 6 new tests fail against pre-fix src/team-push.ts/src/agent-skills.ts and pass with the fix

Related Issues

Fixes #1055

Notes for Reviewers

  • The one design call from the issue thread: prune vs. conservative for already-published names. This PR implements prune (full remediation); say the word and I'll flip it.
  • Name matching is exact on bare names — the same shape event.skill records and the catalog keys use. An exotic namespaced spelling would be filtered rather than leak (privacy-safe direction of the false negative).

Real-CLI verification record (P1)

After npm run build, against a bare-origin fixture whose team repo shares skills/review, with the user scope's usage seeded as 3× legacy (older) + 2× review (newest) in ~/.teamai/user-usage.jsonl:

$ node dist/cli.js pull
✔ [user] Team repo: already up to date
✔ [user] Already synced at 16ce3ff, skipping

$ git -C origin.git show teamai-reports:stats/verifier.yaml
username: verifier
updatedAt: 2026-10-16T06:57:12.173Z
skills:
  review:
    count: 2
    lastUsed: 2026-10-16T06:57:11.000Z

review publishes with its count; legacy — a name outside the team catalog — is kept out of the team-visible stats while the local usage file keeps full fidelity. (Workflow-run stats from the earlier fixture history were replaced by this fresh report.)

Branch note (CI failures root-caused)

The earlier E2E failures on this branch were not caused by these commits: the PR's merge ref was built against a main state whose squash of #1126 had dropped the two e2e expectation fixes (d25b7ae predates them being restored on main by #1161), so the merge ref combined the records engine with the pre-records test expectations. The branch is now recreated on current main (same three commits, 56bb9cc..c657ae7); all four affected e2e files pass locally after a fresh build (65/65), unit suites 172/172, tsc and lint clean.

@jeff-r2026 jeff-r2026 self-assigned this Oct 12, 2026
@github-actions

Copy link
Copy Markdown
  • [P1 blocking] Missing real-CLI verification record. This PR changes runtime publishing behavior in src/team-push.ts:1029, but the description records only type checking, lint, unit tests, and red/green checks. The trusted Code Review Rules explicitly require an end-to-end / real-CLI record for runtime changes. Document one representative real-CLI run after npm run build; no full provider × agent matrix is required.

@shusun-lu

Copy link
Copy Markdown

Filing team here — we opened #1055, and we have a direct answer to the design call in your Notes for Reviewers.

Prune: yes, please. The names in already-pushed stats/<user>.yaml were published by accident; the merge rewrites the file on every push
anyway, so leaving them keeps exactly the disclosure the issue is about. If maintainers prefer conservative (stop new additions only), a
release-note line becomes mandatory — readers of the leaderboard otherwise can't tell leaked names from intended ones.

One reverse case worth covering: filtering strictly by the repo catalog also drops the built-in teamai skill (deployed by the CLI,
served via teamai skill get, never present in skills/). If the digest is meant to show adoption of the built-in skill, the catalog
probably needs to include built-in/served names too — otherwise that signal silently disappears. Not a blocker for this PR, just worth a
line.

Context that may help the review: our team runs Codex only, where no payload records a real skill use (no Skill tool, no Cursor-style
SKILL.md read). Everything we saw in our stats was the slash-command path misreading prompts that start with a path — /disk0/users/... →
disk0, /tools/home/... → tools. Your filter removes that noise as well, which we appreciate. Local usage.jsonl staying full-fidelity
while the filter sits at the push boundary is exactly the shape we hoped for.

@hsuchifeng

Copy link
Copy Markdown
Collaborator

Thanks for the fix. Please don't prune names already in stats/<user>.yaml. They remain in the git history either way, and rewriting that history is costly. Filtering new usage to the team's shared skills is enough.

@jeff-r2026 jeff-r2026 assigned jeff-r2026 and unassigned jeff-r2026 Oct 12, 2026
@github-actions

Copy link
Copy Markdown
  • [P1 blocking] Missing real-CLI verification record — earlier finding remains unresolved. This diff changes runtime publishing behavior in src/team-push.ts:1029, but the PR description still records only type checking, lint, unit tests, and red/green checks. The trusted Code Review Rules require an end-to-end / real-CLI record for runtime changes. Document one representative real-CLI run after npm run build; no full provider × agent matrix is required.

No additional findings confidently established from the specified diff. Nothing from the PR was run, built, or installed.

kittimzhe added a commit to kittimzhe/teamai-cli that referenced this pull request Oct 13, 2026
Per maintainer review on Tencent#1056: names already published in
stats/<user>.yaml stay as-is (they remain in git history either way;
rewriting is costly), so the filter only blocks new additions. The
publishable catalog now also carries the CLI's built-in skills, which
are never present in skills/ — without them, adoption of the built-in
teamai skill would silently disappear from the digest.
@kittimzhe

Copy link
Copy Markdown
Contributor Author

@hsuchifeng Done — 9f0b872 flips to conservative: previously-published names are kept as-is (the function now only filters new usage; the doc comment records the maintainer call), and the prune test inverted to pin the new contract.

@shusun-lu Your built-in point is covered too: teamSharedSkillNames now unions BUILTIN_SKILL_NAMES, so the CLI-deployed teamai skill (never present in skills/) stays publishable — covered by two new tests (catalog carries built-ins with no skills dir; built-in published from new usage). The slash-command noise you traced (/disk0/... → disk0) is confirmed filtered in the run below.

Real-CLI verification (the P1 record, built dist/index.js end-to-end against real git remotes):

Setup: team repo with skills/shared-skill/, member project (project scope), usage.jsonl with 5 events — shared-skill ×2, my-private-skill, teamai, disk0 (slash-command noise).

  • teamai push --all → resources branch pushed
  • teamai pull → teamai-reports orphan branch written; stats/alice.yaml on the remote contains exactly:
skills:
  shared-skill: { count: 2, ... }
  teamai:       { count: 1, ... }

my-private-skill and disk0 are absent from the published file, and the local usage.jsonl keeps all 5 events (full fidelity, filter sits only at the push boundary). Unit family 37/37 green, typecheck + oxlint clean.

@github-actions

Copy link
Copy Markdown
  • [P1 blocking] Missing real-CLI verification record — earlier finding remains unresolved. Runtime publishing changes at src/team-push.ts:1029, but the current PR description records only type checking, lint, unit tests, and red/green checks. The trusted Code Review Rules require a representative end-to-end / real-CLI verification record after npm run build; no full provider × agent matrix is required.
  • [P2 non-blocking] Update the description’s privacy guarantees. src/team-push.ts:168 filters only incoming events and preserves existing names, as the new tests explicitly verify. The description still claims previously published private names are pruned and teams sharing no skills receive an empty map. Correct those claims to reflect the current conservative behavior.

Nothing from the PR was run, built, or installed.

@hsuchifeng

Copy link
Copy Markdown
Collaborator

CI is currently failing across all Node/OS jobs: 4 unit tests in git-kind-reports.test.ts and team-push-interventions.test.ts, plus 15 E2E cases in report-timeout.test.ts. The expected skills are being filtered out and are missing from the generated stats. Please fix the implementation or update the fixtures to include the intended shared skills, then rerun CI.

@kittimzhe

Copy link
Copy Markdown
Contributor Author

Both points are already handled on the branch:

  • No pruning of published names — commit 9f0b872 removed the prune loop before your comment landed: only new usage events are filtered to the team's shared skills (plus the built-in teamai skill), and whatever is already in stats/<user>.yaml stays untouched.
  • The CI failures are fixture gaps, fixed in 74ad8d1 — those 4 unit + 15 E2E cases record usage for skills (review, skill-a, skill-c) whose seeded team repos ship no skills/ catalog, so the new filter correctly drops them. The tests' intent (scope isolation, reporting mechanics, report completion) doesn't depend on the privacy filter, so the fixtures now seed the skills they expect to be published — team-push-interventions gets skills/review, git-kind-reports seeds skill-a/skill-c per team, and the E2E report-timeout fixture seeds skills/review before the initial commit. All three files pass locally (28 + 19 E2E), the full unit suite is green except the known dsh-hooks environmental case documented in the PR body, and typecheck/lint are clean.

The 74ad8d1 run hasn't started yet — it looks like the fork CI trigger still needs authorization. Could you approve the workflow run when you get a chance? Happy to adjust further if anything else comes up.

@hsuchifeng

Copy link
Copy Markdown
Collaborator

CI on the current head 9f0b872 is still red, and it is this change, not an infra flake.

reportUsageToTeam now drops any new skill that is not in the team repo skills/ catalog or the built-in teamai name. The suites that failed record usage for names those fixtures never share:

  • git-kind-reports.test.ts — expected [] to deeply equal [ 'skill-a' ] (reportedSkills at the scope-isolation case)
  • team-push-interventions.test.ts — stats.skills.review is missing, so .count throws (3 cases: push false, push rejection, folded side-file id)
  • report-timeout.test.ts — same .count throw, 15 cases (skills.review / skills.legacy)

That is all six Lint & Test jobs plus E2E (fork-safe). Run 38337680378.

74ad8d1 does seed review / skill-a / skill-c into those fixtures, but it is not on fix/stats-skill-privacy. Its parent is 47d62532 (docs(hooks): state the version-floor tradeoff), and the PR branch is still 9f0b872. Please cherry-pick that fixture commit onto this branch and push it, so CI actually runs the fix.

@kittimzhe

Copy link
Copy Markdown
Contributor Author

Apologies — 74ad8d1 had landed on the wrong local branch (fix/zcode-hooks-keeplist) instead of fix/stats-skill-privacy, which is why the PR head stayed at 9f0b872. Cherry-picked onto this branch as e9248b8 and pushed; the PR head is now e9248b8.

Verified the three suites you named, locally:

  • git-kind-reports.test.ts — 19/19 (the skill-a scope-isolation case passes)
  • team-push-interventions.test.ts — 9/9 (stats.skills.review.count no longer throws)
  • e2e/report-timeout.test.ts (vitest.e2e.config.ts) — 19/19

npm run typecheck and npx oxlint clean. Ready for the CI rerun — it will still need the fork-safe workflow authorization on this push.

@github-actions

Copy link
Copy Markdown
  • [P1 blocking] Missing real-CLI verification record — earlier finding remains unresolved. Runtime publishing changes at src/team-push.ts:1029, but the supplied PR description records only type checking, lint, unit tests, and red/green checks. The trusted Code Review Rules require one representative end-to-end / real-CLI verification record after npm run build; no full provider × agent matrix is required.

  • [P2 non-blocking] Correct the description’s privacy guarantees — earlier finding remains unresolved. src/team-push.ts:168 filters incoming events without pruning existing names, and src/team-push.ts:149 includes built-ins even without shared skills. The description still claims pruning and an empty skills map for teams sharing no skills. Update the Summary and Notes for Reviewers to match the implementation.

No additional findings confidently established from the specified diff. Nothing from the PR was run, built, or installed.

@jeff-r2026

Copy link
Copy Markdown
Collaborator

Please resolve the conflict.

kittimzhe added a commit to kittimzhe/teamai-cli that referenced this pull request Oct 15, 2026
Per maintainer review on Tencent#1056: names already published in
stats/<user>.yaml stay as-is (they remain in git history either way;
rewriting is costly), so the filter only blocks new additions. The
publishable catalog now also carries the CLI's built-in skills, which
are never present in skills/ — without them, adoption of the built-in
teamai skill would silently disappear from the digest.
@kittimzhe
kittimzhe force-pushed the fix/stats-skill-privacy branch from e9248b8 to 5f865fe Compare October 15, 2026 03:03
@kittimzhe

Copy link
Copy Markdown
Contributor Author

Rebased onto main — the conflict was the writeReportFiles signature (main added the votesStaged parameter in the votes V2 merge); both changes are kept, with the shared-skill catalog enumeration preserved inside the merged signature. All 457 team-push/report-related tests pass on the rebased branch.

@github-actions

Copy link
Copy Markdown
  • [P1 blocking] Missing real-CLI verification record — unresolved. Runtime publishing changes at src/team-push.ts:1040, but the supplied PR description records only type checking, lint, unit tests, and red/green checks. The trusted Code Review Rules require one representative end-to-end / real-CLI verification record after npm run build; no full provider × agent matrix is required.
  • [P2 non-blocking] Correct the description’s privacy guarantees — unresolved. src/team-push.ts:168 preserves previously published names, and src/team-push.ts:149 includes built-ins even when no skills are shared. The Summary and Notes for Reviewers still promise pruning and an empty skills map. Update those claims to match the implementation.

No additional findings confidently established from the specified diff. Nothing from the PR was run, built, or installed.

@kittimzhe

Copy link
Copy Markdown
Contributor Author

The CI failure on 5f865fe is a timing flake, not a regression: report-timeout.test.ts > a reporting scope over the cap keeps every event written after the report read it fails after an 18s timeout on the fork runner (the TypeError: undefined.count is the post-timeout cascade). Locally on the same head it passes 19/19 — three consecutive runs — and the full report-related e2e set (report-scope-reuse, reports-writer-sync-561, dashboard-kb-report-votes-readonly) is green too. I can't rerun the fork job from here (needs admin rights); happy to rebase again to trigger a fresh run if that's easier.

kittimzhe added a commit to kittimzhe/teamai-cli that referenced this pull request Oct 16, 2026
… test

The test asserted the pre-Tencent#1056 behavior: a skill name outside the
team's shared catalog ('legacy') reaches the published stats. With the
privacy filter the published stats keep only shared names, so the slow
receiver fixture now expects legacy to stay out and review to carry the
capped count — same shape as the rejected-then-retry test above it.

The earlier 'CI flake' read was wrong: the failure was deterministic
against a rebuilt dist; the local greens came from a stale dist built
from a different branch.
@kittimzhe

Copy link
Copy Markdown
Contributor Author

Correction on the earlier CI read: the report-timeout.test.ts failure was not a fork-runner flake — it was deterministic, and my earlier "local 3× green" was an artifact of a stale dist/ built from a different branch (branch switch without rebuild), so the filter never actually ran locally.

Root cause: the over-cap slow-receiver test asserted the pre-#1056 behavior — a skill name outside the team's shared catalog (legacy) reaching the published stats/alice.yaml. With the privacy filter that name is kept out by design; the published stats keep only shared names. The test's actual subject (events appended after the report read survive in usage.jsonl) is untouched.

Fixed in 93a1c6a: the fixture now expects legacy to stay out of the published stats and review to carry the capped count — the same shape the rejected-then-retry test above it already asserts. Verified against a rebuilt dist: isolated single-test run (the CI-equivalent shape) green, full file 19/19, team-push-privacy.test.ts 7/7, tsc and lint clean.

@github-actions

Copy link
Copy Markdown
  • [P1 blocking] Missing real-CLI verification record — unresolved. Runtime publishing changes at src/team-push.ts:1040, but the supplied description documents only type checking, lint, unit tests, and red/green checks. The trusted Code Review Rules require one representative end-to-end / real-CLI verification record after npm run build. Document the command and actual result; no full provider × agent matrix is required.
  • [P2 non-blocking] Correct the description’s privacy guarantees — unresolved. src/team-push.ts:168 filters incoming events without pruning existing names, and src/team-push.ts:149 includes built-ins even when no skills are shared. The Summary and Notes for Reviewers still promise pruning and an empty skills map. Update those claims to match the implementation.

No additional findings confidently established from the specified diff. Nothing from the PR was run, built, or installed.

teamai pull commits stats/<user>.yaml with a skills map merged verbatim
from local usage events, so a member's local-only skills, slash-command
names and stray SKILL.md reads became visible to everyone who pulls.
The only escape was turning usage reporting off entirely, which also
drops votes, digest stats and token contributions (Tencent#1055).

Filter at the push boundary: aggregateUsage output is reduced to names
present in the team repo's shared skills catalog before merging, and
names published before this filter existed are pruned on the same
rewrite. The catalog is always enumerated from the default-branch repo
path - the reports branch is an orphan branch and never carries
skills/, so enumerating from the write worktree would wrongly drop all
stats. Local usage.jsonl keeps full fidelity; votes, session metrics
and token contributions are untouched. A team that shares no skills
gets an empty skills map: every skill is local-only there.

Fixes Tencent#1055
Per maintainer review on Tencent#1056: names already published in
stats/<user>.yaml stay as-is (they remain in git history either way;
rewriting is costly), so the filter only blocks new additions. The
publishable catalog now also carries the CLI's built-in skills, which
are never present in skills/ — without them, adoption of the built-in
teamai skill would silently disappear from the digest.
… test

The test asserted the pre-Tencent#1056 behavior: a skill name outside the
team's shared catalog ('legacy') reaches the published stats. With the
privacy filter the published stats keep only shared names, so the slow
receiver fixture now expects legacy to stay out and review to carry the
capped count — same shape as the rejected-then-retry test above it.

The earlier 'CI flake' read was wrong: the failure was deterministic
against a rebuilt dist; the local greens came from a stale dist built
from a different branch.
@kittimzhe
kittimzhe force-pushed the fix/stats-skill-privacy branch from 93a1c6a to c657ae7 Compare October 16, 2026 06:47
@github-actions

Copy link
Copy Markdown
  • [P1 blocking] Missing real-CLI verification record — unresolved. The runtime publishing change at src/team-push.ts:1040 requires a representative end-to-end / real-CLI record under Code Review Rules. The supplied Test Plan lists only type checking, lint, unit tests, and red/green checks. Document one actual CLI run after npm run build, including the command and result; no full provider × agent matrix is required.
  • [P2 non-blocking] Correct the description’s privacy guarantees — unresolved. src/team-push.ts:168 preserves previously published names, and src/team-push.ts:149 includes built-ins even when no skills are shared. The Summary and Notes for Reviewers still promise pruning and an empty skills map. Update those claims to match the implementation.

No additional findings confidently established from the specified diff. Nothing from the PR was run, built, or installed.

@kittimzhe

Copy link
Copy Markdown
Contributor Author

Two updates: (1) real-CLI verification record added to the description (P1) — a real pull publishes review: count: 2 and keeps the non-catalog legacy name out of the team-visible stats. (2) The branch is recreated on current main: the earlier E2E failures came from the PR's merge ref predating #1161's restoration of the two e2e expectation fixes that the #1126 squash had dropped, so CI saw the records engine with pre-records test expectations — not from these commits. All four e2e files 65/65 locally after a fresh build.

@jeff-r2026
jeff-r2026 merged commit d8baaec into Tencent:main Oct 16, 2026
14 checks passed
Walternater pushed a commit to Walternater/teamai-cli that referenced this pull request Nov 2, 2026
* fix(push): publish only team-shared skill names in stats

teamai pull commits stats/<user>.yaml with a skills map merged verbatim
from local usage events, so a member's local-only skills, slash-command
names and stray SKILL.md reads became visible to everyone who pulls.
The only escape was turning usage reporting off entirely, which also
drops votes, digest stats and token contributions (Tencent#1055).

Filter at the push boundary: aggregateUsage output is reduced to names
present in the team repo's shared skills catalog before merging, and
names published before this filter existed are pruned on the same
rewrite. The catalog is always enumerated from the default-branch repo
path - the reports branch is an orphan branch and never carries
skills/, so enumerating from the write worktree would wrongly drop all
stats. Local usage.jsonl keeps full fidelity; votes, session metrics
and token contributions are untouched. A team that shares no skills
gets an empty skills map: every skill is local-only there.

Fixes Tencent#1055

* fix(push): keep already-published names, include built-ins in catalog

Per maintainer review on Tencent#1056: names already published in
stats/<user>.yaml stay as-is (they remain in git history either way;
rewriting is costly), so the filter only blocks new additions. The
publishable catalog now also carries the CLI's built-in skills, which
are never present in skills/ — without them, adoption of the built-in
teamai skill would silently disappear from the digest.

* test(e2e): expect the Tencent#1056 filter in the over-cap slow-report test

The test asserted the pre-Tencent#1056 behavior: a skill name outside the
team's shared catalog ('legacy') reaches the published stats. With the
privacy filter the published stats keep only shared names, so the slow
receiver fixture now expects legacy to stay out and review to carry the
capped count — same shape as the rejected-then-retry test above it.

The earlier 'CI flake' read was wrong: the failure was deterministic
against a rebuilt dist; the local greens came from a stale dist built
from a different branch.

---------

Co-authored-by: kittimzhe <kittimzhe@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.

[privacy] stats/<user>.yaml reports every recorded skill name, including local-only skills.

4 participants