feat(mcp): expose thread workspace inventory#8685

closed
t3code/codex-turn-mapping
agents/mcp-workspaces/inventory
openedAug 29, 2026byJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 4d ago

An agent can otherwise see only the branch and worktree path recorded on its thread, which does not establish whether Git agrees, which physical checkouts exist, or which live threads own them.

This change enriches t3_worktree_status with separate recorded and actual state, and adds bounded t3_worktree_list discovery. A dedicated Git inventory path reports canonical repository common-directory and physical worktree-root identities, including detached and stale registrations, without changing the existing paginated vcs.listRefs wire/cache shape.

Results are current-project scoped and read-only. Pages are sliced before status I/O; nested and symlinked recorded paths are resolved through actual Git identity; independent nested repositories are not misattributed; ordinary typed inventory failures make binding counts explicitly incomplete while interruption and defects propagate.

Focused validation:

  • 241 lower-layer tests across real Git inventory, MCP status/list, registration, Claude exposure, contracts, client-runtime cache compatibility, and presentation
  • vp run --filter t3 typecheck
  • vp run --filter @t3tools/contracts typecheck
  • vp run --filter @t3tools/shared typecheck
  • vp run --filter @t3tools/client-runtime typecheck
  • Targeted vp lint, vp fmt --check, and git diff --check

Review base: t3code/codex-turn-mapping at 415ed0f73b97f1655b6282492f81d0b2bba3a9cc. Native stack: #8685 โ†’ #8680. This discovery layer does not add lifecycle pruning, revival, or cleanup.

Implemented by GPT-5.6-Sol via Codex in T3 Code.

Synced from GitHub

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

โš™๏ธ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 71b8ce76-89e2-4f74-b444-f8a7e54f290f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ๐Ÿ” Trigger review

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

Synced from GitHub

Thread transfer impact

โš ๏ธ The latest CI run did not produce a thread transfer result for 718558b.

This comment will update automatically after the next completed run.

packages/contracts/src/git.tslines 272โ€“272Outdated ยท 8c4905bOpen thread
โ€ฆโ€ฆ@@ -263,6 +269,7 @@ export type VcsStatusStreamEvent = typeof VcsStatusStreamEvent.Type;
263269
264270export const VcsListRefsResult = Schema.Struct({
265271 refs: Schema.Array(VcsRef),
272+ worktrees: Schema.Array(VcsWorktreeCheckout),
MmacroscopeappBoton lines 272โ€“272Aug 29, 2026

๐ŸŸก Medium src/git.ts:272

Existing persisted VCS-ref caches without worktrees fail VcsListRefsResult decoding after upgrade, so the loader returns ConnectionPersistenceError and users lose the cached/offline branch list until refresh. Because this field is required, make it backward-compatible with a default empty array or migrate the cache schema.

suggestion
1 worktrees: Schema.optionalWith(Schema.Array(VcsWorktreeCheckout), { default: () => [] }),
๐Ÿš€ Reply "fix it for me" or copy this AI Prompt for your agent:
text
1In file @packages/contracts/src/git.ts around line 272:
2
3Existing persisted VCS-ref caches without `worktrees` fail `VcsListRefsResult` decoding after upgrade, so the loader returns `ConnectionPersistenceError` and users lose the cached/offline branch list until refresh. Because this field is required, make it backward-compatible with a default empty array or migrate the cache schema.

Fixed in c49a0611921ca9671aae4b590b0339a0001a6dd3 by removing the worktrees field from VcsListRefsResult entirely. Worktree enumeration now uses a dedicated internal Git workflow read, so existing RPC responses and persisted branch caches keep their original shape. Added old-shape contract decode and client-runtime persisted-cache coverage.

apps/server/src/mcp/WorktreeMcpService.tsline 637Outdated ยท c49a061Open thread
โ€ฆโ€ฆ@@ -463,27 +510,165 @@ const make = Effect.gen(function* () {
634+ bindingCount: bindings.length,
635+ } as const;
636+ }
637+ const actual = statusExit.value;
MmacroscopeappBoton line 637Aug 29, 2026

๐ŸŸก Medium mcp/WorktreeMcpService.ts:637

A deleted Git worktree is returned with availability: "available" and no statusError, so agents can treat a stale checkout as usable. readWorkspaceStatus succeeds with isRepo: false for a missing cwd; check filesystem existence in the success branch before setting availability.

โœ… Resolved in 0130019314ce24de5c9733dad2286cec6c23160a

Fixed in 0130019314ce24de5c9733dad2286cec6c23160a. A successful status read with isRepo: false now checks filesystem existence and reports availability: "missing" for a deleted checkout or "unreadable" for an existing non-worktree path, with a matching statusError. Added coverage for the stale registered path case.

docs/orchestration-v2/orchestrator-mcp-server.mdline 309Outdated ยท c49a061Open thread
โ€ฆโ€ฆ@@ -302,6 +302,26 @@ Without `runId`, it selects the newest interruptible run. A terminal run is
306+
307+Reads the calling thread's saved branch and worktree path, then reads Git status from that path.
308+The result keeps recorded and actual state separate and reports whether they agree. A missing
309+worktree, non-repository path, or branch mismatch is visible without changing either state.
MmacroscopeappBoton line 309Aug 29, 2026

๐ŸŸ  High orchestration-v2/orchestrator-mcp-server.md:309

t3_worktree_status returns operation_failed instead of a not_repository result when the saved worktree path is missing or outside a Git repository. The implementation starts loadWorktrees(workspacePath) before constructing the agreement, and GitVcsDriver.listWorktrees fails for that path, so the documented workspace_missing/not_repository state is never reported. Handle this discovery error when building the agreement so the status result remains visible.

Also found in 1 other location(s)

apps/server/src/mcp/WorktreeMcpService.ts:521

status starts loadWorktrees(workspacePath) as part of Effect.all, but GitVcsDriver.listWorktrees fails when that path is missing or outside a Git repository. Consequently the whole status call returns operation_failed before it can produce the implemented workspace_missing or not_repository agreement, precisely when a recorded worktree was deleted or its binding points outside the repository.

๐Ÿš€ Reply "fix it for me" or copy this AI Prompt for your agent:
text
1In file @docs/orchestration-v2/orchestrator-mcp-server.md around line 309:
2
3`t3_worktree_status` returns `operation_failed` instead of a `not_repository` result when the saved worktree path is missing or outside a Git repository. The implementation starts `loadWorktrees(workspacePath)` before constructing the agreement, and `GitVcsDriver.listWorktrees` fails for that path, so the documented `workspace_missing`/`not_repository` state is never reported. Handle this discovery error when building the agreement so the status result remains visible.
4
5Also found in 1 other location(s):
6- apps/server/src/mcp/WorktreeMcpService.ts:521 -- `status` starts `loadWorktrees(workspacePath)` as part of `Effect.all`, but `GitVcsDriver.listWorktrees` fails when that path is missing or outside a Git repository. Consequently the whole status call returns `operation_failed` before it can produce the implemented `workspace_missing` or `not_repository` agreement, precisely when a recorded worktree was deleted or its binding points outside the repository.

Fixed in 0130019314ce24de5c9733dad2286cec6c23160a. t3_worktree_status now treats worktree inventory discovery as optional for the saved path, separately checks path availability, and still returns a typed workspace_missing or not_repository agreement instead of failing the whole status read. Added a missing saved-worktree regression where inventory discovery fails.

apps/server/src/mcp/WorktreeMcpService.tsline 549Outdated ยท 0130019Open thread
โ€ฆโ€ฆ@@ -463,27 +510,195 @@ const make = Effect.gen(function* () {
546+ ? "workspace_missing"
547+ : actual.refName !== projection.thread.branch
548+ ? "branch_mismatch"
549+ : "in_sync";
CcursorBoton line 549Aug 29, 2026

Inventory failure marked not a repository

Medium Severity

t3_worktree_status sets agreement to not_repository whenever worktree inventory for the thread path fails, even if actualWorkspace.isRepo is already true. A timeout or git worktree list error then contradicts the status payload and can send the agent down the wrong repair path.

Reviewed by Cursor Bugbot for commit 0130019314ce24de5c9733dad2286cec6c23160a. Configure here.

Fixed in b3658e777174937b83647d9199388c7b6d8849ba. Inventory failure no longer forces not_repository: that result is based only on actual.isRepo === false; when status proves a repository but physical inventory identity cannot be established, the tool reports workspace_missing instead. The focused status suite covers the distinct missing, non-repository, and inventory-unavailable outcomes.

apps/server/src/mcp/WorktreeMcpService.tsline 530Outdated ยท 0130019Open thread
โ€ฆโ€ฆ@@ -463,27 +510,195 @@ const make = Effect.gen(function* () {
527+ fileSystem.exists(workspacePath).pipe(Effect.orElseSucceed(() => false)),
528+ ],
529+ { concurrency: 5 },
530+ );
CcursorBoton line 530Aug 29, 2026

Status dies if project inventory fails

Medium Severity

t3_worktree_status requires loadWorktrees on the project root to succeed. If that inventory read fails, the whole tool errors even after the thread's own git status has already been read, so an attached worktree cannot report recorded vs actual state.

Reviewed by Cursor Bugbot for commit 0130019314ce24de5c9733dad2286cec6c23160a. Configure here.

Fixed in b3658e777174937b83647d9199388c7b6d8849ba. Project-root inventory is now optional during status reconciliation, just like saved-workspace inventory. A real GitWorkflowService non-repository test uses an existing plain directory and verifies the tool returns not_repository with its recorded and actual state instead of failing the read.

apps/server/src/mcp/WorktreeMcpService.tsline 609Outdated ยท b3658e7Open thread
โ€ฆโ€ฆ@@ -463,27 +510,197 @@ const make = Effect.gen(function* () {
606+ : null;
607+ const bindingLimit = input.bindingLimit ?? 20;
608+ const threadWorkspaces = yield* Effect.forEach(threads, (thread) =>
609+ threadWorkspacePath(thread, projectWorktreeRoot).pipe(
MmacroscopeappBoton line 609Aug 29, 2026

๐ŸŸก Medium mcp/WorktreeMcpService.ts:609

listWorktrees omits thread bindings when a thread's recorded worktreePath is a nested directory such as /repo/packages/server: threadWorkspacePath returns that directory, while inventory keys the checkout by /repo, so the comparison at line 619 yields no match and reports an incorrect bindings and bindingCount. Resolve each recorded path to its Git worktree root before comparing it with inventory paths.

โœ… Resolved in 3beb4dc0f7d7c8da625bccca93d2d8f7d77d54b1

Fixed in 3beb4dc0f7d7c8da625bccca93d2d8f7d77d54b1. Recorded paths are now realpath-canonicalized and mapped to the longest canonical physical worktree root from the existing inventory before bindings are counted. The focused regression uses a nested recorded cwd, verifies it binds to the root entry, and asserts that pagination still performs only one inventory read and one status read for the returned page.

Synced from GitHub

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable โ€” This PR adds a model-visible worktree inventory capability and substantially expands existing worktree status behavior across MCP, contracts, orchestration, and Git infrastructure. The cross-layer runtime impact and non-trivial inventory/path-resolution logic warrant human review.

No code changes detected at 718558b. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

apps/server/src/mcp/WorktreeMcpService.tsline 626Outdated ยท 45486fbOpen thread
โ€ฆโ€ฆ@@ -463,27 +515,229 @@ const make = Effect.gen(function* () {
623+ ),
624+ ];
625+ const physicalRootByRecordedPath = new Map<string, string>();
626+ yield* Effect.forEach(
MmacroscopeappBoton line 626Aug 30, 2026

๐ŸŸ  High mcp/WorktreeMcpService.ts:626

listWorktrees performs a Git inventory lookup for every unresolved recorded thread path before returning any page, so limit: 1 still scans all stale archived/deleted bindings and can leave the MCP request hung on thousands of slow operations. Bound this resolution by the requested page (or otherwise avoid resolving unlisted bindings) so pagination also bounds the work performed.

โœ… Resolved in d1a69553ec81ba2d68399b392dd2b7ef993becc9

Fixed in d1a69553ec81ba2d68399b392dd2b7ef993becc9. Resolution work is page-local and capped. Candidate paths first pass a non-authorizing nearest-root filter, then Git common-dir/current-root identity proves attribution so nested independent repositories are not counted. Ordinary inventory failures mark ; defects and interruption still propagate. The focused limit=1 and failed-candidate tests pass.

Synced from GitHub

Macroscope has since reviewed this pull request. An earlier review was skipped by a cost limit; a review has now completed, so that notice no longer applies.

CcursorBotreviewed ยท 1 inline threadAug 30, 2026
Synced from GitHub

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit d1a69553ec81ba2d68399b392dd2b7ef993becc9. Configure here.

apps/server/src/vcs/GitVcsDriverCore.tsline 142Outdated ยท d1a6955Open thread
โ€ฆโ€ฆ@@ -139,6 +139,7 @@ interface GitRepositoryPaths {
139139interface GitRefsSnapshot {
140140 readonly localBranches: ReadonlyArray<VcsRef>;
141141 readonly remoteBranches: ReadonlyArray<VcsRef>;
142+ readonly worktrees: ReadonlyArray<GitVcsDriver.GitWorktreeCheckout>;
CcursorBoton line 142Aug 30, 2026

Unused worktrees field persisted in cached snapshot

Low Severity

The new worktrees field on GitRefsSnapshot is populated inside readGitRefsSnapshot and stored in the TTL-governed snapshot cache, but no consumer of the snapshot ever reads snapshot.worktrees. The listRefs function only accesses localBranches, remoteBranches, and hasPrimaryRemote. The worktree data IS used internally within readGitRefsSnapshot to build existingWorktrees and worktreeMap, but it doesn't need to be part of the returned/cached struct. Keeping it there adds dead data to every cached entry without any reader.

Additional Locations (1)

Reviewed by Cursor Bugbot for commit d1a69553ec81ba2d68399b392dd2b7ef993becc9. Configure here.

Confirmed. GitRefsSnapshot.worktrees was dead cached payload: readGitRefsSnapshot still keeps its local worktrees value to map checked-out paths onto branch refs, while listWorktrees remains the dedicated inventory API. Removed only the snapshot interface member and returned property in 602bf694f3a73d5f8029d961d9e2df9cddb8d449. Both Git driver suites pass (65 tests), and the canonical server typecheck completes without errors.

apps/server/src/mcp/WorktreeMcpService.tslines 684โ€“689Outdated ยท bdd0832Open thread
โ€ฆโ€ฆ@@ -460,27 +512,258 @@ const make = Effect.gen(function* () {
686+ const exists = yield* fileSystem
687+ .exists(workspacePath)
688+ .pipe(Effect.orElseSucceed(() => false));
689+ const detail = errorMessage(Cause.squash(statusExit.cause));
MmacroscopeappBoton lines 684โ€“689Sep 5, 2026

mcp/WorktreeMcpService.ts:689

readWorkspaceStatus fails only with the tagged WorktreeMcpFailure, so this could catch that tag directly instead of inspecting an Exit/Cause. Two consequences of the current form: defects and interruption are also folded into an availability: "missing"/"unreadable" entry rather than propagating, and statusError is re-derived from a squashed cause instead of the error's own structured message.

Consider replacing the exit inspection with Effect.catchTags({ WorktreeMcpFailure: ... }) (or Effect.catch, since the whole error channel is handled) and using error.message for detail; the later statusExit.value read would become the success value of that pipeline.

No longer relevant as of 07d53fd07b5fd71d4c3a71ed0c37ca24bed2931c

Fixed in 07d53fd07b5fd71d4c3a71ed0c37ca24bed2931c. The listing now matches only the typed WorktreeMcpFailure channel; defects and interruption propagate. Focused coverage asserts the ordinary unreadable result, Cause.hasDies for a defect, and Cause.hasInterruptsOnly for interruption (64/64 lower tests; the 115/115 upstack suite also passes).

Verified: typed failures are handled while defects and interruption propagate. Thanks.

Superseded as the active MCP proposal by #10566 in the rebuilt MCP stack, following the maintainer request to review thin service wrappers separately from service hardening.

This is not a full feature-parity replacement. This discovers branch-backed refs/worktrees only. Detached unbound inventory and switching an existing checkout require a shared service operation and are intentionally not rebuilt inside MCP. The old workspace work is retained for that extraction.

Closing this mixed proposal without merging. The remote branch agents/mcp-workspaces/inventory and exact head 718558bfe90355b4e072ef0279d4115d6f8e10fc are retained so the service changes can be extracted for separate review later.