Skip to content

feat: keep the resources teamai delivers in project scope out of git (#915) - #1000

Open
SaulMoro wants to merge 352 commits into
Tencent:mainfrom
SaulMoro:feat/915-git-exclude
Open

SaulMoro wants to merge 352 commits into
Tencent:mainfrom
SaulMoro:feat/915-git-exclude

Conversation

@SaulMoro

@SaulMoro SaulMoro commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

project-scope delivery
+ record each file teamai wrote and still owns
+ list those files in the clone's local info/exclude
+ combine live worktrees without hiding member files
+ move mixed content into per-member files where supported
+ report and clean up protection through doctor, dry run, and uninstall

Closes #915. The ownership fixes from #995 are merged and included. The pending live tool checks must be verified before merge.

sharing.gitExclude.enabled defaults on for new teams and off for existing ones. A member can override it with gitExcludeEnabled. Credentials stay protected regardless of that setting. Member lines in info/exclude stay intact. Tracked files remain visible and are never deleted by cleanup.

Claude and CodeBuddy MCP servers move to their tools' local scopes. Copilot instructions use an owned file. Codex dispatches project team hooks through its user hook configuration. OpenCode V2 receives instructions and MCP through the plugin; V1 retains its supported file layout. The docs mirror, built-in resources, HTTP deliveries, and workspace cache records are covered.

Evidence

  • Before: a no-config HTTP workspace exposed .teamai/.gitignore and its MCP records to git status; project uninstall deleted an edited managed MCP server. The new real-CLI cases failed on those behaviors. After: generated cache files are ignored, member files remain addable, and edited servers stay with a warning.

  • Workspace MCP cleanup: teamai uninstall --force in the current workspace reads its .teamai/managed-local-mcp.json, both without config and with user config. Invalid tool JSON preserves the records, names the file, and exits 1. Repairing the JSON and repeating the command removes the server and its plaintext token. The 10 MCP cases and 7 HTTP delivery cases pass with --retry=0.

  • Validation at 7567e274: npm run build, npx tsc --noEmit, and npm run lint pass. The complete npx vitest run passes 8,352 tests, with 19 skipped. A Git discovery ceiling at the dependency directory keeps the real-shell fixture from inheriting its parent checkout; assertions are unchanged.

  • Full real-CLI E2E: npm run test:e2e -- --retry=0 passes 983 tests across 124 files, with 27 tests in 3 files skipped. It exits 0 with no RPC timeout, taking 8 min 43 s locally with 4 workers. CI must confirm the new head.

  • The common-directory probe regression failed before caching and passes after it. Failed probes are still retried. The existing damaged-repository git-exclude regression passes unchanged.

  • After integrating main at 6f85cf85 (fix(hooks): keep one owner for main-checkout team hooks across worktrees #1003), validation at e5d505b8 passes build, typecheck, lint and the full unit suite (8,366 passed, 19 skipped). Six affected real-CLI E2E files pass all 69 tests with --retry=0, including all 30 shared-hook isolation cases plus Codex dispatch, self mode, worktree exclusion, and uninstall. The complete 983-test E2E run above predates this merge; only the affected files were rerun after it.

  • Validation at 63e2727c passes build, typecheck, lint and the full unit suite (8,377 passed, 19 skipped). Five real-CLI E2E files (local-agent-model-key-915, local-agent-teardown-915, local-agent-delivered-915, local-agent-mcp-local-915, git-exclude-uninstall-915) pass all 51 tests with --retry=0. 642e6c5a passed codex-team-hook-dispatcher-915. 69942c45 passed git-exclude-delivered-915, instruction-targets and opencode-versions-915. The later commits change only uninstall and HTTP-source removal. Each failure path below was confirmed failing before its fix:

    • Codex: a pull with a read-only ~/.codex/hooks.json removed the team hooks from the project file. An uninstall or uninstall --agent codex that could not update that file exited 0. One version also dropped the project from the dispatcher index, so the retry left the dispatcher in place.
    • HTTP source: source remove-http exited 0 in five cases: a models file that did not parse, a model record that did not parse, a workspace cache it could not delete, a skill it could not uninstall, and a read-only exclude file holding a deleted models file's line. Each time it dropped the record of what was left, which could include teamai's model key. When a skill copy could not be deleted, its cache was already gone, so the retry took the copy for the member's and left it. In a repository git could not read, it also left the cache .gitignore and a rule copy behind and dropped their records. An unreadable git-exclude.json was deleted.
    • Uninstall: when a rule could not be deleted, or a context file's block had lost its end marker, uninstall exited 0 and dropped the file's exclude line and records.
    • Unit tests: a delivered copy in a repository git cannot read was deleted; the OpenCode V2 plugin missed the root project's team context, MCP, and rules past a nested package's own .opencode/rules; and a local-agent workspace with an unreadable flag lost its lines when git could not read the repository.

#995 gaps closed here

  • The first pull after upgrading performs a full ownership sync, including when git exclude is off.
  • The next HTTP sync migrates older project MCP installs into the tool's current scope.
  • HTTP resource ownership is recorded per tool. Cleanup keeps member edits and files added inside delivered skills, and removes resources for the tools that received them.

E2E runner

#915 adds 14 E2E files, about 1000s of test time, so the fork-safe job grew from 14 to 24 minutes. Two test-infra changes, no runtime code:

  • yield-between-tests.ts (setup file): Vitest's worker sends each task update with a 60s RPC timeout and reads the reply only when its event loop turns. Back-to-back synchronous tests (spawnSync of the CLI) never let it turn, so a streak past 60s on a slow runner exits 1 with Timeout calling "onTaskUpdate" although every test passed. An afterEach that awaits setImmediate caps the streak at one test. Repro: three synchronous 25s tests exit 1 without it and 0 with it. On GitHub runners, this branch without it hit the timeout in 8 of 8 E2E jobs; with it, 0 of 8.
  • maxWorkers: 4 in CI (was 2): the ubuntu runner has 4 vCPUs. Two runs each on this branch: 2 workers took 24.1 and 25.6 min, 4 workers 12.5 and 14.5 min, all tests passing; the slowest test went from 22s to 33s, under the 60s timeout.

Runtime probe overhead

Successful gitCommonDir probes are reused during one CLI process. Failed probes are retried, and checkout locations must remain stable for that process. OpenCode is probed for its version only after confirming that the current plugin exists. Git-exclude path routing remains uncached so a repository that stops answering is reported immediately.

Two sequential runs per variant with --maxWorkers=1 on the same machine:

Real-CLI E2E file Before After Mean reduction
claude-mcp-local-915 44.67s, 47.06s 35.84s, 36.00s 22%
git-exclude-worktrees-915 63.59s, 64.16s 50.06s, 56.06s 17%

These are local measurements for two files, not a measured reduction of the whole CI job.

Deviations and known limits

  • Skills are listed per file so files a member adds inside a skill remain visible. Project uninstall rebuilds the HTTP block without that project, preserving other workspaces. The docs search whitelist uses !/docs/**. Local MCP ownership is keyed by tool and local-scope key.
  • In self mode, Claude servers stay in each checkout's .mcp.json, listed while teamai-only. Self-mode worktrees share one Claude key, which cannot hold different servers for each branch.
  • Outside self mode, tclaude detection remains per checkout. Worktrees sharing a Claude key can disagree when tclaude exists in only one checkout.
  • source remove-http leaves project MCP servers and preserves their records. Run teamai uninstall in the affected workspace, or use the backend's uninstall_mcp. Uninstall does not scan every unconfigured workspace.
  • The OpenCode V2 plugin uses the nearest .opencode holding teamai-context.md or teamai-mcp.json. Without one, it reads every .opencode/rules from the session up, outermost first, as OpenCode V1 globs teamai's rules entry. A member's nested rules then load beside the team's.
  • teamai deletes or takes over a project file only when git reports it untracked or no repository holds it. A repository git cannot read keeps the file, with a message naming git's error. This covers delivered copies, a created models file, Codex's project hook file and the legacy .codebuddy/.gitignore.
  • Codex team hooks move to the dispatcher only after its entries are written to ~/.codex/hooks.json. If uninstall cannot update that file or the dispatcher index, the project stays in the index and the data home is kept. The run exits 1, and the retry removes the dispatcher.
  • source remove-http may fail to uninstall a skill, rule or instruction, to read its model record, to clean a models file, or to delete a workspace cache. The same holds when it cannot write an exclude file holding its lines. It then keeps the records naming them and exits 1. A skill's cache goes only after its copies, so a retry can still tell teamai's files from the member's. While git cannot judge a copy or the cache .gitignore, nothing is removed for it, and its records stay. An unreadable git-exclude.json is kept. While installs remain, the disabled marker keeps the source config, which only the retry reads. The retry removes what is left, and a user-scope teamai uninstall then keeps the local agent's home.
  • A delivered skill, rule, agent, or docs file that uninstall cannot delete keeps its exclude line and the data home. So does an instruction file whose block stays, or a docs search whitelist uninstall cannot remove. The run exits 1, so the retry can find the file.
  • No-config uninstall retains protection for built-in skill files left on disk and names them: Kept <lines> in <file>, so git still ignores <paths>, which this uninstall leaves on disk.
  • Legacy HTTP prompt records remain single-tool until reinstall. Copilot and OpenCode HTTP prompt paths lack dedicated real-CLI coverage. Atomic Codex index writes lack crash-injection coverage.

Live checks pending

Before merge: Cursor must load an excluded nested rule; Copilot must apply the owned instructions without an attached file; Codex must run context and blocking hooks through the dispatcher; OpenCode V2 must load plugin context and a project-only MCP server. Fixtures and steps are prepared, but these live checks are not verified.

Conditional changes remain deferred: claude-internal and tclaude need a /memory or /context check proving rules/teamai-context.md loads; Qoder and Qoder CN need confirmation that settings.local.json supplies MCP configuration.

Merge Danger

Door: two-way. Disabling the option removes delivery blocks on the next pull. Uninstall removes protection after safe cleanup and keeps records for failed removals.

Blast Radius: project delivery. Existing teams opt in; the first pull after upgrade runs a full sync. The pending live tool checks must complete before merge.

With any plugin configured, OpenCode's first request for a directory waits
for it to npm-install @opencode-ai/plugin into its config dir. On CI that
fetch outlasted the 5 s session request. teamai's plugins import nothing
from it, so the fixture records it as installed, and a failed session
request now carries the server log.
…as an edit (Tencent#993)

judgeRemoval replaces removedCopyChanged with three outcomes: remove,
edited (changed since teamai delivered it, or on another checkout's
record), and notTeamais (no record, no team version). Pull's tombstone
cleanup and the rules sweep now say a member's own file is not teamai's
instead of claiming the member changed a teamai copy.
…eamai's (Tencent#993)

removeStaleAgentSiblings deleted every same-stem file with another
extension beside a rendered agent, a member's own included. It now goes
through judgeRemoval with the agent's origin and the checkout record, and
names a sibling it keeps. Built-in agents keep sweeping their own names.
…Tencent#993)

The mirror replaced a local directory at a team doc file's path, or a
local file at a team docs directory's path, and deleted the backup with
its staging directory. membersDocs now treats such an entry as the
member's unless the team history proves it teamai's (a file where the
team now has a directory, or a directory whose every file is a team
version), and copyDocs leaves a kept entry in place. A link is still
replaced without touching its target.
…e moves onto it (Tencent#915)

src/git-exclude.ts owns info/exclude files: sync (replace), ensure
(add-only gate with a per-path result), remove (owner or all, with a keep
predicate) and report. Owners are named [a-z0-9/_%-] with per-owner
markers; mcp-exclude keeps Tencent#886's markers, trimmed parsing and its
already-ignored rule, and ensureExcludedFromGit maps ensure's results to
GitExclusion unchanged. Lines route to the repository a path lands in
(submodule, nested clone, symlink target), use the on-disk spelling, NFC
only with core.precomposeunicode, and refuse line breaks and trailing
spaces. Each owner's exclude files live in a caller-supplied record;
stateHomeRecord keeps them in <stateHome>/git-exclude.json. git children
drop the repository variables a hook exports.

The re-including rule's source is now resolved from the toplevel, where
git names it, not from the file's directory: a rule in .claude/.gitignore
was named as .claude/rules/.claude/.gitignore.
…ng it (Tencent#993)

membersDocs skipped links, so the mirror still moved a member's link
aside and deleted it with the staging directory (rest of the Codex P1).
A link at a doc's path, or at an ancestor of one, is now the member's
unless the team has the same link there: it is kept, named and never
followed, and the docs under it are not delivered.
…t#993)

Node 22 on macOS failed on 9309e20 with one unhandled rejection from
src/__tests__/recall-attribution.test.ts (a votes-lock write outliving the
test's temp HOME); every test passed and Tencent#995 does not touch that code.
The fork cannot re-run jobs, so this empty commit re-triggers CI.
…ncent#915)

updateFileLocked read through readFileSafe, which turns every read error
into empty content: an info/exclude that exists but cannot be read was
rewritten with only teamai's block, dropping the member's lines, and the
ensure that gated a resolved MCP value reported success.

A read failure other than ENOENT/ENOTDIR now throws NotReadableError and
writes nothing. ensure returns notReadable {path, error, reason, fix};
sync and remove report write kind notReadable and keep the file in the
owner's record; ensureExcludedFromGit maps it to failed, so the secret is
withheld. A missing exclude file is still created.
…tExclude (Tencent#915)

sharing.gitExclude.enabled (default false; init writes true into a new
git-mode or single-repo teamai.yaml) and the partition override
gitExcludeEnabled resolve as override, team, false.

pull() carries one DeliveryRecorder for the project scope. Writers report
landed paths per writer id and say whether they succeeded or failed; the
skills handler is the first (through the pull's ledger, so the local
agent's ledger-less calls report nothing). After pullSources, still under
the partition sync lock and skipped for a contended scope, pull merges the
reports into the checkout record's gitExcludePaths (fast path add-only;
full sync replaces writer by writer, a failed writer keeps its previous
entries still on disk) and syncs teamai's delivered block in the clone's
info/exclude, or removes only that block when the flag is off. The list is
kept whatever the flag; awaitingFullSync and the complete-branch rebuild
carry it, and a record without it misses the fast path, so the first pull
after upgrading from 0.26.0 is a full sync. The delivered owner's exclude
files are recorded in the partition state (gitExcludeFiles).
…t#915)

Every writer of a project-scope pull now reports what it wrote, or
confirmed as teamai's, to the delivery recorder, and says whether it
delivered all it meant to:

- rules, one file per line (namespace subdirectories, flattened names,
  Copilot instructions), also from the fast path's rewrites;
- agents, plus a copy a tool holds for its model while the record still
  matches it;
- the built-in teamai skill and the teamai-recall rule and agent;
- the owned teamai-context files that hold teamai's blocks;
- the main checkout's .claude/settings.local.json while its hook manifest
  records team hooks there, and Copilot's .github/hooks/teamai.json while
  it holds teamai's entries, read from disk so an unresolved team hook
  set or Copilot's built-ins-installed skip still lists them;
- .claude/settings.local.json while the co-author record says teamai
  wrote the empty trailer and the file still holds it;
- source skills this pull landed, never the manifest's entries. No
  configured source is a successful empty result (the copies stay on
  disk, visible); a source left as it was keeps its previous lines.

A copy pull keeps for the member's edit is never reported, so it leaves
the block. Collisions and failed writes mark their writer failed.

The acceptance fixture (roles, a project, a source, recall, team hooks,
MCP with and without a resolved value, Copilot plus six agents, a Codex
copy in .agents/skills, a tracked SKILL.md with a new team file) checks
git status against an allowlist of paths later tickets move, after init,
pull and a session start, with the member's files visible and addable.
…not rewrite (Tencent#993)

An unrecorded server equal to today's team render was adopted into the
in-memory records, but with no file write the manifest was not saved, so
a later team removal or uninstall found nothing owned and left the server
installed. The reconcile now saves the manifest whenever this run changed it.
…all (Tencent#993)

A dry run now says it would record an unrecorded server that already holds
the team's render, in .mcp.json-style files and in Codex config, and still
writes nothing. The real-CLI cases add the dry run before the adoption and
an adoption followed by teamai uninstall; both failed before b62e503.
Hook adoption needs no change: reconcileHooks saves its records whenever
they differ from the previous ones, whether or not it wrote a file.
…mai owns (Tencent#915)

With sharing.gitExclude on, Copilot's project culture, shared-instructions and
recall blocks go to .github/instructions/teamai-context.instructions.md with
applyTo: "**", which the delivered git exclude block lists. The next pull
strips teamai's blocks from .github/copilot-instructions.md and deletes it only
when teamai created it and git does not track it. Turning the flag off moves
the blocks back on the next pull. Doctor checks the new file through the
existing instruction checks; the usage guide names the Copilot surfaces that
read no .github/instructions file and the chat.includeApplyingInstructions
dependency.
Tencent#993)

When the team deleted a doc file the member had replaced with a directory,
the prune walked into that directory and deleted its files, which match no
team version. A directory at a path where the team history had a doc file,
holding anything that is not a team version, is now kept whole and named.
…eep Codex's shared skills (Tencent#915)

When a delivered copy stops being delivered (role or project switch, a root
skill once roles are set, a team tombstone or removed rule, a source dropping
a skill), the removal pass now keeps a path the checkout's index tracks and
names it once per pull with the git rm -r hint, so a later pass that proves
the copy teamai's still leaves it. The inactive-namespace, stale-skill and
tombstone sweeps also visit Codex's .agents/skills/<name>, deleting a copy
only when judgeRemoval returns remove and naming an edited or member copy.
…ngs (Tencent#915)

In a single-repo team with sharing.gitExclude on, Claude Code's team hooks
go to each checkout's own .claude/settings.local.json, which the delivered
block lists, and the committed .claude/settings.json keeps only the
built-ins, so a fresh clone still has them and a pull leaves git status
clean. The per-checkout hook index records the team hooks of the one file
that holds them. Turned off, the next pull moves them back into the tracked
settings. init . syncs the delivered block after its own writers, since it
ends without a pull. hooks remove and uninstall also clear settings.local.json.
…ut vitest's RPC

Vitest's worker sends each task update with a 60s RPC timeout and reads the
reply only when its event loop turns. Back-to-back synchronous tests never
let it turn, so on a slow runner the run exits 1 with Timeout calling
"onTaskUpdate" although every test passed.
Resolve the docs reorganization (Tencent#1009): carry the Tencent#993 edits of
usage-guide into the docs/guide pages that now hold each paragraph,
apply the CI E2E setup change to docs/dev/ci-e2e-setup.md, and take
main's deletion of the zh-CN team-secrets design.
The Auto-sync section moved to member-guide in the docs reorganization,
so the in-page anchors Tencent#993 added no longer resolve.
Brings in origin/main's docs reorganization (Tencent#1009) through Tencent#995's
merge. The Tencent#915 edits of usage-guide move to the docs/guide pages that
now hold each paragraph; the new "Keeping Delivered Files Out of Git"
section goes to the end of member-guide, after co-author attribution as
before, and in-page links to sections on other pages now name the page.
GitHub's ubuntu runner has 4 vCPUs. On this branch, 4 workers took the
fork-safe E2E job from about 24 to about 15 minutes (two runs of each
setting); the slowest test went from 22s to 33s, under the 60s timeout.
Tencent#995 landed on main as d89c51e, a squash with the same content as its
branch tip b0c36d2, which this branch already contains; the merge
takes main as a parent and changes no file.
@SaulMoro SaulMoro changed the title [After #995] feat: keep the resources teamai delivers in project scope out of git (#915) feat: keep the resources teamai delivers in project scope out of git (#915) Oct 9, 2026
@SaulMoro
SaulMoro marked this pull request as ready for review October 9, 2026 08:15
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
  • [P1 blocking] src/opencode-hooks.ts:227 treats any nearest .opencode/rules directory as a TeamAI project marker. In a monorepo initialized at the root, opening a nested package with its own user-created .opencode/rules makes the V2 plugin stop there, so it silently omits the root project’s teamai-context.md and teamai-mcp.json. Continue searching unless a TeamAI-specific marker exists.
  • [P3 nit] src/local-agent.ts:2529 preserves an unreadable workspace’s existing exclusions only when report() successfully places and recognizes each path as listed. If the team config is unreadable and Git also fails to inspect a protected path, report() returns gitFailed without listed; the following sync() can remove that path’s existing exclusion. Preserve the current block or force add-only behavior when this report has placement failures.

The five findings from the earlier pass are resolved in the current diff. The PR description includes sufficient real-CLI testing evidence for this runtime-changing PR.

…de lines

The OpenCode V2 plugin stopped at the nearest .opencode with a rules
directory, so a member's own .opencode/rules in a nested package hid the
teamai project above it. It now prefers a .opencode holding a teamai file
and falls back to the nearest rules directory only when none does.

A local-agent workspace whose flag cannot be read passed only the paths
git could place to the sync, which then dropped the lines of the others.
Those paths now reach the sync, which keeps every line while git fails.
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
  • [P1 blocking] src/opencode-hooks.ts:236 still falls back to the nearest arbitrary .opencode/rules directory when no TeamAI-specific marker exists. In a rules-only TeamAI project, opening a nested package with member-owned .opencode/rules selects that directory and omits the root project’s TeamAI rules. The prior OpenCode finding is therefore only partially resolved.
  • [P1 blocking] src/hooks.ts:1031 treats gitTracks() returning unknown as equivalent to untracked. If Git cannot inspect a tracked .codex/hooks.json, placeCodexTeamHooks() may classify it as TeamAI-only and write project hooks into the tracked file instead of using the dispatcher. Unknown tracking status must take the conservative/foreign path.
  • [P1 blocking] src/hooks.ts:1080 commits the dispatcher index and returns true before reconcileCodexDispatchers() installs the corresponding entry in ~/.codex/hooks.json. If that later write fails, the same reconciliation removes the team hooks from the project file, leaving Codex with no delivery path for those hooks. The transition must retain the project-file hooks unless dispatcher installation succeeds.
  • [P1 blocking] src/uninstall.ts:2116 considers only hook and OpenCode cleanup failures when deciding whether delivered exclusion lines and ownership state must remain. If deleting a delivered skill, rule, agent, or docs file fails—for example because its parent directory is not writable—the command removes its exclusion and deletes the state anyway, exposing the leftover file to Git and making the advertised retry unable to identify it.
  • [P2 non-blocking] src/local-agent.ts:3043 deletes the legacy .codebuddy/.gitignore whenever Git returns anything other than tracked. If git ls-files fails, a tracked file containing those two lines is deleted despite the function’s stated rule that committed files stay; preserve it unless Git explicitly reports untracked.

The other five first-pass findings and the local-agent preservation finding are resolved. The PR description contains sufficient real-CLI testing evidence for this runtime-changing PR.

…lete

- OpenCode V2 plugin: with no teamai file above the session, read every
  .opencode/rules up, outermost first, as V1's glob does, so a nested
  package's own rules no longer hide a rules-only team's.
- Codex: a hook file git cannot judge inside a repository counts as not
  teamai's, and the project file keeps the team hooks unless the
  dispatcher entries were written to ~/.codex/hooks.json.
- Uninstall: a delivered skill, rule, agent or docs file that could not be
  deleted keeps its exclude line and the data home, and marks the run
  incomplete so the retry finds it.
- Local agent: the legacy .codebuddy/.gitignore goes only when git says it
  is untracked.
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
  • [P1 blocking] src/resources/delivered-copies.ts:555 treats every gitTracks() result except tracked as safe to delete. If git ls-files fails inside a repository, pull/uninstall can delete a tracked skill, rule, or agent copy. Preserve the path on unknown; only confirmed untracked or outside-repository paths may be removed.
  • [P1 blocking] src/local-agent.ts:3250 similarly deletes a TeamAI-created models file when Git returns unknown. If the member committed that file and Git is temporarily unable to inspect the repository, model cleanup deletes a tracked file despite the stated tracked-file guarantee.
  • [P1 blocking] src/uninstall.ts:1960 only warns when an owned instruction file cannot be cleaned. The uninstall then removes its exclusion and ownership state and exits successfully, leaving the TeamAI instruction active and potentially visible to Git with no retry metadata. Add the file to filesLeft when cleanup throws or reports an undelimited block. The same issue exists for a failed docs-search-whitelist removal at src/uninstall.ts:2114.
  • [P2 non-blocking] src/hooks.ts:998 deletes the project from codex-team-hooks.json before updating ~/.codex/hooks.json. If the latter write fails, the stale dispatcher remains, and retries short-circuit at runsFromCodexDispatcher() because the index entry is already gone. Reconcile the user hook file first or restore the index entry on failure.

The previously reported findings are resolved in the current diff. The PR description contains sufficient real-CLI testing evidence for this runtime-changing PR.

…ecords on failed cleanup

- gitUntracked: a file git says it does not track, or one in no
  repository; a repository git cannot answer for is neither. Delivered
  copies, a created models file and Codex's project hook file use it, so
  a git failure no longer lets teamai delete or take over a tracked file.
- Uninstall: an instruction file whose block stays, or a docs search
  whitelist it cannot remove, keeps its exclude line and the data home,
  and the run exits 1.
- Stopping Codex dispatch restores the project's index entry when
  ~/.codex/hooks.json cannot be updated, so the retry removes its entries.
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
  • [P1 blocking] src/uninstall.ts:2465 swallows failure from stopCodexTeamHookDispatch() without marking uninstall incomplete. If ~/.codex/hooks.json is read-only—or the dispatcher index lock times out—the command exits successfully even though the project remains in codex-team-hooks.json and its team hooks continue running. Set a nonzero exit status and preserve retry state whenever dispatcher removal fails.

The previously reported findings are resolved in the current diff. The PR description contains sufficient real-CLI testing evidence for this runtime-changing PR.

…lete

Uninstall stops the project's Codex team-hook dispatch before the data
home goes. When ~/.codex/hooks.json or the dispatcher index cannot be
updated, the dispatcher counts as a hook left in place: the records stay
for the retry and the command exits 1. Excluding only Codex from the
project does the same.
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
  • [P1 blocking] src/local-agent.ts:3159 swallows model cleanup failures, after which finishAgentHookTeardown() deletes the model manifest. If models.json is temporarily invalid or unwritable, source remove-http exits successfully while leaving TeamAI’s model and token behind; retries no longer know which entries TeamAI owned. Preserve the manifest and mark removal incomplete.
  • [P1 blocking] src/local-agent.ts:4821 similarly swallows workspace-cache deletion failures before teardown deletes the workspace manifest. If .teamai/cache cannot be removed, the command reports success and later retries only retain its exclude lines because no state remains to retry deleting the cache. Preserve retry state and return a nonzero status.

The previously reported findings are resolved in the current diff. The PR description contains sufficient real-CLI testing evidence for this runtime-changing PR.

… not delete

Removing the HTTP source logged a models file it could not clean, or a
workspace cache it could not delete, then deleted the manifests that
name them and reported success. The teardown now keeps those manifests,
reports the removal incomplete with exit 1, and the retry, which finds
them with the source already disabled, removes what is left. A user-scope
`teamai uninstall` keeps the local agent's home when its teardown is
incomplete.
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
  • [P1 blocking] src/local-agent.ts:4794 swallows a failed skill/rule/instruction uninstall, but finishAgentHookTeardown() only preserves manifest.json for cache failures (src/local-agent.ts:4871). If a delivered skill cannot be deleted, the command exits successfully and deletes its ownership record; retry cannot remove the leftover resource or its retained exclude line. Treat remaining manifest entries as incomplete and preserve the manifest.
  • [P1 blocking] src/local-agent.ts:3154 treats an unreadable or malformed model manifest as an empty manifest. source remove-http then skips every delivered model and deletes the manifest during teardown, reporting success while model entries and plaintext keys remain with no retry metadata. Manifest read failure must mark teardown incomplete and preserve the file.

The previously reported findings are resolved in the current diff. The PR description contains sufficient real-CLI testing evidence for this runtime-changing PR.

…moval could not handle

- A skill, rule or instruction the teardown could not uninstall keeps its
  manifest entry. The disabled marker keeps the source config while any
  entry is left, so the retry uninstalls it. The run exits 1.
- A model record that cannot be read or parsed is kept, no models file is
  changed, and the run exits 1, instead of being read as empty and
  deleted with teamai's models and keys still in place.
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
  • [P1 blocking] src/local-agent.ts:2398 deletes the cached skill source before removing its delivered copies. If deleting a destination copy then fails, the retry has no source hashes; localAgentCopyFiles() classifies every remaining file as member-owned, drops the manifest entry, and permanently leaves the delivered skill and exclusion line behind. Keep the cache until all copies are removed, or persist the precomputed ownership for retries.
  • [P1 blocking] src/local-agent.ts:4898 preserves a nonempty git-exclude.json but does not count it as incomplete. If a deleted models file’s credentials block cannot be updated—for example, .git/info/exclude is read-only—source remove-http exits successfully. disabledSourceLeftovers() checks only the local-agent owner, so the retained credentials record is never retried and the stale pattern can hide a future member-created models file.

The previously reported findings remain resolved. The PR description contains sufficient real-CLI testing evidence for this runtime-changing PR.

… report kept exclude lines

- A skill's cached source goes after its copies, so a copy that cannot
  be deleted fails the entry while the cache still proves which files
  are teamai's, and the retry removes it.
- Removing the HTTP source counts an exclude file whose local-agent or
  credentials lines it could not write as incomplete, and exits 1. A
  retry finds a credentials record left behind and judges its lines
  from the checkouts of the recorded exclude files.
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
  • [P1 blocking] src/local-agent.ts:4875 treats gitTracks() returning unknown as a successful cache cleanup. If Git temporarily cannot inspect the generated .teamai/.gitignore, source remove-http deletes the workspace manifest, exits 0, and leaves both the file and its exclusion behind. Later retries can locate the exclusion but no longer know to delete the generated .gitignore, so .teamai/local-agent/ remains silently ignored indefinitely. Return false on unknown so the manifest and retry state are preserved.
  • [P3 nit] src/local-agent.ts:4920 interprets an unreadable or malformed git-exclude.json as empty when deciding what state to retain. The preceding cleanup reports this record as a retained block, but teardown then deletes the record, leaving its exclusion blocks without metadata for the advertised retry. Preserve the record whenever reading it fails.

The previously reported findings are resolved. The PR description contains sufficient real-CLI testing evidence for this runtime-changing PR.

…down would remove

- The cache .gitignore a workspace teardown wrote stays, and fails the
  cache cleanup, while git cannot say whether the repository tracks it.
- A skill or rule uninstall fails before it removes anything while git
  cannot judge a copy on disk, so its cache and records stay to prove the
  copy teamai's on the retry.
- An unreadable or malformed git-exclude.json is kept, as it may name
  blocks still in place.
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.

[feat] keep the resources pull delivers in project scope out of git

2 participants