By default, a code workspace gets an isolated git worktree: a separate working directory and index that shares the registered repository's object database. Sessions in the same workspace can deliberately share its worktree; a stacked session gets another branch and worktree. Unrelated branches do not see each other's edits or contend over one index, and no second clone is needed.
This is also where much of the instance's disk usage goes.
On a normal installation using the default worktree root:
~/projects/myapp the repository you registered
~/.opensession/worktrees/myapp-fix-login a code workspace on `fix-login`
~/.opensession/worktrees/myapp-add-metrics another branch and worktree
~/.opensession/worktrees/myapp-ask-checkout shared, read-only Ask checkout
paths.worktreesDir in ~/.opensession/config.json decides where host
worktrees live. OPENSESSION_WORKTREES_DIR overrides it; the normal default is
~/.opensession/worktrees. An instance using OPENSESSION_STATE_DIR gets a
worktree root inside that state namespace unless either setting overrides it.
Directory names use the repository's configured wtPrefix and branch.
A repository started from Open Session itself (Settings → Repositories → New, or "New repository" in the New session palette's Project picker) is what to pick for a project that does not exist anywhere yet; a scratch session (Code with no repo) is only a working directory and never becomes one. The form asks where it lives:
-
A GitHub owner. Open Session opens GitHub's new-repository page with the owner, name, private visibility and README option prefilled. Check those choices and create on GitHub, then return and choose Connect repository. This clones it to
~/checkouts/<name>and records itsghRepo, so sessions have GitHub pull-request support. Personal accounts and organizations both work. Grant the App access to the new repository if its installation only covers selected repositories. No Administration permission is requested. -
This server only. A checkout with a bare origin beside it:
~/checkouts/myapp the checkout sessions branch from ~/checkouts/myapp.git its origin, a bare repositoryThe server makes the first commit (a README on
main) and pushes it, so the registry sees the same shape as a clone and the first code session gets a normal worktree, diff and local review unit. Nothing is published and the registry entry has noghRepo, so this choice has no GitHub pull-request flow. Changingoriginand pushing later does not update the registered GitHub identity or enable PR support. Choose a GitHub organization at creation time if you want that integration. Migrating an existing server-only registration to GitHub is a separate operator task, not an automatic consequence of publishing its commits.
Fresh worktree setup is best-effort. Open Session first tries to seed a ready
warm template, then runs .agents/setup or the configured worktreeSetup
fallback. It next runs the configured depsInstall, or bun install when the
worktree root contains package.json. A failed setup skips the remaining steps
of that attempt but does not block the session. Interactive creation starts this
work in the background, so the first turn may begin before dependencies finish
installing.
Commit .agents/setup and .agents/portals.json to let each workspace
provision itself and expose its app as a Portal on demand. This also lets an
agent open its own change in a browser. See repo-lifecycle.md.
code sessions have write access. This is the default. A new code workspace
normally follows its repository setting, while each person can override each
repository under Preferences with Local checkout or Separate worktree.
Additional sessions in an existing workspace keep its worktree, and
a deliberately selected branch or pull request stays isolated. A new branch
starts from the freshly fetched origin/<defaultBranch>, including when that
default branch is explicitly selected as a base. The registered checkout stays
fetch-only: its local branch and uncommitted edits are not moved. If no remote
default branch exists, a local default branch can still be used. Explicit feature
bases retain local commits that origin lacks, so sessions can stack on unpushed
work. Worktree sessions can commit and use the repository's configured
pull-request flow.
ask sessions are read-only. For an isolated repository they share one
per-repo detached checkout (<wtPrefix>-ask-checkout) pinned to
origin/<defaultBranch>. Open Session refreshes an existing Ask checkout at
most every five minutes. A shared-checkout repository uses its main checkout
instead. Repo-less Ask sessions use a non-git scratch directory.
Attached repos. A code session can attach additional repositories. Each gets an isolated worktree using the session's branch name, and may be reused by another owner of that branch. Ask sessions and remote or volume-backed Sandbox workspaces cannot attach repositories. A repository configured as a shared checkout cannot itself be attached unless shared-checkout behavior has been disabled.
Sandbox sessions clone inside the Sandbox's own disk and create no host worktree. Provider-owned cleanup is separate, and destroying a Sandbox deletes any work not pushed elsewhere. See self-hosting-sandboxes.md.
Checkout isolation does not require pull requests. Each repository can set
publicationMode in the instance's ~/.opensession/config.json:
{
"selfDev": "worktree",
"repos": {
"opensession": {
"repo": "/srv/opensession",
"sharedCheckout": true,
"publicationMode": "direct"
}
}
}Edit the existing repo entry; do not replace the entire repos registry with
this example. pull-request is the default when the field is absent or invalid.
direct instructs interactive code sessions to keep their isolated branch,
review its complete diff, integrate the latest default branch, run the repo's
checks, and publish with a normal fast-forward push to that default branch.
A raced push requires another integration and check, never a force-push.
Explicit PR requests, branches with open PRs, and stacked PR work retain the PR workflow. Code Storage retains its branch-as-change-request workflow. Attached repositories each get their own policy. This setting is prompt guidance, not a permission grant: automation restrictions, connected-person authority, and GitHub branch protections still apply. It does not merge or close existing PRs.
Config is re-read for newly assembled run instructions; an already-running turn keeps its existing instructions. Changing publication mode does not move any session to a different checkout. No client wire model or UI preference changes are required to configure this server-side setting.
A repository can set sharedCheckout: true, making new interactive code
sessions work directly in its registered main checkout. The built-in Open
Session repository uses this mode by default. Note that the live services run
from an immutable release, so an edit in the shared checkout is not live until
it is committed, pushed, and deployed. Settings → Repositories exposes the choice as Use
isolated worktrees; changing it affects new sessions, while existing sessions
keep their recorded checkout. The top-level selfDev: "worktree" setting also
opts Open Session self-development into isolated worktrees. PR-branch sessions,
code automations, and other explicitly isolated runs still use worktrees.
This is a deliberate trade with sharp edges. In a shared checkout:
- Only
add→commit→push. Never rungit reset --hard,git checkout .,git revert, or switch branches. Those operations can replace files under the running server and every other session. - Stage specific files, never
git add -A. Other sessions may have changes in the same tree. Inspect the staged diff before committing. - Commit and push often. Keep shared uncommitted work brief and coordinate changes to the same file.
- Keep
mainatorigin/mainwithbun scripts/shared-checkout-sync.ts. A plaingit pull --ff-onlyrefuses the checkout as soon as one dirty file overlaps an upstream commit, and nobody may discard another session's edit, so without a tool the branch drifts behind for everyone. The sync tool fetches, follows upstream for clean paths, adopts local edits that already landed upstream, three-way merges genuine local edits onto the new base (index and worktree separately, with a copy of the pre-merge file under.git/shared-checkout-sync/), leaves conflicting edits untouched and lists them, then moves the branch with a compare-and-swap. Exit code 2 means the branch is current but listed edits still sit on the old base and must be reapplied by their owner before staging. It never touches untracked files or other branches, and it refuses to run when local commits are unpushed.
If sessions do not need to edit the running checkout, use isolated worktrees.
packages/core/opensession-server/src/server/disk-gc.ts starts five minutes
after the server and runs hourly by default. It only finds Cargo target/
directories marked by CACHEDIR.TAG, under host worktrees:
- Caches with no recent entry for more than 7 days are reclaimed unless an active build process is using their worktree.
- At 80% usage on the root filesystem, remaining caches are reclaimed oldest first until usage falls below 70%. The normal pressure pass protects caches touched in the last 24 hours; if usage remains at least 80%, a final pass can reclaim caches idle for more than 2 hours.
The sweep reads /proc on Linux and uses ps plus lsof on macOS. It skips
everything if it cannot determine which worktrees contain live build
processes. It protects Ask checkouts and warm infrastructure. It does not
remove worktrees, branches, commits, node_modules, or non-Rust build output.
Disable it with OPENSESSION_DISK_GC=0. The thresholds and cadence can be
overridden at startup with OPENSESSION_DISK_GC_COLD_DAYS,
OPENSESSION_DISK_GC_HOT_HOURS, OPENSESSION_DISK_GC_URGENT_HOT_HOURS,
OPENSESSION_DISK_GC_PRESSURE_PCT, OPENSESSION_DISK_GC_RELIEF_PCT, and
OPENSESSION_DISK_GC_INTERVAL_MS.
packages/core/opensession-server/src/server/worktree-reaper.ts starts after
ten minutes and runs hourly. It removes a checkout when its branch tip is in
the remote default branch or its pull request is merged or closed, but protects
worktrees used by a live process, sessions active within the last 6 hours, and
checkouts created within the last 6 hours (dated by their .git pointer, so a
fresh checkout survives even when the session list has not caught up with the
session that made it). It also parks session-owned checkouts after 7 days without session activity,
or after 24 hours when every owner is an automation. Branch and session records
remain. A later prompt recreates a missing primary worktree; a parked attached
repository may need to be attached again.
Dirty files and local-only commits do not necessarily keep a reaped or parked
checkout on disk. Before removal, the reaper banks tracked changes, non-ignored
untracked files, and unpushed commits under the Open Session parked-work state directory.
If banking fails, or the untracked payload exceeds 1 GiB by default, it keeps
the worktree. Banked state is retained for 90 days by default and is not
reapplied automatically; its metadata.json, patch, tarball, and git bundle
are the recovery material.
The reaper skips the entire pass when /proc is unavailable. Disable it with
OPENSESSION_WORKTREE_REAPER=0. Tune it with
OPENSESSION_WORKTREE_IDLE_DAYS, OPENSESSION_WORKTREE_ACTIVE_HOURS,
OPENSESSION_WORKTREE_AUTOMATION_IDLE_HOURS,
OPENSESSION_WORKTREE_BANK_MAX_MB, and
OPENSESSION_PARKED_WORK_RETENTION_DAYS.
A separate six-hour sweep removes eligible clean primary worktrees in the default repository for non-running sessions archived and inactive for more than 14 days. It refuses worktrees with uncommitted files or commits absent from every remote. Ask checkouts, warm infrastructure, independent clones, detached worktrees, and unregistered healthy worktrees are protected from the hourly reaper.
The repository's object database and history are shared by all its worktrees. Most generated files are not shared. In retained worktrees, the common large entries are:
- Rust
target/directories. These can reach tens or hundreds of gigabytes. A sharedsccacheor deliberateCARGO_TARGET_DIRstrategy can reduce duplication, while the automatic GC handles stale Cargo caches. - Other build output, such as
dist,.next, andbuild. Open Session removes these only when it removes the whole worktree. node_modules. The GC deliberately leaves it alone. With Bun's default install, package files are hardlinked into a shared store, so summing per-worktreeduresults double-counts shared inodes and can greatly overstate the space that deletion would free.
The examples below use the normal default root. Replace WT_ROOT if
paths.worktreesDir, OPENSESSION_WORKTREES_DIR, or
OPENSESSION_STATE_DIR changes it.
WT_ROOT=~/.opensession/worktrees
# what exists, and apparent size per entry
# (the sum can overcount hardlinked package files)
du -sh "$WT_ROOT"/* | sort -h | tail -20
# inspect a candidate for uncommitted and local-only work
WT="$WT_ROOT/myapp-old-branch"
git -C "$WT" status --short
git -C "$WT" log --oneline HEAD --not --remotes
# remove it through its registered repository so Git's registry stays correct
git -C ~/projects/myapp worktree remove "$WT"
# forget registrations whose directories are already gone
git -C ~/projects/myapp worktree pruneDeleting a worktree directory with rm -rf leaves Git believing it still
exists. git worktree prune repairs that registry, but git worktree remove
avoids the problem.
Before removal, confirm in Open Session that no session using the workspace is
running, stop its previews and Portals, and check for live processes with
lsof +D "$WT". opensession status reports only the service manager's state;
it does not report running sessions or worktree use.