Skip to content

[bug] Cross-project gaps left after #748: dashboard events, pull on an unreadable project config, unreported scopes #752

Description

@SaulMoro

Follow-up to #748. Its PR fixes the Stop nudge and scopes skill usage per scope. What it left out:

machine
├─ hook handlers read config from the process cwd, not the dispatcher's      5
├─ teamai pull in a project with an unreadable config → user scope,           2
│    or a legacy .teamai/ behind a broken partition (another team)
├─ ~/.teamai/dashboard/reported-*.json   one snapshot set for every scope     1
│    report filter: cwd string prefix (no realpath, Copilot has no cwd)
│    user-scope report: excludeProjectRoots is always []
├─ ~/.teamai/votes/<user>.yaml           one file for every scope             6
├─ <dataHome>/usage.jsonl of a scope that never reports → grows forever       3
└─ stats pushed before #748 → may already list other projects' skills         4

Order: 5 first (no overlap with #758), then 2 → 1+4 → 6 → 3 once #758 merges. One PR each; the last one closes this issue.

  1. Dashboard reports per scope. events.jsonl is not truncated after a report (only compactEvents shrinks it, at 5,000 lines), so the fix(usage): scope skill-usage events by cwd so one project's usage never reaches another team's stats #750 loss does not apply to it. The cross-scope leak is the delta snapshots: reported-interventions.json, reported-prompt-tokens.json and reported-daily-sessions.json are one set per machine, keyed by sessionId (team-push.ts). A user-scope pull reports project P's session and marks it done; P's own pull then sends nothing for it.

    Fix: keep events.jsonl machine-wide (the teamai dashboard UI, stats --by-repo, session save and the contribute Stop check read across scopes, and data-directory-layout.md documents it as a machine-level singleton). Instead:

    • stamp each event with its scope's <dataHome> key at write time; the dispatcher knows the scope even for Copilot, which withholds cwd;
    • filterEventsByScope filters by that key. Events written before the change: a project takes those whose realpath'd cwd is under its root; the user scope never reports them;
    • move the reported-*.json snapshots to <dataHome>/dashboard/. A project scope seeds its snapshot from the shared one on first use, so nothing already reported is sent again;
    • remove the dead excludeProjectRoots path in pull.ts.
  2. pull with an unreadable project config resolves detectProjectConfig() ?? user. Two ways it picks the wrong config:

    • no other project config: it processes and reports the user scope;
    • a broken partition with a legacy <workspace>/.teamai/config.yaml behind it: detectProjectConfig() skips the partition and returns the legacy config, which may name another team. The session-start pull hook runs pull() without the config gate (src/hook-handlers.ts), so every session start syncs that team's skills, rules and docs into the project. Seen with the real CLI while verifying fix(usage): discard pre-upgrade usage and stop falling back past an unreadable config (#748) #758: reports-wt/ and learnings-wt/ were created in the legacy .teamai/.

    The hooks and usage already stop at any unreadable config (resolveConfigForDir, which since fix(usage): discard pre-upgrade usage and stop falling back past an unreadable config (#748) #758 also refuses the lower-priority fallback); pull should do the same. When findUnreadableProjectConfig() reports a problem:

    • teamai pull syncs no scope, prints the problem with BROKEN_CONFIG_ADVICE, and exits 1;
    • the session-start pull hook skips silently, and so do its package hint (pkg-hint.ts has the same fallback) and agent-root seeding.

    The hint must not point at teamai doctor: resolveDoctorContext is detectProjectConfig() ?? loadLocalConfig() without the unreadable sink, so doctor diagnoses whichever config it falls back to.

  3. Unbounded growth in scopes that never report (http, usageReport: false, or a remote that rejects every push). Dropping events at write time would empty teamai stats for them, since the local file is their only source. Fix: cap usage.jsonl (keep the newest 5,000 events) inside pull(), under the scope's sync lock and after the report's truncate. A cap applied by a hook could run between the report's read and truncateUsageAfterReport, which removes the first N lines, and would delete events never reported (the fix(usage): scope skill-usage events by cwd so one project's usage never reaches another team's stats #750 failure).

  4. Retroactive cleanup. Old events carry no cwd, so nothing can tell which project a pushed skill came from. Document how to remove a skill entry from stats/<user>.yaml on teamai-reports by hand (usage guide, both languages). No command.

  5. Handlers resolve their config from the process cwd, not from the dispatcher. hook-dispatch resolves the scope from the payload cwd (resolveConfigForDir), then chdirs there so handlers agree. Three handlers read their config again through autoDetectInit() on the process cwd: the correction keywords (teamCorrectionKeywords), votes-sync, and webhook-dispatch (loadWebhookConfig). The contribute hint is not affected: it already resolves from the payload cwd (fix(skills): one share gate, actionable refusals, and a louder stub deploy #747). When chdir fails, those three follow whichever directory the host spawned the hook in:

    payload cwd: deleted worktree   → dispatcher: user scope
    process cwd: project A          → keywords, votes, webhook: project A
    

    Fix: pass the config the dispatcher resolved to every handler (execute(stdin, tool, config)), and drop autoDetectInit() from them. A chdir(HOME) fallback was considered and rejected: it misses a HOME that is itself a git repo set up as a teamai project. Regression test: a deleted cwd with the hook process inside project A, and a Stop hook whose keywords, votes and webhook must follow the user scope.

  6. Votes are machine-level. Every scope records into ~/.teamai/votes/<user>.yaml, and whichever scope syncs next pushes the pending deltas to its team (votes-sync, and the vote staging in team-push.ts). It is the same shape as the old shared usage.jsonl. Fix: votes per scope (<dataHome>/votes/, and ~/.teamai/user-votes/ for the user scope, mirroring user-usage.jsonl). Pending votes left in the shared directory name no project and are not pushed.

Out of scope.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions