Skip to content

feat(projects): multi-repo projects (port of pingdotgg/t3code#26254) - #12

Open
jappyjan wants to merge 77 commits into
feat/linearfrom
t3/multi-repo-projects
Open

jappyjan wants to merge 77 commits into
feat/linearfrom
t3/multi-repo-projects

Conversation

@jappyjan

@jappyjan jappyjan commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner

Draft stack PR on the fork, not for upstream: multi-repo projects as the third fork feature branch (feat/multi-repo-projects), a port of pingdotgg#26254 (source e9aac9d5f9) onto feat/linear, plus Pipelines and Linear glue. The numbered migrations 073/074 are replaced by the idempotent fork schema module MultiRepoSchema.ts.

QA fixes on top of the port:

  • A plain wrapper folder (non-Git home) had no "Edit from here" and the server refused the rollback because its root checkpoint is missing. Rewind now works for it and restores only the tracked repos.
  • Removed a stray storageCleanup.test.ts-e file with conflict markers that came in with the port.

Review fixes:

  • Rewind: a file rewind past a missing root checkpoint is allowed only when the home is not a Git repo. Before this, a Git home whose baseline capture failed was silently skipped, and only the repos were restored.
  • Pipelines: "Retry from step start" is never saved for a step inside a multi-repo task folder. The check now also asks the task folder store, because repo scopes are recorded best effort. The single-repo error text is unchanged.
  • Git status: after each turn the server refreshes the local Git status of every repo the run included, so web, mobile and other clients all update. This replaces the earlier client-only resubscribe, which only re-read the server's cache.
  • Rewind isolation: the task folder is found by its recorded path, so a worktrees directory reached through a symlink no longer refuses every rewind.
  • Web: the Repos chip selection is cleared after a composer pipeline start.
  • Mobile: a task folder's home lists branches from its own worktree, so its branch shows as current (upstream fix(mobile): mark the thread worktree's branch as current pingdotgg/t3code#23436 behaviour).

Release gates (push as feat/multi-repo-projects, Air runbook, upstream comment) are still pending.

🤖 Generated with Claude Code

jappyjan and others added 30 commits November 3, 2026 01:22
A pipeline is a saved sequence of agent steps (provider, model, options,
permissions, skills, prompt template). Starting one creates a parent thread
that shows progress and one child thread per step; each step's final answer
feeds the next step's prompt. Steps auto-advance, can pause before a step,
and can be continued, retried, skipped or cancelled. Includes a shipped
"Feature delivery" starter, web and mobile editors and run views, and
sidebar nesting of step threads under their pipeline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds list_pipelines, start_pipeline, pipeline_status, continue_pipeline and
cancel_pipeline. Starts run as the agent with its mode limits; plan-mode
callers are refused, and continue re-checks the remaining steps' runtime
modes so an agent cannot escalate a user-started run. t3_thread_read now
reports a thread's pipeline. Raises the MCP tool-list budget by 1971 bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Runner: steps only borrow a workspace a thread actually recorded, actions
and their follow-ups are uninterruptible, retry/skip stop the old step
thread, launches persist their workspace and prompt for recovery, cancel
is atomic, recovery watches parent deletion first, user-facing errors no
longer leak causes, starters seed with the server's default runtime mode,
forks drop the pipeline field, and a live-runs stream lists active runs.

MCP: agents cannot pass a user's pause on a user-started run, and
start_pipeline defaults to the caller's checkout.

Web and mobile: fixes for file drops on step rows, a vanished selected
pipeline, lifecycle actions on step rows, double-fired run actions,
unsaved editor changes, and mobile starts that dropped the pipeline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ovider checks

Clients subscribe to live pipeline runs once per environment: a paused
run marks its parent as needing input and nested steps show their real
step names. Web and mobile share one start-retry key helper, starter
templates swap unavailable providers for the default model and editors
block saving unavailable providers. Validation limits come from
contracts. The user guide is trimmed and mentions agent-started runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The run view now leaves room for the thread details panel and no longer
draws an empty composer border. The pipeline picker menu is non-modal so
the composer stays visible while it is open, a running step shows one
status instead of two, and the draft footer hides the permissions control
while a pipeline is selected since each step has its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Retrying a failed step started a new thread in the shared worktree as-is, so it
began from whatever the failed attempt left on disk.

Retry now takes an optional restoreWorkspace flag. The first attempt's baseline
checkpoint (taken before its first provider turn) is saved per step, and a
restore-retry puts the worktree's files and index back to it, after saving the
failed state under refs/t3/pipeline-runs/. Commits stay on the branch. Restore
only runs in T3-managed worktrees, waits for the old agent to stop, and leaves
the run paused if anything fails. Web and mobile offer both choices when a step
can be restored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A step thread that was steered has one root checkpoint scope per run, so the
first match could be a later run's baseline that already holds the step's own
changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A step that watched its PR stayed parked as waiting on checks, so the
finished pipeline stayed on the Working shelf and the step would wake
after the pipeline had moved on. Steps hand back when their turn ends,
like subagents: refuse the watch in the MCP tool and the orchestrator,
and give steps the child PR report hint when they link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The migrator skips every id up to the highest recorded one, so a
Pipelines id recorded by this branch would make the next upstream
migration with that id look already applied, and startup would refuse
the database. Create the idempotent tables after the numbered
migrations instead, on every start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Restore the EnvironmentId import upstream dropped, and keep the mobile
pipeline screens compilable by the React Compiler: no try/finally
without catch, and memo dependencies it can preserve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…step

A step's turn ending was treated as its result, so steps that stopped short
(groundwork PR missing, commit hook still running, signing agent down) moved
the run on and later steps worked on nothing.

- Steps now call handoff_pipeline_step with their output. Every prompt gets
  an appended instruction: hand off when done, ask with the question tool
  when something the step builds on is missing.
- A turn that ends without a handoff pauses the run (awaiting_handoff) and
  keeps the last message. A later handoff from the step's thread resumes the
  run; Continue takes the last message instead.
- Rerun from step N (paused, cancelled or completed runs) resets that step
  and every later one, keeping earlier outputs. Exposed over RPC, the web
  run view and the rerun_pipeline_step MCP tool.
- The Feature delivery starter checks what each step builds on, and saved
  copies that still match an older starter version are upgraded on start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pipeline parents in the sidebar start collapsed and show one bar segment per
step, coloured by status, with a done count. The chevron (or ArrowRight/Left)
shows the step rows; a started step's segment opens its thread. A collapsed
pipeline still shows the open step. Expansion is remembered per pipeline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A run pauses when a step's turn ends without a handoff, and stays paused
while that step works or monitors again. The sidebar treated every paused
run as waiting on the user, so these pipelines sat in the inbox with a
needs-input status. A paused run now waits on the user only while its
current step is not busy, for shelf placement, row status and progress bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n's coordinator

* feat(server): revise a pipeline step with feedback and make the parent thread a coordinator chat

Adds PipelineService.reviseStep (same-thread revision, later steps pinged in their own threads),
the revise_pipeline_step MCP tool, the pipelineRuns.revise RPC, persisted revisions, coordinator
authority to continue a user's pause, and the coordinator instructions sent with the parent's
first provider turn.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* feat(web,mobile): coordinator chat, Coordinator tab and Send feedback for pipelines

The pipeline parent page is now a coordinator chat with the run view pinned above it, per-step
feedback with revisions listed, a Coordinator right-panel tab and header button in step threads,
and Send feedback on messages and selected text. Mobile gets the same flows. Adds MCP eval cases
and user docs.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* test: cover revisions in the MCP toolkit fixture and revise permissions

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(web,server): coordinator chat keeps the run title and the composer survives a Suspense reveal

- A pipeline parent's first message no longer retitles the run via titleSeed.
- ComposerPromptEditorTiptap no longer throws when a Suspense boundary re-runs
  its layout effects against an editor useEditor already destroyed. Opening the
  Coordinator tab crashed the app on first mount and on every reload after.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(web): feedback dialogs refuse notes over the prompt limit; test the coordinator preamble rules

- Both feedback dialogs bypassed the composer's length check, so a 130k note
  went to the coordinator unchecked. They now show the composer's message and
  disable Send.
- The coordinator preamble decision lives in pipelineCoordinatorNote with a
  unit test; the provider turn start only calls it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(web,mobile): the coordinator header no longer repeats the step name

"Step 2/4 · QA · Running step 2 of 4 · QA" became "Step 2/4 · QA · Running"
through a shared pipelineRunStateLabel.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* refactor(clients): share feedback routes and revision author labels; never-run skipped steps can't be revised

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(server): revisions can't race a moving run, the coordinator can steer mixed-mode runs, retry keeps feedback

- reviseStep fails with "The run moved on" when the step it stopped is no longer current
- the run's coordinator is not limited by later steps' runtime modes; other agents are also
  checked against step threads the user raised, and a step's delegated threads can't revise it
- retry after a revision relaunches with the feedback; archived step threads relaunch fresh
- a stop that lands while a revision message is in flight stops the new turn
- coordinator and agent feedback is sent as the agent, not the user
- downstream pings only call outputs from the revised step onward new
- never-run (gate-skipped) steps can't be revised

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(web): the coordinator tab no longer takes over the step page's shortcuts and dialogs

The embedded coordinator chat stands down like a background thread: no window shortcuts,
paste/focus grabbing, menu actions, global dialog hosts or right-panel state of its own.
Also: quiet shell + memo for the panel, error and missing-parent states, a toast when the
coordinator send fails, clipped selection quotes, an Open action on the revise toast,
shared route list, run actions visible while the pinned run view is collapsed, and the
parent row accepts file drops.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(mobile): visible Send feedback on assistant messages, confirmation and length checks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(server): a superseded revision turn is stopped even after a cancel; revise waits for a launching step; chained revisions pass every revised output; a revised first step keeps its workspace

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(web): page shortcuts still work from the coordinator tab, and it no longer stashes, previews, answers or focuses for the step

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Upstream's React Compiler coverage rejects try/finally, so the mobile
feedback sheet resets its sending state without one. The MCP tool list
budget is re-measured for upstream's new tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The parent thread no longer prefixes the pipeline name. It starts with the
input's first line and gets an AI-generated title from the run's input, using
the same title generation as normal chats (new `titleSource` on
thread.metadata.update).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pipelines in the mobile thread list start collapsed, like the web sidebar. The
parent row shows one bar segment per step, coloured by status, with a done
count, and only the open step stays listed. Tapping the bar, or the VoiceOver
action, shows or hides the steps. Expansion is remembered per pipeline in the
mobile preferences.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… runs

A handoff followed by a turn that ends with pending background tasks or
active subagents left the run waiting on that work, possibly forever.
handoffStep now fails, naming the work, until the agent stops or finishes it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…f branch

Review follow-up to the handoff guard. handoffStep now picks its branch once
(resume or record) and guards before acting, so a new branch cannot skip the
guard. The refusal names task IDs (what TaskStop/KillShell take) and subagent
titles, and the read skips settled subagents.

Tests now cover refusals on the awaiting_handoff and awaiting_continue branches,
advance the clock so the updatedAt check can fail, and use the real
completionDelivery shape. The user guide says a step can't hand off while its
background commands run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Upstream's side chats and the Coordinator tab both added an embedded flag to the chat
view and right panel actions; keep one. Mobile pipeline rows keep the regenerating-title
label. Tests that build the MCP toolkit provide PipelineService, and layer chains past
pipe's 20 arguments are split. The context budget counts the pipeline tools again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clients that get only the 25 newest settled threads lost a finished
pipeline's parent once it aged past that cut, while its unsettled steps
were still sent. Without a parent to nest under, every step listed as a
top-level chat. The shell trims now keep a run's parent while they keep
any of its steps.

Provider subagents also copied their parent step's pipeline tag, so they
counted as steps of the run. They no longer inherit it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The composer skipped applying the cursor on a freshly mounted editor,
because Tiptap only sets isInitialized after a timeout. Guard against
destroyed editors with isDestroyed instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Upstream's new MCP service tests build the service without PipelineService; give them the
same empty mock the other tests use. Upstream's new mobile thread list test reaches the
pipeline runs atom, so it mocks state/server and passes the pipeline inputs. The context
budget counts the pipeline tools again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, linear-issue context

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
API-key-only Linear integration: issue-tracker packages, LinearService with
caches and write-back reactor, linear_issue_links schema, /linear drill-down
page, /settings/linear, start-chat and run-pipeline-from-issue flows.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…atus

The keep-previous wrapper had no refresh hook, so command-triggered refreshes only re-derived the wrapper and never re-ran the query, leaving the settings card stale until reload. Connect also refreshed the status under its {apiKey} input instead of {}.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
MenuGroupLabel was rendered outside a MenuGroup, which Base UI rejects.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tion

The parent layout route has no validator, so its raw params were merged under the validated Linear search and a value like priority=abc reached the filter menu and crashed it. The route validator now unsets every key it owns; a single hand-typed priority is accepted.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nput limit

Toggling an include used to discard the edit. Includes are now locked once the text is edited, with a Reset edits button, and Start is blocked with a note when the text exceeds the provider input limit.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
jappyjan and others added 16 commits November 3, 2026 01:33
Link handlers read Linear status at click time, but only sidebar rows kept it
mounted, so with the sidebar off-screen the status never loaded and every
Linear link fell back to the browser. Mount it once at the app root.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Desktop: a Linear link that launches the app waits for the Linear status
  instead of falling through to the browser panel.
- Web: a link clicked on the Linear page keeps the page's scope and filters,
  and the list highlights an issue opened by identifier.
- Mobile: keep Linear status loaded from the app root, like web, so the
  first tap on a link routes in-app.
- Simplify the modifier checks; tests prove Linear routing ignores the PR
  setting and blocks the browser's own navigation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tion

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Web and mobile get a Machine control with Auto balance. Write-back is recorded on the run's
machine when it has the same Linear workspace connected, else on the primary with one static
comment. Activity merges matching machines and opens runs on the right one.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…r dialog

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ain in comments

Servers now advertise linearRemoteLinks, so clients can tell an older primary
(which would drop the remote field and churn on a run it cannot read) from one
that stores the run's machine. Pipeline and machine names in the one-off remote
comment are flattened to one line with no brackets, like chat titles. The
reactor guard for remote links is dropped: those links never use the pipeline
comment mode the reactor lists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Run in the chosen project on the primary when one repo holds several projects.
- Wait for both Linear statuses before deciding write-back; Start stays
  disabled meanwhile, so a quick start no longer falls back by mistake.
- Skip write-back and say so when the primary predates remote links.
- Clamp the machine name to what the link schema accepts.
- Keep the shown pipeline by id, then name, across machines (including the
  default), allow picking a second same-named pipeline, and mark one Last used
  on mobile too.
- Auto waits for the pipeline list before judging provider readiness.
- Web: choosing Auto again re-checks, as in the composer; readiness is read
  through a keyed atom so provider pushes do not re-render the dialog.
- Live runs stream only for machines with unread remote runs.
- Remote runs show a run icon and 'Other machine' instead of a chat icon and
  'Idle'; mobile thread chips key fallback runs by their machine.
- The composer and the dialog share one provider readiness check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…primaries

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…quested pipeline id

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Server settings now read the managed policy, so the Linear service tests provide its test
layer. Upstream's new plan follow-up composer tests pass the Linear contexts the snapshot
carries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Projects with several repos get per-repo review, checkpoints and worktrees.

Source: pingdotgg#26254 (commit e9aac9d,
"feat(projects): projects with several repos get per-repo review and worktrees").
Ported onto feat/linear and resolved toward current upstream APIs. The PR's numbered
migrations 073/074 are not carried here; the schema lives in persistence/MultiRepoSchema.ts
(next to PipelineSchema.ts) with the same column, table and index names.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…s task folders

- Pipelines: a workspace with repository scopes has no restorable step start, so
  "Retry from step start" is refused (never a partial restore); "Retry with current files" works.
- Linear/branch names: a requested branch gets one free name across the home and every planned
  repo, so the `-2`, `-3` suffix is the same in the whole task folder.
- Composer: starting a pipeline passes the Repos chip selection like a normal send.
- Fixes for the port: test fixtures for trackedFolders, checkpoint/antigravity tests, budget.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tracked repos

A plain (non-Git) home records a missing root checkpoint, so the web never showed
Edit from here and the server refused the rollback. Treat a missing root as
restorable when tracked repos hold ready checkpoints, and restore only those repos.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…es files

Only the home repo's status was refreshed on a workspace mutation, so tracked
repos kept showing +0 -0 until reloaded.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL labels Nov 3, 2026
JappyMondo and others added 7 commits November 3, 2026 15:04
…out a snapshot

The plain-home rewind fix accepted any missing root checkpoint once a thread had repo scopes. A Git home whose baseline capture failed is also recorded as missing, so restoring files reset the repos and silently kept the home's later edits. Restore files past a missing root only when the home is not a Git repo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…epo task folder

Repository scopes are recorded best effort, so a step whose scope setup failed still saved a home-only start snapshot and Retry from step start restored one repo of several. Also ask the task folder store, and keep the single-repo error text unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a turn

Turn end refreshed only the root folder's local status. The client-side resubscribe added for tracked repos only re-read the server's cached status, so it is replaced by refreshing each repository scope at finalization, which also reaches mobile and other clients.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing rewind isolation

Task folder roots are stored unresolved but were looked up with the realpath of the scope, so a worktrees directory reached through a symlink refused every file rewind in a task folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…line

The pipeline path kept the selection, so the next New worktree launch from the same draft reused it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps upstream's pingdotgg#23436 behaviour for the home repo of a task folder, so its worktree branch shows as current.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Git status

Calling the full workspace refresh per repo rescanned search indexes twice and ran pull request lookups inline at turn end. Repo scopes also include repos earlier runs used. Refresh the local status of the repos this run included; the root refresh and the forked PR refresh already cover the rest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jappyjan
jappyjan marked this pull request as ready for review November 3, 2026 14:25
@jappyjan jappyjan added the agent-context-increase Accept agent context growth label Nov 3, 2026
@jappyjan
jappyjan force-pushed the feat/linear branch 3 times, most recently from 71d3807 to 74e1199 Compare November 4, 2026 04:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-context-increase Accept agent context growth size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants