Repository navigation
fix(session): honor Cursor CLI's two recorded tool roots - #1165
Conversation
The PR description documents sufficient testing: a representative real-CLI verification and an E2E build/run record. The disclosed worker timeout does not constitute a missing verification record. Review was read-only; no PR code was run, built, or installed. |
`teamai init` records a relocated tool root so every later run targets the directory that tool reads. Cursor CLI was not in `RELOCATABLE_TOOLS`, so a member with `CURSOR_DATA_DIR` / `CURSOR_CONFIG_DIR` set had teamai listing `~/.cursor/projects` and writing the co-author setting to `~/.cursor/cli-config.json`. Cursor moves two directories and neither is the root its `toolPaths` entry hangs off — the user `mcp.json`, `rules/` and `commands/` stay in `~/.cursor` whatever the variables say. An `applyToolRoots` key re-roots the `toolPaths` entry of the same name, so a key named `cursor` would move a layout that has to stay put. The two roots are therefore recorded as `cursor-data` and `cursor-config`: the table holds roots, not tool ids. `getCursorProjectsDir` follows the recorded data root and the `cursor` adapter takes its store in the constructor, as the Claude Code and Codex adapters already do. `cursorConfigDir` adds the config root TeamAI had no way to express, `$XDG_CONFIG_HOME/cursor` included. On the IDE side, `cursor-store` stops hard-coding `~/.config/Cursor`. A blank `XDG_CONFIG_HOME` counts as unset (`?.trim() ||`, matching `switch.ts` and `opencode.ts`), and the three test files that build fixtures under `~/.cursor` / `~/.config/Cursor` now pin the variable the way the rest of the suite does. Fixes the CLI and IDE halves of Tencent#1138.
a59f5d3 to
d4990e1
Compare
The earlier blank- The description documents sufficient real-CLI/E2E verification. The disclosed worker timeout does not constitute a missing verification record. Review was read-only; no PR code was run, built, or installed. |
|
No new findings in the specified diff. Earlier findings are resolved:
The PR description documents sufficient testing: representative real-CLI verification and an E2E build/run record. The disclosed worker timeout does not constitute a missing verification record. Review was read-only; no PR code was run, built, or installed. |
Summary
teamai initrecords a relocated tool root so that every later run targets the directory the tool actually reads. Cursor CLI was not inRELOCATABLE_TOOLS, so a member withCURSOR_DATA_DIR/CURSOR_CONFIG_DIRset got teamai listing~/.cursor/projectsand writing the co-author setting to~/.cursor/cli-config.json.Cursor CLI moves two directories, and neither of them is the root its
toolPathsentry hangs off.Type of Change
What Cursor CLI reads
Read off the cursor-agent 2026.09.28 bundle, the two directories the issue asks about:
CURSOR_CONFIG_DIR$XDG_CONFIG_HOME/cursor, then~/.cursorcli-config.json,permissions.json,chats/,acp-sessions/CURSOR_DATA_DIR(also--data-dir)~/.cursorprojects/— the transcriptsThe user
mcp.json,rules/andcommands/stay in~/.cursorwhatever the variables say.Why the two roots are not recorded as
cursorapplyToolRootsre-roots thetoolPathsentry of every key it finds intoolRoots, so a key namedcursorwould movetoolPaths.cursor— the usermcp.json,rules/andcommands/that have to stay put. The roots are therefore recorded ascursor-dataandcursor-config:RELOCATABLE_TOOLSholds roots, not tool ids, and its doc block now says so.src/__tests__/cursor-tool-roots.test.tspins thatscopedToolPathsleavescursorwhere it is.What follows the record now
getCursorProjectsDir(toolRoots)resolves<data root>/projects. Thecursorentry ofADAPTER_REGISTRYforwardstoolRoots, andCursorAdaptertakes the resolved store in its constructor instead of closing over the default — the shape#1135gaveClaudeCodeAdapterandCodexAdapter.cli-config.json. NewcursorConfigDir()intypes.ts;coauthor-reconcile.tswrites there instead of a barepath.join(userHome, '.cursor', ...). This is the one placetoolRootscannot express the answer —XDG_CONFIG_HOMEnames a parent directory, and~/.configitself is refused as a root — so the fallback is read where the write happens.VSCODE_PORTABLE→VSCODE_APPDATA→--user-data-dir, and on Linux$XDG_CONFIG_HOMEor~/.config, joined withCursor).session-flow/cursor-store.tswas the one place that hard-coded~/.config/Cursor; it now honorsXDG_CONFIG_HOME, aside-history.tsalready does for the same IDE.Test Plan
npx tsc --noEmitpassesnpm run lintpasses — 0 warnings, 0 errorsnpx vitest run— I could not run the full suite here; see "Regression check" for what I ran insteadNew and updated tests
src/__tests__/cursor-tool-roots.test.ts(new, 11 cases) — the recorded keys, the transcript store (list, read and write under the recorded root,~/.cursorleft alone), the config root through the realreconcileCoAuthorForConfig, and the IDE store root (including a blankXDG_CONFIG_HOME). 53 passed together with the updatedtool-roots.test.ts. Its cleanup hook declares60_000: removing the fixture is I/O bound, and this box's temp volume is slow enough to miss the 15s default — the repo gives 84 other hooks an explicit budget for the same reason.src/__tests__/e2e/cursor-tool-roots-1138.test.ts(new, 3 cases) — the real CLI end to end. 3 passed (below).src/__tests__/tool-roots.test.ts— the refusal message now names the four supported keys.src/__tests__/coauthor-reconcile.test.ts,src/__tests__/cursor-user-images.test.ts,src/__tests__/agent-session-copy.test.ts— pinXDG_CONFIG_HOMEso the fixtures and the code agree whatever the runner exports. No assertion changed; see "Fixes after the first CI run".Regression check. I ran the same selection on the change and on the unmodified tree (same tarball), because on this Windows box a failure cannot be attributed without that:
Every one of the 22 fails on the unmodified tree too: POSIX file-mode and symlink assertions, a skill name containing
:,reg.exeblocked by this machine's sandbox, the OpenCode CLI not installed here, and a Codex shim that needs a login shell.Real-CLI verification. Cold
$HOME, one business repo with an https origin, and a transcript planted under~/.cursor/projects. The only variable across the six steps is the recordedtoolRoots:The E2E file, run the way
AGENTS.mdsays to:The three cases declare
120_000, following the ~237 cases in this suite that do. That is not decoration: on this machine a fixture that runsteamai initcosts ~30s per call, so the two-init case lands at ~62-70s and misses the 60s default. The same machine, same default budget, on the unmodifiedsrc/__tests__/e2e/init-self-agent-filter.test.ts: 2 of its 6 cases fail on that timeout (re-running init…62.1s,any other command still self-heals…64.2s) and pass once raised. So the timeout is the machine, not the change.Fixes after the first CI run
The first push came back
Lint & Testfailed on ubuntu-latest, passed on macos-latest (all three Node versions each way). Two causes, both in this change:Three existing test files inherited
XDG_CONFIG_HOME.coauthor-reconcile.test.ts,cursor-user-images.test.tsandagent-session-copy.test.tsbuild their expectations under~/.cursor/~/.config/Cursorbut never pin the variable — unlike the rest of the suite, whereagent-session-list.test.ts:79pins it and every E2E file does. With the new code reading that variable, they resolved a different directory than the fixture wrote and read backundefined. Each now pins it the way the rest of the repo does. No assertion changed; the variable-set path is covered by the new file instead.Evidence: on this Windows box, exporting
XDG_CONFIG_HOMEand re-running reproduces the ubuntu set —coauthor-reconcile.test.ts3 failed andcursor-user-images.test.ts1 failed, with the same case names and messages as the annotations. Unset, both pass. The fouragent-session-copy.test.tscases areskipIf(process.platform === 'win32'), so they cannot appear here at all; the annotations are the evidence for those. macOS never sets the variable — that is why it stayed green.A blank
XDG_CONFIG_HOMEwas read as set.getCursorStateRoot()used??, which keeps'', sopath.join('', 'Cursor')returned the relative pathCursor— the working directory. It is?.trim() ||now, matchingswitch.tsand the$XDG_DATA_HOMEread the OpenCode adapter already does (opencode.ts:307) — both treat a blank variable as unset — andcursor-tool-roots.test.tspins the blank case. (ide-history.ts:139has the same??shape for a different IDE; out of scope here, and I left it alone rather than widen the diff.)Cause 1 is what CI showed; cause 2 is the P2 finding the bot review filed on the PR.
The
E2E (fork-safe, no credentials)check is red on every fork PR here. It fails on #1062 (damenjs) exactly as it does on this one — a fork PR runs that variant and skips the other. Its annotations on this PR areworkflow-subworkflow/workflow-inline(record: 'pending'), which is workflow code this change does not touch; #1161 and #1154 have since changed that code onmain, so re-running may clear it.Related Issues
Addresses items 1 and 3 of #1138. Item 2 is #1142's and item 4 is untouched.
Notes for Reviewers
RELOCATABLE_TOOLS(cursor-data,cursor-config) andcursorConfigDir().doctordoes not cover the two new keys.buildToolRootCheckscompares where teamai would write a tool'stoolPathsagainst where that tool reads them; these two roots have notoolPathsentry — that is the point — so the check skips them, and a stale record of either root goes unreported. Covering it needs a check that is nottoolPaths-shaped; I left it out rather than widen this PR, and because that comment block sits inside#1119's insertion point.releasePreviousToolRootsskips them too, sinceisAgentExcludedreads acursor-datakey as an agent that is not enabled. Nothing is lost: neither root holds hooks or the managed MCP files, which is what that pass cleans up.c57da5af.#1148(src/types.ts, +2 lines) and#1062(src/init.ts, new import and push path) merged while this was open. The patch applied to both with a 2-line offset only, andtsc/lintpass on the rebased tree. Remaining open overlap issrc/types.ts(feat(antigravity): add support for Antigravity CLI (agy) #1040, fix(doctor): report the default paths a custom toolPaths entry drops #1119, feat(dashboard): skills inventory, detail, and workspace exclusions on the Resources page (#1123) #1143).#1142("honor Muse XDG data home", by @SIBTAIN-ASAD) touches onlydashboard-collector.tsand its tests — a different XDG consumer, no overlap.#1157(Cursor skill-usage hook matcher) touches hooks, not the session store.