Skip to content

fix(pull): refresh reports worktree before reading votes and stats #557

Description

@jeff-r2026

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

  1. Configure a user-scope independent Git clone with a document docs/guide.md and recall enabled.
  2. Materialize its reports worktree and run teamai pull --force.
  3. From a second clone, commit and push votes/bob.yaml to teamai-reports, voting for docs/guide.md.
  4. 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])

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

    bugSomething isn't workinghelp wantedExtra attention is needed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions