Skip to main content
import { Worktree } from "smithers-orchestrator";

type WorktreeProps = {
  key?: string;
  path: string; // required, non-empty; relative paths resolve against the launch root
  id?: string;
  branch?: string; // omit to use current branch
  baseBranch?: string; // default "main"
  skipIf?: boolean;
  children?: ReactNode;
};
<Worktree path="/tmp/smithers/wt-a" baseBranch="main">
  <Task id="build" output={outputs.outputC}>{{ value: 1 }}</Task>
  <Task id="test" output={outputs.outputC}>{{ value: 2 }}</Task>
  <MergeQueue>
    <Task id="apply" output={outputs.outputC}>{{ value: 3 }}</Task>
  </MergeQueue>
  <Parallel maxConcurrency={2}>
    <Task id="lint" output={outputs.outputC}>{{ value: 4 }}</Task>
  </Parallel>
</Worktree>

Path Access

Workflow code can read the same resolved absolute worktree path that Smithers uses for task execution:
const root = ctx.worktreePath("build") ?? ctx.resolveWorktreePath("/tmp/smithers/wt-a");
const verify = JSON.parse(readFileSync(join(root, "artifacts/build/verify.json"), "utf8"));
Use ctx.worktreePath(taskOrWorktreeId) when a task descriptor or explicit <Worktree id> has rendered. Use ctx.resolveWorktreePath(pathProp) for the same string passed to <Worktree path={...}>, including on the first render before descriptor lookups exist. Both APIs use the graph resolver, so they match the path Smithers gives worker tasks.

Notes

  • Descendants inherit worktreeId and absolute worktreePath as cwd.
  • Do not pin a cwd on an agent used inside a <Worktree>. A worker agent’s working directory resolves as agent.cwd ?? worktreePath ?? repoRoot, so an explicit cwd (e.g. new ClaudeCodeAgent({ cwd: process.cwd() })) OVERRIDES the worktree, so the agent then reads and writes the repo root instead of the isolated worktree. Its branch stays empty and a downstream merge/land step has nothing to merge. Leave agent cwd unset and let <Worktree> supply it. Likewise, helpers that touch a worktree’s files must use ctx.worktreePath(...) or ctx.resolveWorktreePath(...); never reconstruct paths with process.cwd(), import.meta.dir, ../.., or fallback candidate lists.
  • jj-backed worktrees include colocated git metadata, so git-native CLI agents such as Codex resolve the worktree directory as their repository root. Do not compensate by setting an agent cwd; that defeats the worktree isolation above.
  • jj continuously snapshots a worktree’s working copy onto its bookmark. A compute <Task> (or helper) that detects changes with git status --porcelain can see a clean tree and skip its work, because jj has already committed the working-copy edits to the bookmark, and git checkout or git restore of a file in the worktree won’t stick for the same reason (jj re-materializes it). Detect a worktree’s changes by diffing against its base branch (jj diff --from <baseBranch> --to @, or git diff <baseBranch>...HEAD), not by git’s dirty-state, and let the merge/land step operate on the committed bookmark rather than re-running git add/git commit.
  • Innermost <Worktree> wins when nested; duplicate ids are rejected.
  • Empty/whitespace path is rejected at render time.
  • A relative path resolves against the launch root, not the workflow file’s directory. The launch root is --root (if given), else the nearest ancestor of the operator working directory that contains a .smithers/ package, else the operator working directory. It is never dirname(workflowFile); workflowPath is threaded through render separately and is not used as the path base. A relative path like wt-a lands beside wherever you ran bunx smithers-orchestrator up <path> from, which can differ from where the workflow source lives. Smithers emits a one-time warning for relative worktree paths showing the base and resolved absolute path. Pass an absolute path (for example /tmp/smithers/wt-a) when you need a deterministic location, and read back the resolved location with ctx.worktreePath(...) or ctx.resolveWorktreePath(...) rather than reconstructing it.