Repository navigation
[bug] self mode: second worktree cannot publish learnings or reports, and loses its queue #808
Description
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
- Side-branch checkouts move to the partition. In self mode,
getWorktreeDirreturns<getDataHome()>/<dirname>.git worktree addaccepts a path outside the repo, andgitRootstays the business repo, whose refs every worktree shares. The lock (dirname(worktreePath)) moves with it. - The queue moves to the partition. In self mode,
pendingLearningsDirreturns<getDataHome()>/pending-learnings. - 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. - One-time migration. Move each existing
<worktree>/.teamai/pending-learnings/*into the partition queue, then rungit worktree prune, and remove the old.teamai/*-wtregistrations.
// 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:
autoPushViaMRcommits in the knowledge clone, not inlearnings-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.bakwithout 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 forlearnings-wt,reports-wtandpending-learnings.skill-data/(share,core): grep forpending-learningsand.teamai/learnings-wt.
Reproduction
- Set up a self-mode project at
/rwith a bare origin, then runteamai pull. teamai contributein/rsucceeds.git worktree add ../b -b b,cd ../b,teamai contribute. It saves locally withfatal: 'teamai-learnings' is already used by worktree at '/r/.teamai/learnings-wt'.git worktree remove ../b. The learning queued in step 3 is gone.
Acceptance
- Contributions from
/rand frombboth reachorigin/teamai-learnings, whichever runs first. - Removing
bafter a contribute there loses nothing. recallin/rreturns 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.tsuses a single checkout today.
Environment
- OS: macOS (Darwin 25.5.0)
- teamai: 0.22.0 (
distbuilt fromfd0e913) - Provider: GitHub (self mode)
- AI tool(s): Claude Code
Activity
fix(data-layout): let every self-mode worktree publish learnings and …
fix(data-layout): let every self-mode worktree publish learnings and …
fix(data-layout): let every self-mode worktree publish learnings and …
fix(data-layout): let every self-mode worktree publish learnings and …
SaulMoro commentedon Sep 24, 2026
The
autoPushViaMRsuspicion listed under "Not in scope" here is item 1 of #823.fix(data-layout): let every self-mode worktree publish learnings and …
fix(data-layout): let every self-mode worktree publish learnings and …
fix(data-layout): let every self-mode worktree publish learnings and …
fix(data-layout): let every self-mode worktree publish learnings and …
fix(data-layout): let every self-mode worktree publish learnings and …
fix(data-layout): let every self-mode worktree publish learnings and …
fix(data-layout): let every self-mode worktree publish learnings and …
fix(data-layout): let every self-mode worktree publish learnings and …
2 remaining items
fix(data-layout): let every self-mode worktree publish learnings and …
fix(data-layout): let every self-mode worktree publish learnings and …
fix(data-layout): let every self-mode worktree publish learnings and …
fix(data-layout): let every self-mode worktree publish learnings and …
fix(data-layout): let every self-mode worktree publish learnings and …
fix(data-layout): let every self-mode worktree publish learnings and …
fix(data-layout): let every self-mode worktree publish learnings and …
fix(data-layout): let every self-mode worktree publish learnings and …
fix(data-layout): let every self-mode worktree publish learnings and …
fix(data-layout): let every self-mode worktree publish learnings and …
fix(learnings): find learnings despite a broken manifest and in the d…
fix(learnings): find learnings despite a broken manifest and in the d…
test(e2e): isolate HOME and the teamai binary for fixture git
fix: delivery and local-state bugs (#993) (#995)