Problem
After #489, independent Git clones read votes and stats from the sibling teamai-reports worktree. pull calls ensureReportsWorktree, which returns an existing worktree without fetching/resetting it. Consequently, even teamai pull --force can rebuild the search index from stale report data.
Reproduction and actual result
- Configure a user-scope independent Git clone with a document
docs/guide.md and recall enabled.
- Materialize its reports worktree and run
teamai pull --force.
- From a second clone, commit and push
votes/bob.yaml to teamai-reports, voting for docs/guide.md.
- Run
teamai pull --force again in the first environment.
Observed: the remote contains the vote, but the local reports worktree still has no votes/bob.yaml; the rebuilt index has no hotness from that vote. Running teamai members (which refreshes reports), then teamai pull --force, produces hotness: 1 for the document.
Expected: pull should consume the latest available remote reports before rebuilding report-dependent outputs. Offline fallback may retain the cached copy.
Suggested fix
Refresh the reports worktree before votes are indexed and stats are read for recommendations, with appropriate coordination to avoid discarding pending writes. The stats path has the same ensure-only pattern; the concrete end-to-end reproduction above verifies votes/hotness.
Relevant code at reviewed head: src/pull.ts:838-839 and src/pull.ts:1069-1070.
Verification
Reproduced on PR #489 head d11e1018af3cd6ce580bb1b4801e6efbb91b0af7 with Node 22.22.0 on macOS, real local bare Git remotes, and the built CLI. Build, typecheck, and 118 focused tests passed. The full Agent × Provider matrix was not run. Accepted as a follow-up when merging #489.
Standalone reproduction
Run npm run build, save the following as repro.py, then run python3 repro.py from the repository root. It creates isolated temporary repositories and a temporary HOME; no hosted remote is used. The script demonstrates both follow-up findings.
import os, json, subprocess, pathlib, tempfile
root=pathlib.Path(tempfile.mkdtemp(prefix='pr489-repro-')); home=root/'home'; home.mkdir(); work=root/'work'; work.mkdir()
env=dict(os.environ, HOME=str(home), GIT_CONFIG_NOSYSTEM='1', GIT_CONFIG_GLOBAL=str(root/'gitconfig'), GIT_AUTHOR_NAME='Alice', GIT_AUTHOR_EMAIL='alice@example.test', GIT_COMMITTER_NAME='Alice', GIT_COMMITTER_EMAIL='alice@example.test')
(root/'gitconfig').write_text('[init]\n defaultBranch = main\n')
def run(args,cwd=work):
p=subprocess.run(args,cwd=cwd,env=env,text=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
if p.returncode: raise Exception(p.stdout)
return p.stdout
seed=root/'seed'; seed.mkdir(); run(['git','init'],seed); (seed/'teamai.yaml').write_text('team: review\nrepo: https://example.test/team.git\nsharing:\n recall:\n enabled: true\n'); (seed/'docs').mkdir(); (seed/'docs'/'guide.md').write_text('# Guide\nUseful guide for a reproducible review.\n'); run(['git','add','.'],seed); run(['git','commit','-m','seed'],seed)
remote=root/'remote.git'; run(['git','clone','--bare',str(seed),str(remote)])
clone=home/'.teamai'/'team-repo'; clone.parent.mkdir(); run(['git','clone',str(remote),str(clone)])
(clone.parent/'config.yaml').write_text(json.dumps(dict(repo=dict(kind='git',localPath=str(clone),remote=str(remote)),username='alice',scope='user',additionalRoles=[],agents=['claude'])))
cli=['node', str(pathlib.Path.cwd()/'dist/index.js')]
print('FIXTURE',root)
print('BRANCHES BEFORE',run(['git','branch'],remote)); print('MEMBERS',run(cli+['members'])); print('BRANCHES AFTER',run(['git','branch'],remote))
print('PULL1',run(cli+['pull','--force']))
idx=clone.parent/'search-index.json'; data=json.loads(idx.read_text()); print('INDEX',json.dumps(data)[:1700])
other=root/'other'; run(['git','clone','-b','teamai-reports',str(remote),str(other)])
(other/'votes').mkdir(); (other/'votes'/'bob.yaml').write_text('votes:\n docs/guide.md:\n at: "2026-09-14T00:00:00Z"\n'); run(['git','add','.'],other); run(['git','commit','-m','bob votes'],other); run(['git','push'],other)
print('PULL2',run(cli+['pull','--force'])); print('LOCAL VOTE EXISTS',(clone.parent/'reports-wt'/'votes'/'bob.yaml').exists()); print('REMOTE VOTE',run(['git','show','teamai-reports:votes/bob.yaml'],remote)); print('INDEX2',idx.read_text()[:1700])
print('MEMBERS REFRESH',run(cli+['members'])); print('PULL3',run(cli+['pull','--force'])); print('INDEX3',idx.read_text()[:1700])
Problem
After #489, independent Git clones read votes and stats from the sibling
teamai-reportsworktree.pullcallsensureReportsWorktree, which returns an existing worktree without fetching/resetting it. Consequently, eventeamai pull --forcecan rebuild the search index from stale report data.Reproduction and actual result
docs/guide.mdand recall enabled.teamai pull --force.votes/bob.yamltoteamai-reports, voting fordocs/guide.md.teamai pull --forceagain in the first environment.Observed: the remote contains the vote, but the local reports worktree still has no
votes/bob.yaml; the rebuilt index has no hotness from that vote. Runningteamai members(which refreshes reports), thenteamai pull --force, produceshotness: 1for the document.Expected: pull should consume the latest available remote reports before rebuilding report-dependent outputs. Offline fallback may retain the cached copy.
Suggested fix
Refresh the reports worktree before votes are indexed and stats are read for recommendations, with appropriate coordination to avoid discarding pending writes. The stats path has the same ensure-only pattern; the concrete end-to-end reproduction above verifies votes/hotness.
Relevant code at reviewed head:
src/pull.ts:838-839andsrc/pull.ts:1069-1070.Verification
Reproduced on PR #489 head
d11e1018af3cd6ce580bb1b4801e6efbb91b0af7with Node 22.22.0 on macOS, real local bare Git remotes, and the built CLI. Build, typecheck, and 118 focused tests passed. The full Agent × Provider matrix was not run. Accepted as a follow-up when merging #489.Standalone reproduction
Run
npm run build, save the following asrepro.py, then runpython3 repro.pyfrom the repository root. It creates isolated temporary repositories and a temporary HOME; no hosted remote is used. The script demonstrates both follow-up findings.