Issue 1 — Runnable worktrees: .codev/config.json worktree block + afx dev command
Problem
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:
export interface UserConfig {
// ... 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:
- Look up the builder and its worktree path via builder state (reuse existing builder lookup patterns from
attach / cleanup commands)
- 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' (new terminal type; needs adding to BuilderType or a separate terminal-type enum — follow whichever pattern the existing Tower REST API uses)
- Env inherited from main's workspace (worktree's symlinked env files apply naturally)
- 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:
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/__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/codev/src/agent-farm/commands/spawn-worktree.ts — extend symlinkConfigFiles, add runPostSpawnHooks
packages/codev/src/agent-farm/commands/spawn.ts — call runPostSpawnHooks after each createWorktree
packages/codev/src/agent-farm/utils/config.ts — add getWorktreeConfig helper
packages/codev/src/agent-farm/cli.ts — register afx dev command
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.
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/codev/src/agent-farm/lib/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
- Issue 2 (VSCode review tooling) depends on the
worktree.devCommand concept + Tower dev-PTY type from this issue.
- Issue 3 (PIR protocol) depends on this for the code-review gate's testability.
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:
attach/cleanupcommands)worktree.devCommandfrom.codev/config.json; error with helpful message if unsettype: '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'(new terminal type; needs adding toBuilderTypeor a separate terminal-type enum — follow whichever pattern the existing Tower REST API uses)afx attach, or VSCodeafx 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/__tests__/dev.test.ts— unit tests for builder lookup, devCommand resolution, existing-dev detection,--stopbehaviorModified
packages/codev/src/agent-farm/types.ts— addworktreeblock toUserConfigpackages/codev/src/agent-farm/commands/spawn-worktree.ts— extendsymlinkConfigFiles, addrunPostSpawnHookspackages/codev/src/agent-farm/commands/spawn.ts— callrunPostSpawnHooksafter eachcreateWorktreepackages/codev/src/agent-farm/utils/config.ts— addgetWorktreeConfighelperpackages/codev/src/agent-farm/cli.ts— registerafx devcommandpackages/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.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/codev/src/agent-farm/lib/tower-client.tsrun()inpackages/codev/src/agent-farm/utils/shell.tsfs.globSync(Node 20+)Dependencies
Blocks
worktree.devCommandconcept + Tower dev-PTY type from this issue.