Skip to content

fix(server): tell multi-repo agents which repos are in the project - #313

Open
dotrahat wants to merge 35 commits into
LoganRupe:multi-repo-workspacesfrom
dotrahat:fix/multi-repo-list-repo-roots
Open

dotrahat wants to merge 35 commits into
LoganRupe:multi-repo-workspacesfrom
dotrahat:fix/multi-repo-list-repo-roots

Conversation

@dotrahat

Copy link
Copy Markdown

In a multi-repo project, the agent is often not told which repos belong to the project, so it searches the workspace folder for them. Claude isn't told about repos inside the workspace folder, and Cursor, Grok, OpenCode and Antigravity aren't told about any. Details are in #309.

Fix

This is option A from #309. The runtime instructions now list the project's repos, in a new block after #307's path block. #307's wording is unchanged, and the new block is added in the same cases. For a project whose .code-workspace is in the home folder, the model now sees:

<project_repositories>
This project's repositories are:
- /home/<user>/<repo-1>
- /home/<user>/<repo-2>
- /home/<user>/Downloads/<repo-3>
- /home/<user>/<repo-4>
- /home/<user>/<repo-5>
The working directory, /home/<user>, is the project's workspace folder, not a repository. Folders under it that are not listed above are not part of the project, so there is no need to search it for more repositories.
</project_repositories>

The last sentence is left out when the working directory is itself one of the repos. That happens in isolated worktree runs, and when the .code-workspace file is in a repo's root folder. To tell these cases apart, the session now starts with the full repo list (a new optional repoRoots field on ProviderSessionStartInput). additionalRoots leaves the working directory out, so it can't be used for this.

Codex: needs a step when main is merged in

On this branch, newer Codex models don't receive T3 Code's instructions at all, including #307's path block and this list. GPT-6 Luna got Codex's built-in instructions instead. pingdotgg#13547 fixes this. It's on this fork's main, but not yet on multi-repo-workspaces.

When merging main in, two changes keep both blocks for Codex. I tested them in a local merge.

  1. In CodexSessionRuntime.ts, keep multiRepo in buildCodexTurnInstructions' input and pass it to buildCodexAdditionalContext, next to model, modelName and reasoningEffort. Without this, Codex silently loses both blocks.
  2. In CodexSessionRuntime.test.ts, change the multi-repo test to read additionalContext.t3_code_runtime.value instead of developer_instructions.

Size check: Codex allows about 4,000 bytes per entry. With five repos, T3 Code's entry is 3,113 bytes.

Testing

Fixes #309

Model: Claude Opus 5.5, harness: Claude Code.

LoganRupe and others added 30 commits September 24, 2026 16:50
Adds first-class support for multi-repo "workspaces" — a project that
spans several independent git repos (a `.code-workspace` as the source of
truth) without forcing them into a single monorepo. Checkpointing, turn/
thread diffs, VCS status, agent launches, @-mention search, file
previews, worktrees, terminals, and the Source Control UI all fan out
across every repo root, with isolated runs getting one worktree per repo.

Squashed from 36 phased commits during rebase onto upstream pingdotgg/main
(client connection architecture rewrite pingdotgg#2978 + server Services/Layers
flattening). Original commits preserved in branch multi-repo-workspaces-backup.

Squashed commits (oldest first):
  feat(project): multi-repo-root projects ("monorepo-like" without being a monorepo)
  chore(multi-repo-root): review fixes
  fix(multi-repo-root): repair 123-commit rebase drift
  fix(multi-repo-root): integrate scanGitRepos RPC with auth-scope feature
  fix(multi-repo-root): repair fork test failures
  chore: ignore .playwright-mcp tooling artifacts
  docs(multi-repo-root): design plan for multi-repo workspaces
  feat(multi-repo-root): .code-workspace as project source of truth (Phase 1b)
  feat(multi-repo-root): per-root checkpoint refs foundation (Phase 2a)
  feat(multi-repo-root): fan checkpoint capture out over all repo roots (Phase 2a)
  feat(multi-repo-root): aggregate turn/thread diffs across repo roots (Phase 2a)
  feat(multi-repo-root): fan checkpoint restore out over all repo roots (Phase 2a)
  feat(multi-repo-root): per-repo grouped turn-diff payload (Phase 2c)
  feat(multi-repo-root): render per-repo diff sections in DiffPanel (Phase 2c)
  feat(multi-repo-root): surface repoRoots on web Project model (Phase 2b)
  feat(multi-repo-root): grouped per-root VCS status hook (Phase 2b)
  feat(multi-repo-root): render per-repo git status in the chat header (Phase 2b)
  feat(multi-repo-root): don't flap thread branch across per-repo controls (Phase 2b)
  feat(multi-repo-root): launch agents with a per-root workspace manifest (Phase 3)
  feat(multi-repo-root): fan @-mention file search across every repo root (Phase 3)
  feat(multi-repo-root): resolve cross-root file previews in AssetAccess (Phase 3)
  feat(multi-repo-root): carry a per-root worktree map on threads (Phase 4)
  feat(multi-repo-root): fan worktrees out across every repo root (Phase 4)
  feat(multi-repo-root): resolve agent/diff/checkpoint to per-root worktrees (Phase 4)
  feat(multi-repo-root): remove every per-root worktree on thread delete (Phase 4)
  feat(multi-repo-root): hide bare *.git dirs from the file tree (Phase 5)
  feat(multi-repo-root): add filesystem.writeWorkspaceFile RPC round-tripping unknown keys (Phase 5)
  feat(multi-repo-root): add Manage folders dialog to round-trip the .code-workspace (Phase 5)
  feat(multi-repo-root): re-read .code-workspace on project load and reconcile roots (Phase 5)
  feat(multi-repo-root): browse files and resolve file links across all repo roots (pingdotgg#923)
  feat(multi-repo-root): consolidate per-repo git actions into one Source Control control
  feat(multi-repo-root): pick which repo to open a terminal in
  feat(diff): show all repos in multi-repo branch/working diff with repo filter
  fix(terminal): open per-repo terminal in that repo's worktree
  feat(diff): show working/branch diff for non-isolated multi-repo workspaces
  fix(diff): refresh diff panel on reopen to avoid stale cached patch

Follow-up details (post-rebase work folded in):
  - Branch/working diff fans out per repo with a repo-filter dropdown
    (All repos / per repo), replacing the single-repo computation; the
    base-ref selector is hidden in multi-repo mode since one base ref
    isn't valid across repos.
  - Non-isolated `.code-workspace` projects (no per-repo worktrees, a
    container workspaceRoot that isn't a git repo) diff each project
    repoRoot directly instead of running `git diff` in the container and
    reporting "no changes". The WS handler passes allowedRepoRoots from
    the shell snapshot and ReviewService accepts them in its
    workspace-boundary check, since these repos can live anywhere on disk.
  - Per-repo terminals open in the targeted repo's worktree (resolved via
    the thread's worktree map), falling back to the plain repo root.
  - The diff panel refreshes on reopen (useRefreshOnReopen) so the
    stale-while-revalidate atoms don't serve a stale cached patch after
    the panel is unmounted and remounted.
The multi-repo migrations were originally numbered 033-036 and shipped in
pre-rebase branch builds, so those slots were recorded in
effect_sql_migrations on every machine that ran one. The rebase renumbered
them to 037-040, but Migrator.fromRecord only runs migrations whose numeric
id exceeds the highest recorded id and never compares names -- so main's
real 033-036 (Settled/Snoozed/TitleRegeneration/Pinned) were skipped on
those databases and could never run again.

The result was a projection_threads table missing seven columns that
ProjectionSnapshotQuery selects. listThreads failed statement preparation
with "no such column: settled_override", which crash-looped the backend
before it could serve the client; the desktop window waited 30s for a
backend that never came up and showed an empty screen with no visible
error.

Migration 041 adds the seven columns if absent, which is equivalent to
letting 033-036 run -- each is a nullable TEXT ADD COLUMN with no index or
backfill. Healthy databases already have them and it is a no-op.

detectMigrationLedgerDrift compares the recorded ledger against the
manifest and names the migrations that were consequently skipped, so this
surfaces as a migration problem rather than a corrupt-looking database. It
runs after migrations so healing has already applied, and is wrapped so it
can never fail startup. It warns rather than errors: recorded names cannot
be corrected without rewriting the ledger, so drift is permanent even once
healed, and anything louder would fire on every boot of a database that is
actually fine.

Also documents at the manifest that migration ids are immutable once any
build has applied them, and that a rebase should append after main's
highest id rather than renumber.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The sidebar header artwork was reachable only on Nightly and Dev
builds: any other stage label resolved to no variant, and the
Appearance setting that controls it was hidden along with it, so
self-built clients (which resolve to Alpha) had no way to turn it on.

Fall back to the night sky for stages without their own artwork, and
let the environment pill carry an unrecognised stage label through
instead of dropping it, which is what un-hides the existing
artwork/pill/none setting on those builds.
"Open in" pointed every editor at the project's `workspaceRoot`, which for
a `.code-workspace` project is only the anchor directory holding the file.
VS Code opened it as a plain folder, so none of the repo roots appeared in
the tree -- the multi-root workspace the project is defined by was never
what got opened.

Pass the `.code-workspace` file alongside the anchor directory and pick
between them per editor. The choice is made server-side, in the launcher,
because the target depends on which editor the user picks from the menu
and that is not known when the picker renders. `supportsWorkspaceFile` on
the editor registry marks the VS Code family; Zed and JetBrains would show
the raw JSON and the file manager would hand it to the OS opener, so those
keep getting the directory.

Isolated runs keep getting their worktree. The fanned-out worktrees have
no workspace file of their own, and the project's would open the original
checkouts instead of the copies the thread is working in.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
New-worktree mode was unusable for any `.code-workspace` project. The base
branch selector ran its ref queries against `workspaceRoot`, which for
those projects is the anchor directory holding the workspace file and
usually not a git repo, so every query reported `isRepo: false` and the
selector came up empty. Sending then refused outright, by design, rather
than silently falling back to local execution.

Populating the selector alone would not have been enough. The server
compared each `repoRoot` against the client's `projectCwd` to find the
repo the chosen base ref belongs to; for a workspace-file project that
comparison never matched, so the base ref was dropped for every root and
each repo quietly branched off its own HEAD instead. `startFromOrigin`
went further and ran `git fetch` in the anchor directory, which fails and
aborts the whole send.

`resolveAnchorRepoRoot` picks the repo root that stands in for the project
in single-repo git flows: the anchor when the workspace file lists it,
else the first root in file order. Single-repo projects resolve to exactly
today's value. Both the selector and the worktree fan-out now go through
it, so refs list, the chosen base lands on the anchor repo while cousins
still branch off their own HEAD, and the origin fetch runs in a repo.

The selector's existing auto-default picks the repo's `origin/HEAD` once
refs load, so this works without the user opening the dropdown. Per-root
base selection still needs a wider `prepareWorktree` contract and is left
for follow-up.

Also repairs a stale assertion in the bootstrap worktree test: it still
expected the pre-fan-out `createWorktree` shape (`path: null`,
`baseRefName`) and had been failing on this branch.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Rebasing the multi-repo branch onto main picked up tests written against
the single-repo shapes: thread shells now carry a per-root `worktrees`
map, checkpoint diffs carry per-repo `groups`, and every worktree comes
from the fan-out (which supplies a placement path instead of the old
`path: null` / `baseRefName` pair).

The heal-skipped-columns migration also moved from 041 to 045, appending
after main's 037-040 rather than renumbering into slots main now owns, so
its test walks to 44 before healing at 45.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The rebase onto main moved the multi-repo migrations from 037-040 to
041-044, which recreates the drift the healing migration was written for.
Every machine that ran the branch between the two rebases recorded 37-40
under the multi-repo names, so main's real 037-040 -- TurnsKeysetIndex,
PinOrderKey, DefaultThreadEnvMode, FaviconPath -- were skipped and, since
the migrator only runs ids above the highest recorded one, never will.
ProjectionSnapshotQuery selects favicon_path and default_thread_env_mode,
so those databases crash-loop the backend exactly as before.

Healing them is just re-running them: all four already guard on a
PRAGMA table_info check or CREATE INDEX IF NOT EXISTS, so a second run is
defined behavior and a no-op on databases that ran them for real. That
keeps the repair in one place rather than restating their SQL.

Renamed to HealSkippedRenumberedMigrations since it no longer heals only
thread columns, and the id has not shipped under the old name.

Verified against a copy of a real drifted database: the migrator runs
42-45, skips 41 as already-recorded, and pin_order_key,
default_thread_env_mode, favicon_path and the turns keyset index all come
back.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The multi-repo work landed before sidebar v2 became the default, so all of
it sat in what is now LegacySidebar: the once-per-session `.code-workspace`
reconcile and the "Manage folders..." project context menu. The default
sidebar had no multi-repo awareness at all.

The reconcile moves to `useWorkspaceProjectReconcile`, mounted in
AppSidebarLayout so it runs under whichever sidebar is active -- and runs
at all, rather than only when a project row happens to render. The legacy
copy goes with it.

"Manage folders..." moves to the project settings page. The default sidebar
has no project context menu (its only two are thread-scoped) and already
routes rename/grouping/remove to /projects/$projectKey, so the folder
editor belongs in the same place, scoped to the selected checkout.

Thread rows resolved git status against `workspaceRoot`, which for a
`.code-workspace` project is the folder holding the file and usually not a
repo -- so branch-mismatch never fired and the PR pill went blank. They now
resolve against the anchor repo root. `repoRoots` is backfilled to
`[workspaceRoot]` for single-root projects, so this is a no-op outside
multi-repo workspaces and costs no extra queries. Isolated runs still report
the anchor worktree only; per-root fan-out in a list of rows is a separate
question.

Workspace-backed projects also get the palette's `.code-workspace` marker as
their favicon fallback, so they read as workspaces in the thread card, the
draft row, the hover card and the project picker. A project with its own
icon keeps it.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The diff panel probed git status in a single cwd, falling back to the
project's workspaceRoot. For a workspace-file project that is the anchor
directory holding the .code-workspace, which usually is not a repo, so
the probe returned isRepo: false. That rendered "this project is not a
git repository" and disabled the checkpoint diff query outright, so the
client never asked the server for diffs it already had. The per-repo
grouping below it was never reached.

Resolve the project fallback through resolveAnchorRepoRoot, the helper
the branch selector already uses, and stop gating the panel on the probe
when there are repo targets to diff: each one is a repo by construction.
Single-root projects are unchanged, and a genuinely non-repo project
still gets the message.

With the anchor fixed, the single-cwd branch preview would fetch a patch
the multi-repo view never renders, so it is disabled there. That left
the header refresh button dead, since the per-repo sections own their
queries, so refresh, window focus, and turn completion now bump a token
those sections watch.

Fixes LoganRupe#156
Rebasing onto main moved the multi-repo migrations from 041-045 to
042-046 so main's 041_AuthSessionClientConnection keeps its slot. Every
machine that ran the branch before this rebase has 41 recorded as
ProjectionProjectsRepoRoots, so main's 041 was skipped there and
auth_sessions is missing client_surface and client_app_version.

041 already guards on PRAGMA table_info, so the heal migration just runs
it alongside 037-040.
The mobile review screen diffed one cwd, the thread's anchor. For a
multi-repo workspace that is the container repo, which usually has no
changes, so Working tree and Branch changes silently dropped every other
repo's edits.

Move resolveDiffRepoTargets into client-runtime so web and mobile share
the targeting rule (an isolated thread without a worktree map never falls
back to the project checkouts), and have web import it from there. On
mobile, fan the diff preview out over every target through one derived
atom, fold the per-repo previews into the existing Working tree and
Branch sections as groups, and carry the server's turn diff groups the
same way. Files of a multi-repo diff are labelled with their repo folder
in the native list and in review comments. Single-repo threads keep the
single-cwd diff and render as before.

Verified on an Android emulator against a seeded three-repo workspace:
Turn, Working tree and Branch changes all list the non-primary repo's
changes.

Claude Fable 5 via T3 Code.
Working tree and branch diffs for a multi-repo thread showed 0 files in every
repo while the turn diff showed the changes. The thread's per-root worktree map
only reaches clients on the shell stream: `thread.meta-updated` is not a detail
event, so the detail snapshot keeps the empty map from creation and the diff
panel fell back to diffing the project's repo roots, which are the user's own
clean checkouts.

Take `worktrees` from the shell when merging, like `worktreePath`. Stop the
panel from ever falling back to repo roots for an isolated thread. Name the
repo a single-repo turn diff actually touched instead of the anchor worktree,
and render an unparseable per-repo patch raw instead of reporting "0 files".
The projection snapshot queries never selected the workspace_file
column and never mapped it, so workspaceFile silently dropped out of
every client-facing project shell. Open-in fell back to the folder,
the sidebar never showed the workspace icon, and Manage folders never
appeared (LoganRupe#185).

Select the column in the three project SELECTs and map it in
mapProjectShellRow plus the three inline OrchestrationProject
literals that build read models from the same rows.
The rebase onto upstream main moved the multi-repo migrations from
044-048 to 048-052, vacating four ids that upstream has since claimed:
044_ClearAutomaticProjectModelDefaults, 045_ProjectionProjectsAutoPull,
046_RepairAutomaticSettlementTimestamps and 047_ProjectionProjectIcon.
Machines that ran the previous branch build recorded 44-48 under the
multi-repo names, so the migrator will never run main's real migrations
for those ids: `projection_projects` stays without `auto_pull` and
`project_icon_json`, which ProjectionSnapshotQuery selects on every
project read, and the two data repairs never happen.

Extend 052_HealSkippedRenumberedMigrations to re-run 044-047 after the
033-036 column restore. All four are idempotent: 045 and 047 guard on a
PRAGMA table_info check, and the 044 and 046 repairs only match rows they
have not already rewritten.

046 also needed a guard of its own. It reads `projection_threads.settled_at`,
a column 033 adds, so on a database carrying the 033-036 drift it aborts
the boot at id 46 before the heal at 52 can restore the column. It now
returns early when the column is absent; the heal re-runs it once the
column is back, so the repair is deferred rather than lost.

Proven on a VACUUM INTO copy of the real ~/.t3/userdata database (ledger
ids 33-48 all recorded under branch names): after migrating, every column
the snapshot query reads is present and the ledger ends at 52.
Upstream's local-only base branch fallback (pingdotgg#8751) added a
remoteBranchExists probe to the worktree bootstrap, which the multi-repo
anchor test's GitVcsDriver layer did not implement.
Rebasing onto main moved the multi-repo migrations from 048-051 to
050-053, and the healing migration from 052 to 054. Machines that ran the
previous branch build recorded 48-52 under the multi-repo names, so main's
real 048_ProjectionThreadBranchPullRequest and
049_ProjectionThreadsActiveOrderKey were skipped and, since the migrator
only runs ids above the highest recorded one, never will.
ProjectionSnapshotQuery selects branch_pull_request_json and
active_order_key on every thread read, so those databases crash-loop the
backend.

Both are idempotent ADD COLUMN migrations guarded by a PRAGMA table_info
check, so healing them is just re-running them from 054.
Opening a multi-repo project from a remote environment emitted a
`vscode://vscode-remote/ssh-remote+<host><path>` link built from the
project's anchor directory, so VS Code showed one folder instead of the
multi-root workspace. Only the local-exec branch of OpenInPicker read
`openInWorkspaceFile`; the remote-links branch never consulted it.

No new URI form is needed. VS Code's protocol handler
(`getWindowOpenableFromProtocolUrl` in `src/vs/code/electron-main/app.ts`)
returns `{ workspaceUri }` instead of `{ folderUri }` whenever the path
ends in `.code-workspace`, so `buildRemoteOpenUrl` now runs the same
`resolveEditorTarget` the server uses and links the workspace file for
editors that support one. Every remote-capable editor is a VS Code fork,
so all of them get it.

Fixes LoganRupe#234.
Two repo roots in one turn diff that both changed the same relative path
produced two tree entries at the same path, and Pierre's PathStore threw.
The tree now builds from the per-repo groups the panel already renders,
with each root's files under a folder named for that root. A repeated
path is dropped instead of handed to the tree.

Fixes LoganRupe#240
Upstream claimed 050_ProjectionThreadPullRequests and
051_ProjectionThreadMessageContext, so the multi-repo migrations move to
052-055 and the heal to 056. Machines that ran the previous build recorded
50-54 under the multi-repo names, which skips both of main's migrations for
good: `projection_thread_pull_requests` never exists and
`projection_thread_messages` has no `context_json`.

The heal now re-runs 050 and 051 after 037-049. Both are idempotent: the
table and index use IF NOT EXISTS, the legacy link backfill inserts with OR
IGNORE, and 051 guards on PRAGMA table_info.

050 also hits the reverse hazard 046 did. On a database that skipped 042,
050 runs before the heal and selects `linked_pull_request_json`, which
aborts the boot. It now skips the backfill when that column is absent, and
the heal re-runs it once 042 has restored the column. The existing 042-043
case covers that path.

Verified against a VACUUM INTO copy of the live ledger (max id 54): 055
and 056 ran and the database passes integrity_check.
The rebase resolution keeps upstream's createWorktree call, with progress and cancel, for single-root projects. Only multi-repo projects fan out under <worktreesDir>/<projectId>/<threadId>. Restore upstream's assertion.
`t3 app` now accepts a .code-workspace file as well as a project directory. The CLI sends the file as workspaceFile and its parent as workspaceRoot; the desktop renderer resolves repoRoots through the existing filesystem.readWorkspaceFile query and creates the project the same way the command palette does.

Workspace projects opened from the CLI are titled after the file, matching the palette.
Upstream claimed 052_ProjectionThreadTitleState and
053_PullRequestFilesViewed, so the multi-repo migrations move to 054-057
and the heal to 058. Machines that ran the previous build recorded 52-56
under the multi-repo names, which skips both of main's migrations for
good: `projection_threads` has no `title_state_json`, which the snapshot
query selects on every thread read, and `pull_request_files_viewed` never
exists.

052 is a bare ADD COLUMN, so the heal guards it on PRAGMA table_info like
033-036. 053 uses CREATE TABLE IF NOT EXISTS and is re-run as is. Neither
reads a column an earlier skipped migration owns, so there is no reverse
hazard this time.

Verified against a VACUUM INTO copy of nauvoo's live ledger (max id 56):
057 and 058 ran, both objects exist, and the database passes
integrity_check.
Upstream's rewind guard (pingdotgg#12306) refuses a file restore unless the
checkpoint cwd is the thread's own worktree and no other thread or live
session owns a path inside it. It read a single `checkpointCwd`, which
the multi-repo revert replaced with one root per repo, so the guard no
longer compiled.

Run the guard for each checkpoint root. A root counts as the thread's own
when it is any of its per-repo worktrees, not just `worktreePath`, and
other threads contribute every worktree they own, or every repo root of
their project when they run on the checkouts, since a workspace file can
list repos outside its container.
New upstream fixtures build threads without `worktrees`, and the mobile
lazy file placeholder had no `repoLabel`. The no-origin bootstrap test
still asserted the fan-out worktree path, which single-root projects no
longer take; it now matches upstream's worktree call like its sibling.
DropdownMenuItem stayed exported from ui/menu but fell out of DiffPanel's
import list when upstream rewrote the block, and nodeSqliteClient's
layerMemory collapsed into layer({ filename: ":memory:" }).
…o UI

The rebase brought in upstream's UI-system work (ghost-muted, PopoverPopup
width, DialogPanel spacing) and the restyle ceiling that goes with it. The
branch's hand-styled triggers put the count at 651 against a ceiling of 628,
failing the gate. Routing them through the variants drops it to 616.
WorkspaceGitScan and WorkspaceFile each carried a copy of what
pathExpansion.ts already exports as expandHomePathWith.
…pe#294)

"Open in" on an isolated run opened the anchor worktree as a plain
folder, so the other repos were missing from the editor.

The worktree fan-out now writes a copy of the project's .code-workspace
into the per-thread directory, with each repo folder pointed at its
worktree and non-repo folders left at their original path. The client
opens that copy for fanned-out threads. Threads created before this
have no copy, so the launcher falls back to the directory when the
workspace file is missing.

Fixes LoganRupe#125

Co-authored-by: Logan Rupe <logzerroneous@gmail.com>
…Rupe#295)

A multi-repo isolated run based only the anchor repo on the chosen
branch; every other repo branched off whatever it had checked out, and
mobile's base-branch list was empty because it queried the directory
holding the .code-workspace.

prepareWorktree gains an optional repoBaseBranches list of per-repo
picks. The server bases each non-anchor repo on its pick, else its own
default branch (origin/HEAD), else its checkout, and "start from origin"
now fetches per repo. The web composer shows a "+N repos" menu next to
the anchor's base selector with a branch submenu per repo. Mobile lists
the anchor repo's branches and leaves the rest to the server default.

Fixes LoganRupe#113

Co-authored-by: Logan Rupe <logzerroneous@gmail.com>
LoganRupe and others added 5 commits September 24, 2026 16:51
The +N PR badge in the sidebar and composer now opens a right-panel tab
per pull request when they are unrelated (one per repo, say), and lists
them all on hover. Stacks still open the ordered Pull requests list.

The multi-repo Source Control popover reads each repo's worktree for an
isolated run instead of the original checkout, so each row shows the
run's PR. View PR passes the PR's URL, so a non-primary repo row opens
its own repository's PR rather than the same number in the primary repo.

Fixes LoganRupe#298

Co-authored-by: Logan Rupe <logzerroneous@gmail.com>
Upstream moved composer-strip triggers onto ComposerControl and made
shadcn/no-restyle a lint error. The per-repo base branch trigger now
uses ComposerControl like the branch selector beside it, and the diff
worktree tooltip drops its leading-tight override.
In a multi-repo project whose workspace root is a parent folder, chat file chips resolved repo-relative paths against the workspace root and failed to open, or opened a same-named file in another repo.

- Multi-repo sessions get runtime instructions to write absolute file paths; every provider adapter passes the flag.
- Chat file chips pass their owning repo root to the file panel, and skip the workspace-wide basename lookup when a root is known, for both open and reveal.
- The file tree reveals files and repo roots under their repo label; breadcrumbs name the owning repo using the tree's labels.
- A link to the workspace root or a repo root opens as a folder.

Fixes LoganRupe#302
When a project's repos sit inside its workspace folder, Claude Code leaves
them out of the environment block it shows the model, and Cursor, Grok,
OpenCode and Antigravity get no repo list at all. The model then treats the
workspace folder as the project and searches it for repos.

The multi-repo instructions now list every repo root, and say that the
working directory is the workspace folder, not a repo, when it is not one of
the roots. The session start input carries the full root list so adapters
can tell whether the working directory is a repo root.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L labels Sep 26, 2026
@LoganRupe

Copy link
Copy Markdown
Owner

Thanks for picking this up so quickly, and for testing it across providers. The Codex write-up is a good catch too. I wouldn't have spotted that newer models were skipping our instructions on this branch.

I checked out the branch: the tests pass and the server typecheck is clean. Two things before I merge:

  1. Non-git folders. WorkspaceFile.ts only keeps folders that have a .git in repoRoots. So if a .code-workspace lists a plain folder like docs/, the last sentence tells the model it isn't part of the project. "Folders under it that are not listed above" can also read as covering api/src inside a listed repo. Could you narrow it to something like "Other repositories under it are not part of this project, so there is no need to search it for more"?

  2. Codex. Rather than leave the merge steps in the PR body, I'm rebasing multi-repo-workspaces onto main now, so fix(server): newer Codex models get T3 Code's instructions again pingdotgg/t3code#13547 will be on the branch. Once that's pushed, could you rebase onto it and make the two Codex changes you described (multiRepo into buildCodexAdditionalContext, and the test reading additionalContext.t3_code_runtime.value)? That way the PR carries its own fix, and a test catches it if Codex ever loses the block.

I'll comment here once the branch is updated.

@LoganRupe
LoganRupe force-pushed the multi-repo-workspaces branch from 44b00df to 3f17cf7 Compare September 26, 2026 20:40
@LoganRupe

Copy link
Copy Markdown
Owner

multi-repo-workspaces is now rebased onto main, so pingdotgg#13547 is on the branch. This PR will need a rebase onto it, and GitHub shows it as conflicting until then.

One change from what I asked earlier: I had to resolve the #307 commit against pingdotgg#13547 during the rebase. Both Codex changes you described are already on the branch. multiRepo goes into buildCodexAdditionalContext, and the multi-repo test reads additionalContext.t3_code_runtime.value. So for Codex you only need to carry your MultiRepoWorkspace type through that call instead of the boolean. The narrower "not part of the project" wording is still the one open item.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants