Parallel worktree sessions: isolated runs, per-project setup commands, and activity indicators#19357
Conversation
Sessions were get-or-create by resourceId alone, so multiple Intake runs over the same project resolved to one shared server-side session: each new run repointed the workspace and clobbered the previous run. Add an optional session scope so callers can address independent sessions over the same resource (one per git worktree): - core: registry keyed by resourceId + scope; getSessionByResource and setResourceId are scope-aware - server: session routes accept a sessionScope query param and qualify the stable session id with it - client-js: AgentControllerSession carries scope on every request - web: client cache and session-derived query keys include the worktree projectPath; hooks/providers/mutations pass it through, and useStartFactoryRun derives it before createThread/send Unscoped callers behave exactly as before. Co-Authored-By: Mastra Code (anthropic/claude-fable-5) <noreply@mastra.ai>
…sions # Conflicts: # mastracode/web/src/web/ui/domains/workspaces/hooks/__tests__/useWorkspaces.msw.test.tsx
…thread Bringing a brand-new worktree scope online seeds the session with an empty untitled thread (core creates one when no thread matches the scope tags), and the Factory run then created a second, titled thread — so every Investigate/Review click showed the run thread plus an "Untitled" sibling in the sidebar. The run now claims the seeded thread by renaming it when it is still fresh (no title, no messages), and only creates a new thread when the session resumed a real one. Co-Authored-By: Mastra Code (anthropic/claude-fable-5) <noreply@mastra.ai>
Agents were starting in freshly forked worktrees with no dependencies installed, so their first actions were always install/build churn. Each GitHub project can now store a setup command (e.g. "pnpm i && pnpm build") that runs inside every newly created worktree before any agent execution. - schema: nullable setup_command column on github_projects (idempotent ALTER TABLE ... IF NOT EXISTS migration) - server: GET/POST /web/github/projects/:id/settings to read/save the command; worktree creation runs it via runWorktreeSetup() and returns a 400 setup-failed error (with trailing stderr) when it exits non-zero; reused worktrees skip setup - client: fetchProjectSettings/saveProjectSettings services plus useProjectSettingsQuery/useSaveProjectSettingsMutation hooks - ui: Settings > General > "Worktree setup" section with one draft field per GitHub project; saving a blank field clears the command Co-Authored-By: Mastra Code (anthropic/claude-fable-5) <noreply@mastra.ai>
…transcript Runs were invisible outside the live SSE stream: reloading a thread page mid-run showed no working indicator, and the workspace sidebar gave no hint that another worktree's agent was busy. - server: include running in the session state response and add a non-creating GET .../sessions/:resourceId/running peek so activity can be polled without seeding sessions for idle worktrees - client-js: expose the running flag on session.state() and a session.running() peek - web chat: fold isRunning from display_state_changed into the transcript, hydrate it from the state snapshot on load/reconnect, and fall back to connection state so the working indicator survives page loads mid-run - web workspaces: poll each worktree's scoped session and render a pulsing activity dot on rows whose agent is working Co-Authored-By: Mastra Code (anthropic/claude-fable-5) <noreply@mastra.ai>
…nfig Files brought in by the scoped-sessions merge weren't formatted with the root prettier settings. Formatting-only, no behavior change. Co-Authored-By: Mastra Code (anthropic/claude-fable-5) <noreply@mastra.ai>
Multiple threads inside one worktree don't fit the workspace model — a worktree is one unit of work. Nesting the multi-thread list (titles, rename, clone, new-thread) under every workspace row added noise and made it easy to scatter runs across stray threads. The thread list now only nests under the main-branch workspace; feature worktrees show no thread list, and the workspace row itself is the entry point (selecting it resumes the worktree's latest thread or creates its single one). Local projects keep the flat multi-thread list. Co-Authored-By: Mastra Code (anthropic/claude-fable-5) <noreply@mastra.ai>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
🦋 Changeset detectedLatest commit: aad926d The changes in this PR will be included in the next version bump. This PR includes changesets to release 23 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
PR triageLinked issue check skipped for core contributor @abhiaiyer91. PR complexity score
Applied label: Changed test gateChanged tests failed against the base branch as expected. Label: |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
WalkthroughAgent Controller sessions now support independent ChangesAgent Controller session isolation
Project worktree setup commands
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
| worktreePath: string, | ||
| command: string, | ||
| ): Promise<void> { | ||
| const result = await sh(sandbox, `cd ${shellQuote(worktreePath)} && { ${command}\n}`); |
There was a problem hiding this comment.
P1: User-controlled setupCommand executes arbitrary shell code without validation
User-configured shell command is interpolated directly into sh() without content validation.
Add command validation, whitelist allowed binaries, or restrict setup command configuration to admins.
AI prompt
Check if this security scanner issue is valid. If so, understand the root cause and fix it. If appropriate, update or add tests. Keep the change focused and preserve intended behavior.
<file name="mastracode/web/src/web/github/sandbox.ts">
<violation number="1" location="mastracode/web/src/web/github/sandbox.ts:857">
<priority>P1</priority>
<title>User-controlled setupCommand executes arbitrary shell code without validation</title>
<evidence>The runWorktreeSetup function in sandbox.ts interpolates a user-configured command (stored per-project via /web/github/projects/:id/settings) directly into a shell string: sh(sandbox, `cd ${shellQuote(worktreePath)} && { ${command}\n}`). The settings endpoint only validates type (string|null) and length (max 2000), but does not whitelist commands, escape shell metacharacters, or restrict dangerous binaries. Any authenticated project member can persist a malicious command that executes automatically in the sandbox whenever a new worktree is created.</evidence>
<recommendation>Add content validation for setupCommand before execution. Options include: (1) whitelist allowed commands/binaries and reject anything else; (2) parse commands with a restricted-shell grammar; (3) require org-admin or elevated privileges to set a setup command; (4) run the setup step in a more restrictive sandbox (e.g., deny network egress, limit filesystem scope). At minimum, reject shell metacharacters like ;, |, &, $(), `, and redirection operators.</recommendation>
</violation>
</file>
There was a problem hiding this comment.
Addressed in 43fc6aa. Context on the finding:
Arbitrary shell is the feature, not a bug — setupCommand exists to run project bootstrap like pnpm i && pnpm build, so a binary whitelist would defeat its purpose.
Trust boundary: the command is only configurable by authenticated org members (settings route is gated by resolveOrgTenant + org-scoped project lookup), and it executes exclusively inside the project's isolated sandbox — the same environment where org members already run arbitrary shell via the agent's command tool. It never runs on the web server host, so it grants no privilege beyond what sandbox access already provides.
Hardening added: the settings route now rejects control characters (except newline/tab) on top of the existing type and 2000-char length validation, and runWorktreeSetup() documents the security model. Regression tests cover the rejection and the legitimate multi-line case.
There was a problem hiding this comment.
The developer’s explanation is reasonable: arbitrary shell execution is an intentional feature scoped to a trust boundary where authenticated org members already have equivalent sandbox access via the agent command tool, so this does not constitute a privilege escalation. The added hardening—rejecting non-printable control characters, documenting the security model, and adding regression tests—provides satisfactory defense-in-depth while preserving intended behavior.
Worktrees stay single-conversation, but hiding the thread list entirely removed useful context. Restore the nested thread list under the active worktree row so its title is visible, while keeping it read-only there: no New thread button and no rename/clone/delete actions. Main-branch and local projects keep the full multi-thread controls. Co-Authored-By: Mastra Code (anthropic/claude-fable-5) <noreply@mastra.ai>
…lists A feature worktree holds a single conversation, so the "Threads" label and count added noise above the one title. Hide the whole header in read-only mode; main-branch and local projects keep it along with the New thread button. Co-Authored-By: Mastra Code (anthropic/claude-fable-5) <noreply@mastra.ai>
CI validates that generated files are committed; the new GET /agent-controller/:controllerId/sessions/:resourceId/running route was missing from the CLI route metadata. Co-Authored-By: Mastra Code (anthropic/claude-fable-5) <noreply@mastra.ai>
… model Address the security-scan finding on setupCommand execution: reject control characters (except newline/tab) at the settings route so escape sequences and NULs can't reach the sandbox shell or spoof logs, and document why arbitrary shell is intentional — the command is only configurable by authenticated org members and runs exclusively inside the project's isolated sandbox, the same environment where org members already run arbitrary shell through the agent. Co-Authored-By: Mastra Code (anthropic/claude-fable-5) <noreply@mastra.ai>
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
mastracode/web/src/web/github/routes.ts (1)
823-855: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winFailed setup can get skipped forever on retry.
If
runWorktreeSetupthrows aftergit worktree add, the checkout stays on disk but nogithubWorktreesrow is written. The next request for the same branch sees the existing.gitfile, marks the worktree as reused, and never reruns setup. Track setup completion separately fromreused, or rerun setup when no persisted row exists yet.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@mastracode/web/src/web/github/routes.ts` around lines 823 - 855, The worktree creation flow around ensureWorktree and runWorktreeSetup must rerun setup after a prior setup failure. Track whether the branch already has a persisted githubWorktrees row, and invoke runWorktreeSetup when that row is absent even if result.reused is true; retain the existing behavior for newly created worktrees and persist the row only after successful setup.mastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerStateMutations.ts (1)
40-74: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winScope the connection invalidation
useAgentControllerSessionInitanduseAgentControllerSessionSynckey connection state withqueryKeys.agentControllerConnection(..., projectPath), but both switch mutations invalidate['agent-controller', agentControllerId, 'connection', resourceId]. That broad key can refetch unrelated worktree connections for the same resource; use the scoped helper here instead.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@mastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerStateMutations.ts` around lines 40 - 74, The connection invalidation in useSwitchAgentControllerModeMutation and useSwitchAgentControllerModelMutation is broader than the session connection key. Replace each inline connection query key with queryKeys.agentControllerConnection(args.agentControllerId, args.resourceId, args.projectPath), preserving the existing session invalidation.mastracode/web/src/web/ui/domains/workspaces/hooks/useWorkspaces.ts (1)
123-154: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winCascade thread deletion may run against the wrong worktree's session scope.
threadSessionis a session scoped to whatever worktree is currently selected in the caller (WorkspacesSection.tsx'ssession, derived fromderiveProjectPath(activeProject)), butmutationFnuses it to list/delete threads tagged forworktree.worktreePath— the worktree actually being deleted, which can be a different, non-selected worktree (itsonDeletehandler is enabled independent of selection). Elsewhere in this PR (openWorktreeThread,useAgentControllerThreads,useWorkspaceActivity), every worktree-scoped call explicitly creates a session bound to that specific worktree before callinglistThreads/running, which impliesscopedoes affect these session-bound requests. If that holds here too, deleting a non-active worktree could silently list 0 threads and skip cleanup, leaving orphaned threads after the worktree checkout is already gone.Consider deriving the session from
worktree.worktreePathinsidemutationFninstead of trusting a caller-supplied, statically-scoped session.🔧 Suggested direction
export function useDeleteWorkspaceMutation( project: Project | null | undefined, - threadSession: WorkspaceThreadSession | null | undefined, scope?: AgentControllerThreadsScope, ) { const { baseUrl } = useApiConfig(); const queryClient = useQueryClient(); const { toast } = useToast(); return useMutation({ mutationFn: async (worktree: Worktree) => { if (!project?.githubProjectId) throw new Error('No GitHub project selected'); await deleteWorktree(baseUrl, project.githubProjectId, worktree.branch); // Cascade: delete the threads scoped to this worktree specifically, // not whatever worktree happens to be active in the caller. + const { session: threadSession } = createAgentControllerClient({ + agentControllerId: scope?.agentControllerId ?? '', + resourceId: scope?.resourceId ?? '', + scope: worktree.worktreePath, + baseUrl, + }); if (threadSession) { for (let round = 0; round < 20; round++) {To confirm whether
scopeactually affectsAgentControllerSession.listThreads's underlying request (which would confirm this is a real bug rather than a harmless no-op), it would help to inspect the client-js implementation:#!/bin/bash ast-grep outline client-sdks/client-js/src/resources/agent-controller.ts --items all --type class,method --match 'AgentControllerSession|listThreads|deleteThread|base|running'🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@mastracode/web/src/web/ui/domains/workspaces/hooks/useWorkspaces.ts` around lines 123 - 154, Update useDeleteWorkspaceMutation so the cascade uses an AgentController session scoped to worktree.worktreePath inside mutationFn, rather than the caller-provided threadSession derived from the selected worktree. Reuse the existing session-construction pattern used by openWorktreeThread, useAgentControllerThreads, or useWorkspaceActivity, while preserving the current listThreads/deleteThread loop and optional-session behavior.
🧹 Nitpick comments (2)
mastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerThreadMutations.ts (1)
14-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider deduplicating
toClientArgsacross the agent controller mutation hooks. The sameprojectPath→scopemapping is repeated inuseAgentControllerGoalMutations.ts,useAgentControllerRunMutations.ts,useAgentControllerStateMutations.ts, anduseAgentControllerThreadMutations.ts. A shared helper next tocreateAgentControllerClientwould reduce drift if the scoping contract changes again.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@mastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerThreadMutations.ts` around lines 14 - 22, Deduplicate the repeated argument mapping by introducing a shared helper beside createAgentControllerClient, then update toClientArgs and the corresponding helpers in useAgentControllerGoalMutations.ts, useAgentControllerRunMutations.ts, and useAgentControllerStateMutations.ts to reuse it. Preserve the projectPath-to-scope mapping and all existing arguments.mastracode/web/src/web/ui/domains/workspaces/services/github.ts (1)
396-403: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
fetchProjectSettingserror handling is less structured thansaveProjectSettings.
postProjectGitOp(used bysaveProjectSettings) parses the JSON error body forcode,message, and setsauthRequiredon 401.fetchProjectSettingsonly throws a genericErrorwith the status code, so a 401 here won't be distinguishable asauthRequiredby callers the way save failures are.♻️ Align error handling with `postProjectGitOp`
export async function fetchProjectSettings(baseUrl: string, githubProjectId: string): Promise<ProjectSettings> { const res = await fetch(`${baseUrl}/web/github/projects/${encodeURIComponent(githubProjectId)}/settings`, { headers: { Accept: 'application/json' }, credentials: 'include', }); - if (!res.ok) throw new Error(`Failed to load project settings (${res.status})`); + if (!res.ok) { + const err = new Error(`Failed to load project settings (${res.status})`) as GitOpError; + err.status = res.status; + if (res.status === 401) err.authRequired = true; + throw err; + } return (await res.json()) as ProjectSettings; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@mastracode/web/src/web/ui/domains/workspaces/services/github.ts` around lines 396 - 403, Update fetchProjectSettings to handle non-OK responses using the same structured JSON error parsing as postProjectGitOp, preserving the response code and message and marking 401 failures as authRequired. Keep the successful ProjectSettings parsing unchanged and reuse the existing error shape or helper used by postProjectGitOp.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.changeset/wide-streets-smash.md:
- Line 8: Add a concise TypeScript usage example to the changeset demonstrating
the new client-js `session(resourceId).running()` API, including awaiting the
call and reading the returned `running` flag. Keep the example focused on the
public API and preserve the existing feature description.
In `@mastracode/web/src/web/ui/domains/chat/context/ChatTranscriptProvider.tsx`:
- Around line 46-62: Update the hydration useEffect in ChatTranscriptProvider
around stateRunning/stateUpdatedAt so session snapshots only synchronize the
running flag. Stop passing snapshot omProgress and tokenUsage to syncState,
preserving the newer live SSE-driven progress and usage values while retaining
immediate running-state hydration.
In `@packages/core/src/agent-controller/agent-controller.ts`:
- Around line 59-66: Update sessionRegistryKey to use an unambiguous encoding of
both resourceId and scope, rather than concatenating them with a NUL separator;
preserve distinct keys for unscoped sessions, scoped sessions, and values
containing arbitrary characters.
In `@packages/core/src/agent-controller/session-isolation.test.ts`:
- Around line 401-404: Await controller.setResourceId in the resource re-key
test before querying the renamed and old resource IDs. Keep the existing
assertions unchanged so they verify both the new lookup and registry cleanup
after the operation completes.
---
Outside diff comments:
In `@mastracode/web/src/web/github/routes.ts`:
- Around line 823-855: The worktree creation flow around ensureWorktree and
runWorktreeSetup must rerun setup after a prior setup failure. Track whether the
branch already has a persisted githubWorktrees row, and invoke runWorktreeSetup
when that row is absent even if result.reused is true; retain the existing
behavior for newly created worktrees and persist the row only after successful
setup.
In
`@mastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerStateMutations.ts`:
- Around line 40-74: The connection invalidation in
useSwitchAgentControllerModeMutation and useSwitchAgentControllerModelMutation
is broader than the session connection key. Replace each inline connection query
key with queryKeys.agentControllerConnection(args.agentControllerId,
args.resourceId, args.projectPath), preserving the existing session
invalidation.
In `@mastracode/web/src/web/ui/domains/workspaces/hooks/useWorkspaces.ts`:
- Around line 123-154: Update useDeleteWorkspaceMutation so the cascade uses an
AgentController session scoped to worktree.worktreePath inside mutationFn,
rather than the caller-provided threadSession derived from the selected
worktree. Reuse the existing session-construction pattern used by
openWorktreeThread, useAgentControllerThreads, or useWorkspaceActivity, while
preserving the current listThreads/deleteThread loop and optional-session
behavior.
---
Nitpick comments:
In
`@mastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerThreadMutations.ts`:
- Around line 14-22: Deduplicate the repeated argument mapping by introducing a
shared helper beside createAgentControllerClient, then update toClientArgs and
the corresponding helpers in useAgentControllerGoalMutations.ts,
useAgentControllerRunMutations.ts, and useAgentControllerStateMutations.ts to
reuse it. Preserve the projectPath-to-scope mapping and all existing arguments.
In `@mastracode/web/src/web/ui/domains/workspaces/services/github.ts`:
- Around line 396-403: Update fetchProjectSettings to handle non-OK responses
using the same structured JSON error parsing as postProjectGitOp, preserving the
response code and message and marking 401 failures as authRequired. Keep the
successful ProjectSettings parsing unchanged and reuse the existing error shape
or helper used by postProjectGitOp.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 999c0023-d248-4609-853e-c51b2df08fee
📒 Files selected for processing (66)
.changeset/big-parrots-tickle.md.changeset/lucky-moons-attack.md.changeset/perky-ties-shout.md.changeset/wide-streets-smash.mdclient-sdks/client-js/src/resources/agent-controller.test.tsclient-sdks/client-js/src/resources/agent-controller.tsclient-sdks/client-js/src/route-types.generated.tsmastracode/web/src/shared/api/keys.tsmastracode/web/src/web/github/routes.test.tsmastracode/web/src/web/github/routes.tsmastracode/web/src/web/github/sandbox.test.tsmastracode/web/src/web/github/sandbox.tsmastracode/web/src/web/github/schema.tsmastracode/web/src/web/ui/Sidebar.tsxmastracode/web/src/web/ui/domains/chat/components/Composer.tsxmastracode/web/src/web/ui/domains/chat/components/GoalPanel.tsxmastracode/web/src/web/ui/domains/chat/components/Transcript.tsxmastracode/web/src/web/ui/domains/chat/context/ChatConnectionContext.tsmastracode/web/src/web/ui/domains/chat/context/ChatConnectionProvider.tsxmastracode/web/src/web/ui/domains/chat/context/ChatModelsProvider.tsxmastracode/web/src/web/ui/domains/chat/context/ChatModesProvider.tsxmastracode/web/src/web/ui/domains/chat/context/ChatPermissionsProvider.tsxmastracode/web/src/web/ui/domains/chat/context/ChatSessionProvider.tsxmastracode/web/src/web/ui/domains/chat/context/ChatTranscriptProvider.tsxmastracode/web/src/web/ui/domains/chat/context/useRunPaletteCommand.tsmastracode/web/src/web/ui/domains/chat/hooks/__tests__/useAgentControllerMutationCache.msw.test.tsxmastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerConnection.tsmastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerGoalMutations.tsmastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerModels.tsmastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerModes.tsmastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerPermissionMutations.tsmastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerPermissions.tsmastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerRunMutations.tsmastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerSessionInit.tsmastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerSessionSync.tsmastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerSettings.tsmastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerStateMutations.tsmastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerThreadMessages.tsmastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerThreadMutations.tsmastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerThreads.tsmastracode/web/src/web/ui/domains/chat/hooks/useAgentControllerTranscript.tsmastracode/web/src/web/ui/domains/chat/hooks/useGlobalShortcuts.tsmastracode/web/src/web/ui/domains/chat/hooks/useRouteThreadSync.tsmastracode/web/src/web/ui/domains/chat/services/__tests__/transcript.test.tsmastracode/web/src/web/ui/domains/chat/services/agentControllerClient.tsmastracode/web/src/web/ui/domains/chat/services/transcript.tsmastracode/web/src/web/ui/domains/factory/__tests__/FactoryPages.msw.test.tsxmastracode/web/src/web/ui/domains/factory/hooks/useStartFactoryRun.tsmastracode/web/src/web/ui/domains/settings/components/ProjectSetupSection.tsxmastracode/web/src/web/ui/domains/settings/components/SettingsPanel.tsxmastracode/web/src/web/ui/domains/settings/components/__tests__/ProjectSetupSection.msw.test.tsxmastracode/web/src/web/ui/domains/workspaces/components/WorkspacesSection.tsxmastracode/web/src/web/ui/domains/workspaces/components/__tests__/WorkspacesSection.msw.test.tsxmastracode/web/src/web/ui/domains/workspaces/hooks/__tests__/useProjectSettings.msw.test.tsxmastracode/web/src/web/ui/domains/workspaces/hooks/__tests__/useWorkspaces.msw.test.tsxmastracode/web/src/web/ui/domains/workspaces/hooks/useProjectSettings.tsmastracode/web/src/web/ui/domains/workspaces/hooks/useWorkspaceActivity.tsmastracode/web/src/web/ui/domains/workspaces/hooks/useWorkspaces.tsmastracode/web/src/web/ui/domains/workspaces/index.tsmastracode/web/src/web/ui/domains/workspaces/services/github.tspackages/cli/src/commands/api/route-metadata.generated.tspackages/core/src/agent-controller/agent-controller.tspackages/core/src/agent-controller/session-isolation.test.tspackages/server/src/server/handlers/agent-controller.test.tspackages/server/src/server/handlers/agent-controller.tspackages/server/src/server/server-adapter/routes/agent-controller.ts
…tate instead of a session peek Threads already have per-run active/idle tracking in the agent thread-stream runtime (the same primitive signals' ifIdle delivery uses), so annotate each thread returned by the listThreads route with state: 'active' | 'idle' and derive the sidebar workspace indicators from one polled listing. This drops the invented GET .../running peek route and client session.running() method, and replaces the per-worktree polling loops in the web UI with a single thread-list query keyed by resource. Co-Authored-By: Mastra Code (anthropic/claude-fable-5) <noreply@mastra.ai>
- Make the session registry key collision-proof by JSON-encoding the (resourceId, scope) pair instead of joining with NUL, since NUL can appear in arbitrary caller-provided strings - Await setResourceId in session-isolation tests so registry re-key assertions run after the operation completes - Only sync the running flag from session.state() snapshots so a delayed reconnect refetch can't roll back newer SSE-driven OM progress/usage; the syncState reducer now preserves fields the snapshot omits - Fix the client-js changeset example (listThreads returns an array) and add a usage example to the server changeset Co-Authored-By: Mastra Code (anthropic/claude-fable-5) <noreply@mastra.ai>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.changeset/wide-streets-smash.md:
- Line 8: Add a concise usage example to the changeset describing the new
activity-reporting API: demonstrate session.state() or session.listThreads() and
show the returned running or thread state field. Do not reference the prior
session.running() API; keep the example focused on the current public interface.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 4a59dd3e-7f7a-42bc-8b12-42df71beb12f
📒 Files selected for processing (11)
.changeset/perky-ties-shout.md.changeset/wide-streets-smash.mdclient-sdks/client-js/src/resources/agent-controller.tsclient-sdks/client-js/src/route-types.generated.tsmastracode/web/src/shared/api/keys.tsmastracode/web/src/web/ui/domains/workspaces/components/WorkspacesSection.tsxmastracode/web/src/web/ui/domains/workspaces/components/__tests__/WorkspacesSection.msw.test.tsxmastracode/web/src/web/ui/domains/workspaces/hooks/useWorkspaceActivity.tspackages/cli/src/commands/api/route-metadata.generated.tspackages/server/src/server/handlers/agent-controller.test.tspackages/server/src/server/handlers/agent-controller.ts
💤 Files with no reviewable changes (1)
- packages/cli/src/commands/api/route-metadata.generated.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- mastracode/web/src/web/ui/domains/workspaces/components/tests/WorkspacesSection.msw.test.tsx
- mastracode/web/src/web/ui/domains/workspaces/components/WorkspacesSection.tsx
- mastracode/web/src/shared/api/keys.ts
- client-sdks/client-js/src/resources/agent-controller.ts
- Merge the two overlapping activity changesets into one so client-js and server don't get duplicate changelog entries for the same feature - Drop the @mastra/core bump from the activity changeset: the running flag and per-thread state reuse existing core primitives, core only changed for session scoping (already covered by its own changeset) - Bump client-js/server to minor for the session scope API since it adds new public API surface rather than fixing a bug Co-Authored-By: Mastra Code (anthropic/claude-fable-5) <noreply@mastra.ai>
Summary
Makes Mastra Code web treat each git worktree as an independent unit of work, so multiple agent runs can execute in parallel across worktrees without interfering with each other.
Scoped agent controller sessions (core, server, client-js)
Previously all runs on a GitHub project shared one server-side session: starting a second run repointed the workspace and stopped the first run's stream. Sessions can now be addressed with an optional scope (one per worktree) via
controller.session(resourceId, scope); the server accepts asessionScopequery param and the web UI caches clients per worktree path.One conversation per worktree
Factory runs no longer leave a stray "Untitled" thread (the run now claims the thread a fresh session seeds instead of creating a second one). The sidebar thread list — titles, rename, clone, new-thread — only appears under the main-branch workspace; feature worktrees hold a single conversation and the workspace row itself is the entry point. Local projects keep the flat multi-thread list.
Per-project setup command
Settings › General gains a per-project setup command (e.g.
pnpm i && pnpm build) stored on the GitHub project. It runs inside the sandbox right after a worktree is created, before any agent execution; failures surface as a 400 with the command's stderr so the agent never starts on a broken checkout.Activity indicators
Work in progress is now visible: the session state response includes a
runningflag and a new non-creatingGET .../sessions/:resourceId/runningpeek route lets the UI poll activity without seeding sessions for idle worktrees. The chat working indicator survives page reloads mid-run (hydrated from the state snapshot anddisplay_state_changed), and workspace rows show a pulsing dot while their scoped session is executing.New public API in
@mastra/client-js:Changesets included for
@mastra/core,@mastra/server, and@mastra/client-js.Test plan
session.running()scope-aware requestsCo-Authored-By: Mastra Code (anthropic/claude-fable-5) noreply@mastra.ai
ELI5
Each Git worktree now gets its own independent agent conversation, so agents can work in parallel without mixing messages or settings. Projects can also run a setup command automatically when a new worktree is created.
What changed
sessionScopesupport to server routes and client APIs.runningsession state and per-threadactive/idlestatus.