You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Codev spawns builder worktrees as fresh git checkouts, but doesn't set them up for running. Today only root-level .env and .codev/config.json are symlinked into the worktree (via symlinkConfigFiles in packages/codev/src/agent-farm/commands/spawn-worktree.ts). There is:
No automatic pnpm install, so node_modules is empty
No per-package env file handling (monorepos with packages/*/.env leave those unlinked)
No standardized way to spawn the worktree's dev server
No way to serialize dev environments across main + worktrees (running multiple dev servers simultaneously breaks on CORS, OAuth, cookie scoping, webhook URLs — URLs are load-bearing)
The practical consequence: reviewers cannot easily test a builder's changes. They must manually cd .builders/<name>/, run pnpm install, figure out the right dev command, hope the env is correct, and handle port conflicts themselves. This blocks any protocol that wants a pre-PR review step, and also degrades day-to-day builder inspection for every existing protocol (bugfix, air, spir, aspir).
Proposed solution
Two coupled additions to the builder spawn path:
Part A — Extend .codev/config.json with a worktree block
Add a new top-level worktree section to UserConfig at packages/codev/src/agent-farm/types.ts:
exportinterfaceUserConfig{// ... existing fields (shell, harness, templates, etc.) .../** Worktree setup: file symlinks, post-spawn commands, dev command */worktree?: {/** Glob patterns of files to symlink from workspace root into each new worktree */symlinks?: string[];/** Shell commands to run in each new worktree after `createWorktree` completes */postSpawn?: string[];/** How to run the app in the worktree (consumed by `afx dev`) */devCommand?: string;};}
Defaults (when the block is omitted or fields are unset) — zero inference, zero behavior change for existing users:
symlinks: effectively unset → existing symlinkConfigFiles behavior is preserved (root .env + .codev/config.json only). No glob-based expansion; no assumptions about monorepo structure.
postSpawn: [] (empty). Nothing runs. Codev does NOT try to detect pnpm-lock.yaml / package-lock.json / yarn.lock / Cargo.lock / etc. and infer an install command — project toolchains are too diverse for that to be safe.
devCommand: unset → afx dev <builder> fails with a clear error pointing the user to configure it.
Rationale: Codev supports diverse project stacks (pnpm, npm, yarn, bun, cargo, poetry/uv, go mod, mixed). Any attempt to auto-infer the install or dev command is fragile and surprising when it guesses wrong. Users who want runnable worktrees opt in explicitly for their stack. Users who don't need them see zero behavior change from today.
Implementation:
Extend symlinkConfigFiles in packages/codev/src/agent-farm/commands/spawn-worktree.ts to walk the symlinks glob list when it is configured. For each match in the main workspace, create a symlink at the same relative path inside the worktree, creating intermediate directories as needed. When worktree.symlinks is unset or the block is absent, preserve existing behavior exactly — the hardcoded root .env + .codev/config.json symlinks continue as today, with no additional traversal.
Add a new runPostSpawnHooks(worktreePath: string, commands: string[]): Promise<void> helper in spawn-worktree.ts. Executes each command synchronously with cwd = worktreePath, using the existing run() shell utility. Logs progress as "Running post-spawn hook (1/2): pnpm install...". On failure, aborts the spawn with a clear error that includes the failing command and exit code.
Call runPostSpawnHooks in each spawn function (spawnSpec, spawnTask, spawnProtocol, spawnWorktree, spawnBugfix) immediately after createWorktree returns. All five sites in packages/codev/src/agent-farm/commands/spawn.ts.
Add getWorktreeConfig(workspaceRoot) helper in packages/codev/src/agent-farm/utils/config.ts that resolves the worktree block with defaults applied.
Part B — New CLI command: afx dev <builder-id>
Register a new command in packages/codev/src/agent-farm/cli.ts and implement in a new module packages/codev/src/agent-farm/commands/dev.ts.
Behavior:
afx dev <builder-id>
Look up the builder and its worktree path. ExtractfindBuilderById and findBuilderByIssue from packages/codev/src/agent-farm/commands/attach.ts into a shared packages/codev/src/agent-farm/lib/builder-lookup.ts, then have both attach.ts and the new dev.ts import from there (avoids duplicating the Tower-fallback logic).
Read worktree.devCommand from .codev/config.json; error with helpful message if unset.
Check for any existing Tower-managed dev PTY (terminals with type: 'dev'). If found:
Prompt the user: Currently running dev for <other-builder-id>. Stop it and start <this-builder-id>? [y/N]
On y: kill the existing dev PTY via Tower's terminal API, then proceed
On N: abort without spawning a new one
Create a new Tower-managed PTY via createPtySession (existing helper in spawn-worktree.ts) with:
cwd = the builder's worktree path
Command = worktree.devCommand
type = 'dev' — a new terminal type. Implementation: extract a named TerminalType union in packages/core/src/tower-client.ts ('architect' | 'builder' | 'shell' | 'dev'), replace the existing inline unions in createTerminal options and createPtySession registration (the latter currently omits 'architect' — fix while extracting), and add 'dev'.
label = "Dev: <builder-id>" — explicit label so the Tower dashboard and the VSCode terminal-bridge name the tab consistently. The Tower terminal-spawned SSE event must announce this terminal so the VSCode extension's terminal-manager can auto-open it as a tab; verify the SSE bus does not filter by type (broaden if it does). This is the API surface VSCode sidebar: View Diff + Run/Stop Dev Server commands #690 consumes.
Print the Tower terminal WebSocket URL so the user can attach from the dashboard, afx attach, or VSCode.
Dev server runs until the user stops it (Ctrl+C after attaching, dashboard Stop button, or afx dev --stop).
Add --stop flag:
afx dev --stop
Finds the current Tower-managed dev PTY (type 'dev') and kills it. No-op if none is running.
Port conflict design — serial swap on main's URLs
The dev PTY uses the same ports and URLs as main by design. No port allocation, no env templating. This preserves URL-dependent mechanisms (CORS allowlists, OAuth callbacks, cookie scoping, CSP connect-src, webhook URLs) that would break if the worktree's dev ran on different ports.
Consequence: only one dev environment can run at a time — either main (started by the user themselves, outside Codev's control) or a Codev-managed builder dev. The swap-detection in afx dev handles builder-to-builder swaps automatically. Main-to-builder swaps require the user to stop main themselves (Codev never kills processes it didn't spawn).
If port is in use when afx dev runs (because main is still active), the spawned dev command will fail at bind time with its own clear error. No special handling needed from Codev.
Files to touch
New
packages/codev/src/agent-farm/commands/dev.ts — implements afx dev <builder-id> and --stop
packages/codev/src/agent-farm/lib/builder-lookup.ts — extracted findBuilderById / findBuilderByIssue helpers (shared by attach and dev)
packages/codev/src/agent-farm/__tests__/dev.test.ts — unit tests for builder lookup, devCommand resolution, existing-dev detection, --stop behavior
Modified
packages/codev/src/agent-farm/types.ts — add worktree block to UserConfig
packages/core/src/tower-client.ts — define & export TerminalType ('architect' | 'builder' | 'shell' | 'dev'); use it in createTerminal options
packages/codev/src/agent-farm/commands/spawn-worktree.ts — extend symlinkConfigFiles, add runPostSpawnHooks, switch createPtySession registration to TerminalType, set label: "Dev: <builder-id>" for dev PTYs
packages/codev/src/agent-farm/commands/spawn.ts — call runPostSpawnHooks after each createWorktree
packages/codev/src/agent-farm/commands/attach.ts — switch to importing findBuilderById / findBuilderByIssue from lib/builder-lookup.ts
packages/codev/src/agent-farm/cli.ts — register afx dev command
Dashboard / Tower terminal-list code that case-switches on terminal type — add a 'dev' branch (audit during implementation; grep type === 'builder' / type === 'shell' under packages/codev/src/agent-farm/dashboard/)
codev-skeleton/.codev/config.json — add a commented-out worktree block as a discoverable hint for new projects spawned by codev init
packages/codev/src/agent-farm/__tests__/spawn-worktree.test.ts — add cases for glob-symlinking and runPostSpawnHooks success + failure
CLAUDE.md / AGENTS.md — document the new worktree config block, the afx dev command, and include a "Runnable Worktree Recipes" section with ready-to-paste worktree blocks for common stacks (pnpm monorepo, npm project, yarn, bun, cargo, poetry/uv, go mod). Reinforce that Codev does NOT auto-infer — users must pick the recipe that matches their stack.
Acceptance criteria
Existing behavior preserved (backward compatibility is absolute): repositories without a worktree block in .codev/config.json continue to spawn builders exactly as today. No glob expansion runs, no install runs, no additional env files symlinked. Only the existing hardcoded root .env + .codev/config.json symlinks apply. A user who never opens .codev/config.json to add a worktree block observes zero behavior change.
Opt-in glob symlinking: when worktree.symlinks is configured with glob patterns in .codev/config.json, spawning a builder produces symlinks for each matched file from the workspace root into the new worktree at the correct relative path (including nested paths like packages/foo/.env).
postSpawn runs: after createWorktree, the configured commands execute in the worktree. If pnpm install is the command, node_modules/ is populated before the builder session starts.
postSpawn failure aborts spawn: if a postSpawn command exits non-zero, the spawn is aborted with a clear error identifying the failing command. Worktree is not left in a half-setup state.
afx dev spawns dev PTY: for any existing builder (bugfix, air, spir, aspir) with worktree.devCommand configured, afx dev <builder-id> creates a Tower PTY visible in the dashboard. Dev server output streams to anyone who attaches.
afx dev swap detection: with dev already running for builder A, running afx dev <builder-B> prompts the user to stop A before starting B. Confirming swaps; declining aborts.
afx dev --stop: stops the current dev PTY; no-op if none running.
Port conflict messaging: when main's dev is running, afx dev fails at bind time with the dev command's own port-in-use error (no Codev intervention needed).
Tests: new unit tests cover runPostSpawnHooks, glob symlink expansion, afx dev builder lookup, afx dev existing-dev detection.
Docs updated: CLAUDE.md and AGENTS.md describe the worktree block schema, the afx dev command, and include a "Runnable Worktree Recipes" section with ready-to-paste configurations for pnpm, npm, yarn, bun, cargo, poetry/uv, and go mod — making clear that Codev does not auto-detect the stack and users must copy the recipe matching their toolchain.
Dev PTY label & SSE: dev terminals are created with label: "Dev: <builder-id>" so the dashboard and (via VSCode sidebar: View Diff + Run/Stop Dev Server commands #690) VSCode display a meaningful tab name. The Tower terminal-spawned SSE event includes the new type: 'dev' value so the VSCode terminal-manager can auto-open the PTY as a tab.
Non-goals (explicitly out of scope)
Per-worktree port allocation / env templating (would break CORS + URLs; see design rationale)
Automatic stopping of main's dev when afx dev runs (main is user's responsibility)
Per-worktree database isolation (shared DB acceptable for most bug fixes / small features; users who need isolation add Docker to postSpawn themselves)
Auto-detecting the dev command from package.json scripts (explicit worktree.devCommand only)
Parallel runnable dev environments (serial by design — deploy previews like Vercel/Netlify are the right tool for parallel preview)
Benefits to existing protocols (value delivered without any new protocol)
Bugfix / AIR builders get properly installed node_modules and full env symlinks — reviewers can run the builder's changes without manual setup
SPIR / ASPIR builders same — reviewers can test the implementation phase's output in a runnable worktree
Any builder can have its dev server spawned via a single afx dev <id> command; previously required manual cd + install + run
Reference implementations / existing patterns
Builder state lookup pattern: see existing attach command at packages/codev/src/agent-farm/commands/attach.ts
Tower PTY creation: createPtySession in packages/codev/src/agent-farm/commands/spawn-worktree.ts
Tower terminal listing / killing: see Tower REST API in packages/core/src/tower-client.ts
Shell command execution with logged failure: run() in packages/codev/src/agent-farm/utils/shell.ts
Glob expansion: use Node's built-in fs.globSync (Node 20+)
Dependencies
None. This issue is standalone and foundational.
Blocks
VSCode sidebar: View Diff + Run/Stop Dev Server commands #690 (VSCode sidebar: View Diff + Run/Stop Dev Server commands) depends on the worktree.devCommand concept, the 'dev' terminal type, the "Dev: <builder-id>" label, and the SSE announcement from this issue.
Issue 1 — Runnable worktrees:
.codev/config.jsonworktree block +afx devcommandProblem
Codev spawns builder worktrees as fresh git checkouts, but doesn't set them up for running. Today only root-level
.envand.codev/config.jsonare symlinked into the worktree (viasymlinkConfigFilesinpackages/codev/src/agent-farm/commands/spawn-worktree.ts). There is:pnpm install, sonode_modulesis emptypackages/*/.envleave those unlinked)The practical consequence: reviewers cannot easily test a builder's changes. They must manually
cd .builders/<name>/, runpnpm install, figure out the right dev command, hope the env is correct, and handle port conflicts themselves. This blocks any protocol that wants a pre-PR review step, and also degrades day-to-day builder inspection for every existing protocol (bugfix, air, spir, aspir).Proposed solution
Two coupled additions to the builder spawn path:
Part A — Extend
.codev/config.jsonwith aworktreeblockAdd a new top-level
worktreesection toUserConfigatpackages/codev/src/agent-farm/types.ts:Defaults (when the block is omitted or fields are unset) — zero inference, zero behavior change for existing users:
symlinks: effectively unset → existingsymlinkConfigFilesbehavior is preserved (root.env+.codev/config.jsononly). No glob-based expansion; no assumptions about monorepo structure.postSpawn:[](empty). Nothing runs. Codev does NOT try to detectpnpm-lock.yaml/package-lock.json/yarn.lock/Cargo.lock/ etc. and infer an install command — project toolchains are too diverse for that to be safe.devCommand: unset →afx dev <builder>fails with a clear error pointing the user to configure it.Rationale: Codev supports diverse project stacks (pnpm, npm, yarn, bun, cargo, poetry/uv, go mod, mixed). Any attempt to auto-infer the install or dev command is fragile and surprising when it guesses wrong. Users who want runnable worktrees opt in explicitly for their stack. Users who don't need them see zero behavior change from today.
Implementation:
symlinkConfigFilesinpackages/codev/src/agent-farm/commands/spawn-worktree.tsto walk thesymlinksglob list when it is configured. For each match in the main workspace, create a symlink at the same relative path inside the worktree, creating intermediate directories as needed. Whenworktree.symlinksis unset or the block is absent, preserve existing behavior exactly — the hardcoded root.env+.codev/config.jsonsymlinks continue as today, with no additional traversal.runPostSpawnHooks(worktreePath: string, commands: string[]): Promise<void>helper inspawn-worktree.ts. Executes each command synchronously withcwd = worktreePath, using the existingrun()shell utility. Logs progress as "Running post-spawn hook (1/2): pnpm install...". On failure, aborts the spawn with a clear error that includes the failing command and exit code.runPostSpawnHooksin each spawn function (spawnSpec,spawnTask,spawnProtocol,spawnWorktree,spawnBugfix) immediately aftercreateWorktreereturns. All five sites inpackages/codev/src/agent-farm/commands/spawn.ts.getWorktreeConfig(workspaceRoot)helper inpackages/codev/src/agent-farm/utils/config.tsthat resolves theworktreeblock with defaults applied.Part B — New CLI command:
afx dev <builder-id>Register a new command in
packages/codev/src/agent-farm/cli.tsand implement in a new modulepackages/codev/src/agent-farm/commands/dev.ts.Behavior:
findBuilderByIdandfindBuilderByIssuefrompackages/codev/src/agent-farm/commands/attach.tsinto a sharedpackages/codev/src/agent-farm/lib/builder-lookup.ts, then have bothattach.tsand the newdev.tsimport from there (avoids duplicating the Tower-fallback logic).worktree.devCommandfrom.codev/config.json; error with helpful message if unset.type: 'dev'). If found:Currently running dev for <other-builder-id>. Stop it and start <this-builder-id>? [y/N]y: kill the existing dev PTY via Tower's terminal API, then proceedN: abort without spawning a new onecreatePtySession(existing helper inspawn-worktree.ts) with:cwd= the builder's worktree pathworktree.devCommandtype='dev'— a new terminal type. Implementation: extract a namedTerminalTypeunion inpackages/core/src/tower-client.ts('architect' | 'builder' | 'shell' | 'dev'), replace the existing inline unions increateTerminaloptions andcreatePtySessionregistration (the latter currently omits'architect'— fix while extracting), and add'dev'.label="Dev: <builder-id>"— explicit label so the Tower dashboard and the VSCode terminal-bridge name the tab consistently. The Towerterminal-spawnedSSE event must announce this terminal so the VSCode extension's terminal-manager can auto-open it as a tab; verify the SSE bus does not filter bytype(broaden if it does). This is the API surface VSCode sidebar: View Diff + Run/Stop Dev Server commands #690 consumes.afx attach, or VSCode.afx dev --stop).Add
--stopflag:Finds the current Tower-managed dev PTY (type
'dev') and kills it. No-op if none is running.Port conflict design — serial swap on main's URLs
The dev PTY uses the same ports and URLs as main by design. No port allocation, no env templating. This preserves URL-dependent mechanisms (CORS allowlists, OAuth callbacks, cookie scoping, CSP
connect-src, webhook URLs) that would break if the worktree's dev ran on different ports.Consequence: only one dev environment can run at a time — either main (started by the user themselves, outside Codev's control) or a Codev-managed builder dev. The swap-detection in
afx devhandles builder-to-builder swaps automatically. Main-to-builder swaps require the user to stop main themselves (Codev never kills processes it didn't spawn).If port is in use when
afx devruns (because main is still active), the spawned dev command will fail at bind time with its own clear error. No special handling needed from Codev.Files to touch
New
packages/codev/src/agent-farm/commands/dev.ts— implementsafx dev <builder-id>and--stoppackages/codev/src/agent-farm/lib/builder-lookup.ts— extractedfindBuilderById/findBuilderByIssuehelpers (shared byattachanddev)packages/codev/src/agent-farm/__tests__/dev.test.ts— unit tests for builder lookup, devCommand resolution, existing-dev detection,--stopbehaviorModified
packages/codev/src/agent-farm/types.ts— addworktreeblock toUserConfigpackages/core/src/tower-client.ts— define & exportTerminalType('architect' | 'builder' | 'shell' | 'dev'); use it increateTerminaloptionspackages/codev/src/agent-farm/commands/spawn-worktree.ts— extendsymlinkConfigFiles, addrunPostSpawnHooks, switchcreatePtySessionregistration toTerminalType, setlabel: "Dev: <builder-id>"for dev PTYspackages/codev/src/agent-farm/commands/spawn.ts— callrunPostSpawnHooksafter eachcreateWorktreepackages/codev/src/agent-farm/utils/config.ts— addgetWorktreeConfighelperpackages/codev/src/agent-farm/commands/attach.ts— switch to importingfindBuilderById/findBuilderByIssuefromlib/builder-lookup.tspackages/codev/src/agent-farm/cli.ts— registerafx devcommandtype— add a'dev'branch (audit during implementation; greptype === 'builder'/type === 'shell'underpackages/codev/src/agent-farm/dashboard/)codev-skeleton/.codev/config.json— add a commented-outworktreeblock as a discoverable hint for new projects spawned bycodev initpackages/codev/src/agent-farm/__tests__/spawn-worktree.test.ts— add cases for glob-symlinking andrunPostSpawnHookssuccess + failureCLAUDE.md/AGENTS.md— document the newworktreeconfig block, theafx devcommand, and include a "Runnable Worktree Recipes" section with ready-to-pasteworktreeblocks for common stacks (pnpm monorepo, npm project, yarn, bun, cargo, poetry/uv, go mod). Reinforce that Codev does NOT auto-infer — users must pick the recipe that matches their stack.Acceptance criteria
worktreeblock in.codev/config.jsoncontinue to spawn builders exactly as today. No glob expansion runs, no install runs, no additional env files symlinked. Only the existing hardcoded root.env+.codev/config.jsonsymlinks apply. A user who never opens.codev/config.jsonto add aworktreeblock observes zero behavior change.worktree.symlinksis configured with glob patterns in.codev/config.json, spawning a builder produces symlinks for each matched file from the workspace root into the new worktree at the correct relative path (including nested paths likepackages/foo/.env).createWorktree, the configured commands execute in the worktree. Ifpnpm installis the command,node_modules/is populated before the builder session starts.afx devspawns dev PTY: for any existing builder (bugfix, air, spir, aspir) withworktree.devCommandconfigured,afx dev <builder-id>creates a Tower PTY visible in the dashboard. Dev server output streams to anyone who attaches.afx devswap detection: with dev already running for builder A, runningafx dev <builder-B>prompts the user to stop A before starting B. Confirming swaps; declining aborts.afx dev --stop: stops the current dev PTY; no-op if none running.afx devfails at bind time with the dev command's own port-in-use error (no Codev intervention needed).runPostSpawnHooks, glob symlink expansion,afx devbuilder lookup,afx devexisting-dev detection.worktreeblock schema, theafx devcommand, and include a "Runnable Worktree Recipes" section with ready-to-paste configurations for pnpm, npm, yarn, bun, cargo, poetry/uv, and go mod — making clear that Codev does not auto-detect the stack and users must copy the recipe matching their toolchain.label: "Dev: <builder-id>"so the dashboard and (via VSCode sidebar: View Diff + Run/Stop Dev Server commands #690) VSCode display a meaningful tab name. The Towerterminal-spawnedSSE event includes the newtype: 'dev'value so the VSCode terminal-manager can auto-open the PTY as a tab.Non-goals (explicitly out of scope)
afx devruns (main is user's responsibility)postSpawnthemselves)package.jsonscripts (explicitworktree.devCommandonly)Benefits to existing protocols (value delivered without any new protocol)
afx dev <id>command; previously required manual cd + install + runReference implementations / existing patterns
attachcommand atpackages/codev/src/agent-farm/commands/attach.tscreatePtySessioninpackages/codev/src/agent-farm/commands/spawn-worktree.tspackages/core/src/tower-client.tsrun()inpackages/codev/src/agent-farm/utils/shell.tsfs.globSync(Node 20+)Dependencies
Blocks
worktree.devCommandconcept, the'dev'terminal type, the"Dev: <builder-id>"label, and the SSE announcement from this issue.