Skip to content

[bug] self mode: second worktree cannot publish learnings or reports, and loses its queue #808

Description

@SaulMoro

Description

In single-repo (kind: self) mode, the side-branch checkouts and the contribution queue live inside each worktree. Git lets a branch be checked out in one worktree only, so every worktree after the first can't publish learnings or reports, and a learning queued in a worktree is deleted with it.

Git and http mode are not affected: there these paths are in the shared partition. The real CLI confirmed that contributions from two worktrees both publish there.

Cause. For a self config, readConfigFrom re-anchors repo.localPath to <workspaceRoot>/.teamai (src/config.ts:507). That is intentional, because committed knowledge follows the branch. Several things that belong to the project are derived from localPath too:

<workspaceRoot>/.teamai/            one per worktree
├── learnings-wt/                   checkout of teamai-learnings     getWorktreeDir      src/types.ts:2164
├── reports-wt/                     checkout of teamai-reports       getWorktreeDir
└── pending-learnings/              queue, gitignored                pendingLearningsDir src/utils/pending-learnings.ts:20

~/.teamai/projects/<slug>/          one per project
└── search-index.json               built from the per-worktree paths above

What breaks

Scenario: repo at /r, worktrees /r/.claude/worktrees/a and /elsewhere/b. Unless noted, the real CLI reproduced each item.

Symptom Where
The first checkout to create .teamai/learnings-wt owns teamai-learnings. contribute and pull from every other checkout fail with fatal: 'teamai-learnings' is already used by worktree at …, on every retry. A linked worktree that got there first locks out the main checkout the same way ensureWorktree, src/utils/branch-worktree.ts:158-250
A learning queued in b is deleted when b is removed. A plain git worktree remove does it without --force, because ignored files don't make a worktree dirty pendingLearningsDir
Same lock-out for teamai-reports. From a non-owning checkout, pull reports nothing, stats omits the reported totals, digest fails with the raw git error, and votes-sync doesn't push. The data stays pending, so nothing is lost getWorktreeDir
recall serves whatever checkout rebuilt the shared index last. After a contribute in b, recall in /r returned only b's learning, with a path into b, and /r's own published learning was missing. After b was removed, the path pointed nowhere src/recall.ts:297-300, src/pull.ts:984-999, src/contribute.ts:65-81
Suspected, not run: import --from-mr in a checkout without a valid learnings-wt writes into a plain directory, which the next ensureWorktree removes (fse.remove) src/import.ts:246-252, src/utils/branch-worktree.ts:193-198

Proposed fix

  1. Side-branch checkouts move to the partition. In self mode, getWorktreeDir returns <getDataHome()>/<dirname>. git worktree add accepts a path outside the repo, and gitRoot stays the business repo, whose refs every worktree shares. The lock (dirname(worktreePath)) moves with it.
  2. The queue moves to the partition. In self mode, pendingLearningsDir returns <getDataHome()>/pending-learnings.
  3. Per-checkout index. In self mode, docs, rules and skills really do differ per branch. Key the index per workspace, workspaces/<managedMcpWorkspaceId(workspaceRoot)>/search-index.json, the same way managed MCP is keyed.
  4. One-time migration. Move each existing <worktree>/.teamai/pending-learnings/* into the partition queue, then run git worktree prune, and remove the old .teamai/*-wt registrations.
 // src/types.ts  getWorktreeDir
   if (isSelfMode(localConfig)) {
-    return path.join(localConfig.repo.localPath, dirname);
+    return path.join(getDataHome(localConfig), dirname);
   }
 // src/utils/pending-learnings.ts  pendingLearningsDir
   if (isSelfMode(localConfig)) {
-    return path.join(localConfig.repo.localPath, 'pending-learnings');
+    return path.join(getDataHome(localConfig), 'pending-learnings');
   }

Not in scope

  • A separate suspicion, not tied to worktrees: autoPushViaMR commits in the knowledge clone, not in learnings-wt, so an MR-imported learning may never be pushed. It needs its own check.
  • Migrate edge cases for legacy per-worktree .teamai/ directories (renamed to .bak without draining their queue; a per-worktree migration lock with shared staging). These are suspected and become rare once the queue lives in the partition.

Docs and skills

  • docs/designs/data-directory-layout.md: the self-mode rows for learnings-wt, reports-wt and pending-learnings.
  • skill-data/ (share, core): grep for pending-learnings and .teamai/learnings-wt.

Reproduction

  1. Set up a self-mode project at /r with a bare origin, then run teamai pull.
  2. teamai contribute in /r succeeds.
  3. git worktree add ../b -b b, cd ../b, teamai contribute. It saves locally with fatal: 'teamai-learnings' is already used by worktree at '/r/.teamai/learnings-wt'.
  4. git worktree remove ../b. The learning queued in step 3 is gone.

Acceptance

  • Contributions from /r and from b both reach origin/teamai-learnings, whichever runs first.
  • Removing b after a contribute there loses nothing.
  • recall in /r returns both learnings, with paths that exist.
  • Regression test: a real-git test with a main checkout plus one linked worktree in self mode, contributing from both. src/__tests__/contribute-self-learnings.test.ts uses a single checkout today.

Environment

  • OS: macOS (Darwin 25.5.0)
  • teamai: 0.22.0 (dist built from fd0e913)
  • Provider: GitHub (self mode)
  • AI tool(s): Claude Code

Activity

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions