--- name: worktree description: Create an isolated git worktree + branch + port for this session so concurrent kube-coder sessions and builds don't conflict. Use at the START of any change you want isolated, when two sessions touch the same repo, or before running a dev server/build that would collide on ports or output dirs. Also lists and tears down worktrees. user-invocable: true allowed-tools: Bash, Read argument-hint: "[slug] | list | rm [--force] (default: new, slug from task id)" --- # Per-session git worktree kube-coder isolates sessions at the *terminal* level (each task gets its own tmux session + `~/.claude-tasks//`) but everything still runs in the **same `/home/dev` filesystem**. So two sessions on the same repo collide three ways: - **git tree** — branch/checkout/index thrash, a dirty working tree - **build output** — both write `./dist`, `./build`; Kaniko reads its context from the shared path - **dev-server ports** — `EADDRINUSE` on `:3000` (ports are per-*pod*, not per-session) This skill fixes all three for the current session with **no infra change**: it gives you a dedicated `git worktree` (own checkout + branch) at a path outside the repo, and leases a free port for any server you start. Worktrees share the main clone's object store, so they're cheap on disk — only working files are duplicated. The helper is `worktree.sh` next to this file. Invoke it as `bash "$CLAUDE_SKILL_DIR/worktree.sh" …` (or with its absolute path under `/home/dev/kube-coder/.claude/skills/worktree/`). ## Start an isolated session (the common path) Run from **inside the repo** you want to work on (`cd` there first). `$ARGUMENTS` is an optional human slug; if omitted it derives one from the current task id (`KC_TASK_ID`) or a random token, so it's always unique. ```bash WT=/home/dev/kube-coder/.claude/skills/worktree/worktree.sh eval "$(bash "$WT" new "$ARGUMENTS")" # creates worktree+branch+port, sets KC_WT / PORT cd "$KC_WT" # <-- this cd PERSISTS across your Bash calls ``` After this, **do all work for the change from `$KC_WT`.** The human summary (path / branch / port / preview URL) is printed to stderr — note the **port**, because exported env vars do *not* survive between separate Bash tool calls (the `cd` does). The worktree also records everything in `$KC_WT/.kc-worktree.json`, so you can re-read the port later: ```bash jq -r .port "$KC_WT/.kc-worktree.json" # or read .branch / .path ``` ### What you get - worktree dir: `/home/dev/.worktrees///` - branch: `kc/` (branched from current HEAD, or a base-ref: `new `) - leased port in 3100–3999 (skips kube-coder's reserved ports, other leases and anything already listening) - `new` with a slug that already exists **reuses** that worktree (same path, branch and port) instead of failing ## Running a dev server / preview Bind the leased port explicitly (env doesn't persist between calls), then open it through the app-proxy — no port config in the dashboard needed: ```bash PORT=$(jq -r .port "$KC_WT/.kc-worktree.json") cd "$KC_WT" && PORT=$PORT npm run dev # or: python -m http.server "$PORT", etc. # preview at: /api/app-proxy// ``` ## Builds Because the worktree is its own path, `docker-build` (Kaniko) and local build output (`./dist`, `./build`) no longer collide with other sessions. Run builds with the worktree as context — e.g. `docker-build -t img:tag "$KC_WT"`. ## List / tear down ```bash bash "$WT" list # worktrees + leased ports for the current repo bash "$WT" rm # remove the worktree DIR; the branch is KEPT bash "$WT" rm --force # also drop uncommitted changes in it ``` `rm` deliberately keeps `kc/` — that branch *is* your change. Push/PR it as usual, then delete with the `git branch -D` line `rm` prints. `rm` refuses to delete a dirty worktree unless you pass `--force`. ## Footguns 1. **Uncommitted work stays behind.** A worktree branches from **HEAD** (the committed state). Anything uncommitted on your original checkout is *not* carried in. Commit or stash first if you need it. 2. **`cd` persists, env does not.** In this harness the working directory survives between Bash calls but exported vars don't. Rely on `cd "$KC_WT"` and re-read the port from `.kc-worktree.json` when you need it; don't assume `$PORT` is still set in a later call. 3. **Disk.** Worktrees share `.git`, but a fresh `node_modules` / `.venv` / build tree per worktree is *not* shared and can fill the 20–50Gi PVC. Keep a handful live at once; `rm` ones you're done with. 4. **One repo, one clone.** All worktrees for a repo register under that clone's `.git/worktrees`. Don't `rm -rf` a worktree dir by hand — use `rm` (which runs `git worktree remove` + `prune`) so git's bookkeeping stays consistent. 5. **Not for the boot-time kube-coder pull.** `start.sh` does `git pull --ff-only origin main` on `/home/dev/kube-coder` at boot. Working in a `kc/` worktree keeps `main` clean so that pull still fast-forwards — which is the point. Don't check out `main` inside a worktree. ## The dashboard does this too (#701) A Build can now be launched isolated without this skill: the **New Build** form's *Isolated worktree* toggle, a Board run's *Isolated worktree per item*, `create_task(isolate=true)` from the dashboard MCP, and `spawn_agent(isolate= true)` for sub-agents. All of them — and this script — share one implementation, `charts/workspace/worktrees.py` (in the pod at `/tmp/browser/worktrees.py`): the same `/home/dev/.worktrees//` layout, the same `.kc-worktree.json` manifest, the same port lease and the same lock. So a worktree made here shows up in **Settings → Worktrees**, counts toward `KC_MAX_WORKTREES`, and its port is never handed out twice. When `/tmp/browser/worktrees.py` is present this script delegates to it (override with `KC_WORKTREES_PY`); otherwise it falls back to its own bash implementation, which takes the same `flock` and writes the same manifest. A worktree you make by hand has no owning Build, so the dashboard's cleanup sweep only removes it once it is pristine (nothing committed, nothing dirty) **and** older than `KC_WORKTREE_GC_DAYS` (7). Anything with work in it is yours to remove. Sanity-check the helper before relying on it: ```bash bash -n /home/dev/kube-coder/.claude/skills/worktree/worktree.sh python3 /tmp/browser/worktrees.py --api-version ```