How It Works
Every workspace command in uxd is the same pipeline:
resolve(ref) → materialize(workspace) → act(command)
checkout is the pipeline with a no-op act step. code, run, exec,
shell, sync, and diff are thin wrappers that swap the act step. Once you
know these three stages, you can predict what any verb will do.
Resolve
uxd takes the ref you typed and turns it into a concrete git target: a pull
request, a branch, a commit, or an existing working tree to adopt. Resolution
is ordered and first-match-wins — see Refs for
the full table and the flags that force an interpretation.
Resolution produces a slug, the stable directory name for the workspace:
| Ref kind | Slug |
|---|---|
| Pull request | pr-<n> |
| Branch | branch name, sanitized (feat/login → feat-login) |
| Commit | sha-<first 10 chars> |
| Adopted path | adopted-<dirname>-<hash> |
When two branch names sanitize to the same slug, the loser gets a short content hash appended, so distinct refs never share a directory.
Materialize
Materializing is idempotent. Running it on a workspace that already exists is
cheap and skips the network entirely — the fast path just reports
worktree ready (reused).
One bare clone per project
The first materialization creates a single bare, partial clone at
repo_path. Every workspace for that project is a git worktree off that one
clone, so the second ref you check out costs a fetch rather than a full clone.
→ cloning git@github.com:my-org/my-project.git (bare, partial)
One worktree per ref
uxd fetches the ref and adds a worktree under worktrees_path/<slug>:
→ fetching origin/feat/login
→ worktree ready: ~/dev/uxd/my-project/trees/feat-login
Ports get allocated
Each workspace is assigned a contiguous block of ports, sized by the ports
config key. Allocation is deterministic first: the candidate is derived by
hashing the slug into a 100-stride window above base_port, then scanning
upward only if the candidate is already claimed or occupied. The same
workspace therefore tends to get the same port every time, which keeps
bookmarks and OAuth callback URLs working.
Seed files get copied
Files listed in setup.seed_files are copied in from
<config-dir>/seeds/<project>/ (or setup.seed_from). Existing files are
never overwritten unless you pass --reseed. This is how .env.local gets
into a fresh workspace without you remembering to copy it.
Setup runs, cached
If [setup].run is configured, uxd hashes the files matched by
setup.cache_key and compares that hash to the one recorded for this
workspace. Unchanged means skipped:
→ setup up to date (cache hit)
Changed — or never run — means the install command runs and the new hash is recorded on success. A failed install leaves the old hash in place, so the next attempt retries rather than silently assuming success.
checkout does not run [setup] by default — it is the scriptable
primitive and stays fast. Pass --setup if you want it. run, exec, and
shell always run setup unless you pass --no-setup.Act
The act step is the verb you asked for:
| Verb | Act step |
|---|---|
checkout | none — print the worktree path |
code | launch the configured editor |
run <name> | execute a [commands.<name>] entry through a shell |
exec -- <argv…> | spawn an argv directly, no shell |
shell | start an interactive shell |
sync | re-fetch and hard-reset the worktree to its ref |
diff | render gh pr diff, or git diff <merge-base>...HEAD |
Every child process gets the full uxd environment exported — UXD_PATH,
UXD_PORT, UXD_BRANCH, and the rest — plus whatever you declared in [env].
See Environment and templates.
Where uxd keeps things
| Location | Contents | Default |
|---|---|---|
| Config dir | Project TOML files and seed trees | ~/.uxd |
repo_path | The bare, partial clone | {root}/{project}/repo |
worktrees_path | One directory per workspace | {root}/{project}/trees |
| Data dir | Per-workspace scratch space ($UXD_DATA_DIR) | {worktrees_path}/.data/<slug> |
| State dir | Per-project JSON: workspaces, ports, PR cache, last ref | ${XDG_STATE_HOME:-~/.local/state}/uxd |
State is the bookkeeping — it records which slugs exist, which ports they hold,
and which ref you used last (that is what - resolves to). It is derived data:
if it drifts from reality, uxd doctor flags it and
uxd <project> clean --prune-state reconciles it.
Next
- Workspaces and worktrees — the consequences of the worktree model, and the sharp edges worth knowing.
- Refs — every ref form, in resolution order.