agents/openai.yaml
interface:
display_name: "Environments Manager"
short_description: "Set up per-worktree environments for Claude Code, Cursor, or..."
icon_small: "./assets/codex-icon.svg"
icon_large: "./assets/codex-icon.svg"
brand_color: "#444449"
default_prompt: "Use $environments-manager to help with this task."
policy:
allow_implicit_invocation: true
assets/codex-icon.svg
<!-- @license lucide-static v1.24.0 - ISC -->
<svg role="img" aria-label="environments-manager skill icon"
class="lucide lucide-settings"
xmlns="http://www.w3.org/2000/svg"
width="128"
height="128"
viewBox="0 0 24 24"
fill="none"
stroke="#F5F5F5"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" />
<circle cx="12" cy="12" r="3" />
</svg>
examples/claude/.worktreeinclude
.env
.env.local
.env.development.local
examples/claude/commands/dev.md
---
description: Start the dev server on a free port
---
Run `scripts/dev.sh` from the project root. Stream output until the dev server is listening, then return the URL.
examples/claude/commands/lint.md
---
description: Run the linter
---
Run the project's lint command (`pnpm lint:ci`, `bun run lint`, or equivalent). Report any violations.
examples/claude/commands/test.md
---
description: Run unit tests
---
Run the project's unit test command (`pnpm test:ci`, `bun test`, or equivalent). Report failures with file:line references.
examples/claude/commands/typecheck.md
---
description: Typecheck the project
---
Run the project's typecheck command (`pnpm ts`, `bun run typecheck`, or equivalent based on the lockfile). Report any errors with file:line references.
examples/claude/settings.json
{
"hooks": {
"WorktreeCreate": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/scripts/claude-worktree-create.sh"
}
]
}
],
"WorktreeRemove": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/scripts/claude-worktree-remove.sh"
}
]
}
]
}
}
examples/codex/environments/environment.toml
# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY.
version = 1
name = "Project Worktree"
[setup]
script = "cd \"$CODEX_WORKTREE_PATH\"\nscripts/worktree-up.sh"
[cleanup]
script = "cd \"$CODEX_WORKTREE_PATH\"\nscripts/worktree-down.sh"
[[actions]]
name = "Dev"
icon = "tool"
command = "scripts/dev.sh"
[[actions]]
name = "Typecheck"
icon = "tool"
command = "pnpm ts"
[[actions]]
name = "Unit tests"
icon = "tool"
command = "pnpm test:ci"
[[actions]]
name = "Lint"
icon = "tool"
command = "pnpm lint:ci"
examples/cursor/worktrees.json
{
"setup-worktree-unix": "../scripts/worktree-up.sh"
}
examples/scripts/claude-worktree-create.sh
#!/usr/bin/env bash
# Claude Code WorktreeCreate hook wrapper.
# Replaces default `git worktree add` behavior, then runs the shared scripts/worktree-up.sh.
#
# stdin contract: JSON with { "name": "<worktree-name>", ... }
# stdout contract: the absolute worktree path and NOTHING ELSE.
# Progress goes to <worktree>/.worktree-setup.log AND /dev/tty when available.
set -euo pipefail
INPUT=$(cat)
NAME=$(echo "$INPUT" | jq -r '.name')
REPO="$CLAUDE_PROJECT_DIR"
WORKTREE="${REPO}/.claude/worktrees/${NAME}"
BRANCH="worktree-${NAME}"
SETUP_TIMEOUT_SEC="${CLAUDE_WORKTREE_SETUP_TIMEOUT:-900}"
LOG_FILE=""
# Both the /dev/tty redirect AND the log append are guarded so a missing tty or
# read-only worktree never aborts the hook (the script runs under `set -e`).
log_to() {
{ echo "$*" > /dev/tty; } 2>/dev/null || true
if [[ -n "$LOG_FILE" ]]; then
echo "$(date '+%H:%M:%S') $*" >> "$LOG_FILE" 2>/dev/null || true
fi
}
log_to "[claude-worktree-create] Creating worktree (branch: $BRANCH)..."
mkdir -p "${REPO}/.claude/worktrees"
if git -C "$REPO" rev-parse --verify "$BRANCH" >/dev/null 2>&1; then
git -C "$REPO" worktree add "$WORKTREE" "$BRANCH" >/dev/null 2>&1
else
git -C "$REPO" worktree add -b "$BRANCH" "$WORKTREE" HEAD >/dev/null 2>&1
fi
LOG_FILE="$WORKTREE/.worktree-setup.log"
: > "$LOG_FILE"
# Hooks inherit the shell default Node, NOT the project's .nvmrc. Many CLIs
# (e.g. Convex) crash on older Node. Source nvm/fnm so `node` resolves to
# whatever the worktree's .nvmrc demands.
if [[ -s "$HOME/.nvm/nvm.sh" ]]; then
# shellcheck disable=SC1090,SC1091
. "$HOME/.nvm/nvm.sh" >/dev/null 2>&1 || true
if [[ -f "$WORKTREE/.nvmrc" ]]; then
(cd "$WORKTREE" && nvm use >/dev/null 2>&1) || true
fi
elif command -v fnm >/dev/null 2>&1; then
eval "$(fnm env --use-on-cd)" || true
fi
log_to "[claude-worktree-create] Using node: $(node --version 2>&1 || echo unknown)"
run_setup() {
cd "$WORKTREE"
# CI=1 keeps interactive CLIs (convex, prisma, etc.) from prompting and hanging.
ROOT_WORKTREE_PATH="$REPO" \
CLAUDE_WORKTREE_NAME="$NAME" \
CI=1 \
bash "${REPO}/scripts/worktree-up.sh"
}
# Watchdog: hard-kill setup after SETUP_TIMEOUT_SEC. macOS has no `timeout`
# command by default, so we background the setup and a sleeper that kills it
# on timeout. Without this, an interactive prompt would hang the hook forever.
(
run_setup >> "$LOG_FILE" 2>&1
) &
SETUP_PID=$!
(
sleep "$SETUP_TIMEOUT_SEC"
if kill -0 "$SETUP_PID" 2>/dev/null; then
echo "[claude-worktree-create] TIMEOUT after ${SETUP_TIMEOUT_SEC}s - killing setup (PID $SETUP_PID)" >> "$LOG_FILE"
kill -TERM "$SETUP_PID" 2>/dev/null || true
sleep 5
kill -KILL "$SETUP_PID" 2>/dev/null || true
fi
) &
WATCHDOG_PID=$!
SETUP_EXIT=0
wait "$SETUP_PID" || SETUP_EXIT=$?
kill "$WATCHDOG_PID" 2>/dev/null || true
wait "$WATCHDOG_PID" 2>/dev/null || true
if [[ "$SETUP_EXIT" -ne 0 ]]; then
log_to "[claude-worktree-create] worktree-up.sh exited $SETUP_EXIT - see $LOG_FILE"
else
log_to "[claude-worktree-create] Worktree ready."
fi
# THE ONLY THING ON STDOUT - Claude parses this as the cwd.
echo "$WORKTREE"
examples/scripts/claude-worktree-remove.sh
#!/usr/bin/env bash
# Claude Code WorktreeRemove hook wrapper.
# Runs scripts/worktree-down.sh for cleanup, then removes the git worktree and branch.
#
# stdin contract: JSON with { "worktree_path": "<absolute-path>", ... }
set -euo pipefail
INPUT=$(cat)
WORKTREE=$(echo "$INPUT" | jq -r '.worktree_path')
REPO="$CLAUDE_PROJECT_DIR"
CLEANUP_TIMEOUT_SEC="${CLAUDE_WORKTREE_CLEANUP_TIMEOUT:-300}"
[[ ! -d "$WORKTREE" ]] && exit 0
LOG_FILE="${TMPDIR:-/tmp}/claude-worktree-cleanup-$(basename "$WORKTREE").log"
: > "$LOG_FILE" 2>/dev/null || LOG_FILE=""
log_to() {
{ echo "$*" > /dev/tty; } 2>/dev/null || true
if [[ -n "$LOG_FILE" ]]; then
echo "$(date '+%H:%M:%S') $*" >> "$LOG_FILE" 2>/dev/null || true
fi
}
assert_clean_worktree() {
local changes
changes=$(git -C "$WORKTREE" status --porcelain --untracked-files=all 2>/dev/null || true)
if [[ -n "$changes" ]]; then
log_to "[claude-worktree-remove] REFUSED: worktree has uncommitted changes"
printf '%s\n' "$changes" >&2
exit 1
fi
}
assert_clean_worktree
# Cleanup may call CLIs (e.g. convex env remove) that need the project's Node version.
if [[ -s "$HOME/.nvm/nvm.sh" ]]; then
# shellcheck disable=SC1090,SC1091
. "$HOME/.nvm/nvm.sh" >/dev/null 2>&1 || true
if [[ -f "$WORKTREE/.nvmrc" ]]; then
(cd "$WORKTREE" && nvm use >/dev/null 2>&1) || true
fi
elif command -v fnm >/dev/null 2>&1; then
eval "$(fnm env --use-on-cd)" || true
fi
run_cleanup() {
cd "$WORKTREE"
ROOT_WORKTREE_PATH="$REPO" CI=1 bash "${REPO}/scripts/worktree-down.sh"
}
if [[ -x "${REPO}/scripts/worktree-down.sh" ]]; then
(
run_cleanup >> "${LOG_FILE:-/dev/null}" 2>&1
) &
CLEANUP_PID=$!
(
sleep "$CLEANUP_TIMEOUT_SEC"
if kill -0 "$CLEANUP_PID" 2>/dev/null; then
log_to "[claude-worktree-remove] TIMEOUT after ${CLEANUP_TIMEOUT_SEC}s - killing cleanup"
kill -TERM "$CLEANUP_PID" 2>/dev/null || true
sleep 3
kill -KILL "$CLEANUP_PID" 2>/dev/null || true
fi
) &
WATCHDOG_PID=$!
wait "$CLEANUP_PID" 2>/dev/null || true
kill "$WATCHDOG_PID" 2>/dev/null || true
wait "$WATCHDOG_PID" 2>/dev/null || true
fi
assert_clean_worktree
BRANCH=$(git -C "$WORKTREE" rev-parse --abbrev-ref HEAD 2>/dev/null || true)
git -C "$REPO" worktree remove "$WORKTREE"
if [[ -n "$BRANCH" && "$BRANCH" != "HEAD" ]]; then
git -C "$REPO" branch -d "$BRANCH" 2>/dev/null || \
log_to "[claude-worktree-remove] Kept unmerged branch: $BRANCH"
fi
examples/scripts/dev.sh
#!/usr/bin/env bash
# Example: dev server on a free port. Generated by environments-manager.
set -euo pipefail
WORKTREE_PATH="${CODEX_WORKTREE_PATH:-${CURSOR_WORKTREE_PATH:-$(pwd)}}"
cd "$WORKTREE_PATH"
port="${DEV_PORT_START:-3910}"
while lsof -nP -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1; do
port=$((port + 1))
done
echo "Starting app on http://localhost:$port"
exec pnpm dev -p "$port"
examples/scripts/worktree-down.sh
#!/usr/bin/env bash
# Example: backend-neutral worktree cleanup.
# Generated by environments-manager. Add backend-specific cleanup from references.
set -euo pipefail
WORKTREE_PATH="${CODEX_WORKTREE_PATH:-${CURSOR_WORKTREE_PATH:-$(pwd)}}"
cd "$WORKTREE_PATH"
# Backend-specific cleanup goes here.
# Never guess resource names from branch names. Read them from env/config files
# or deterministic setup metadata created by worktree-up.sh.
rm -f .env .env.local .env.development.local
echo "removed local env files"
examples/scripts/worktree-up.sh
#!/usr/bin/env bash
# Example: backend-neutral worktree setup.
# Generated by environments-manager. Add backend-specific blocks from references.
set -euo pipefail
WORKTREE_PATH="${CODEX_WORKTREE_PATH:-${CURSOR_WORKTREE_PATH:-$(pwd)}}"
SOURCE_PATH="${CODEX_SOURCE_TREE_PATH:-${ROOT_WORKTREE_PATH:-$HOME/Developer/projects/myapp}}"
cd "$WORKTREE_PATH"
# 1. Copy ignored env files
for f in .env .env.local .env.development.local; do
if [[ -f "$SOURCE_PATH/$f" && ! -f "$WORKTREE_PATH/$f" ]]; then
cp "$SOURCE_PATH/$f" "$WORKTREE_PATH/$f"
echo "copied $f"
fi
done
# 2. Install dependencies
if [[ -f pnpm-lock.yaml ]]; then pnpm install
elif [[ -f bun.lock || -f bun.lockb ]]; then bun install
elif [[ -f yarn.lock ]]; then yarn install
elif [[ -f package-lock.json ]]; then npm install
fi
# 3. Backend isolation/seeding
# Load a backend reference before adding database-specific behavior:
# - references/postgresql.md
# - references/convex.md
# - researcher-agent findings for any other backend
# 4. Codegen (adapt to the project)
if [[ -f convex/convex.config.ts || -f convex/schema.ts ]]; then
pnpm exec convex codegen
elif [[ -f prisma/schema.prisma ]]; then
pnpm prisma generate
fi
echo "worktree ready"
references/claude.md
# Claude Code Linking
Wire the shared `scripts/worktree-up.sh` and `scripts/worktree-down.sh` into Claude Code's native worktree system.
## What Claude Code Actually Provides
Claude Code DOES have native worktree support. It is fired on real worktree creation, not on every session.
| Mechanism | What it does |
| :--- | :--- |
| `claude --worktree <name>` (or `-w`) | Creates worktree at `.claude/worktrees/<name>/` on branch `worktree-<name>` |
| `.worktreeinclude` file (gitignore syntax) | Auto-copies matching **gitignored** files (e.g. `.env`, `.env.local`) into every new worktree - **no scripting required** |
| `WorktreeCreate` hook | Runs ONCE when a worktree is created. **Replaces the default `git worktree add` behavior** - the hook script must call `git worktree add` itself and print the worktree path to stdout |
| `WorktreeRemove` hook | Runs when a worktree is being deleted. Receives `worktree_path` on stdin |
| `$CLAUDE_PROJECT_DIR` | Path to the source repo root inside hook scripts |
**Do NOT use `SessionStart` for worktree setup.** `SessionStart` fires on every new conversation in any worktree, not on worktree creation. It would rerun setup every time the user opens a session.
The "run once when worktree is created" hook is `WorktreeCreate` - that is the correct mechanism.
There is no separate `PostWorktreeCreate` hook (feature request #27744 is open). Because `WorktreeCreate` replaces the default git logic, the hook script must be responsible for both the `git worktree add` call AND the project setup.
## Files to Generate
```text
.worktreeinclude # gitignore-syntax list of files to auto-copy
.claude/
├── settings.json # WorktreeCreate + WorktreeRemove hooks
└── commands/ # optional: per-action slash commands
├── dev.md
├── typecheck.md
├── test.md
└── lint.md
scripts/
├── claude-worktree-create.sh # WorktreeCreate hook wrapper
├── claude-worktree-remove.sh # WorktreeRemove hook wrapper
└── (shared worktree-up.sh / worktree-down.sh / dev.sh)
```
`.gitignore` must also include `**/.claude/worktrees/`.
## `.worktreeinclude`
If "Copy ignored env files" was selected in Step 1, **prefer `.worktreeinclude` over the bash `cp` loop** - it's native, zero-script, and runs on every worktree path (CLI `--worktree`, subagent worktrees, desktop parallel sessions).
```
.env
.env.local
.env.development.local
```
Only files matching a pattern **and** gitignored are copied. Tracked files are never duplicated.
## `.claude/settings.json`
```json
{
"hooks": {
"WorktreeCreate": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/scripts/claude-worktree-create.sh"
}
]
}
],
"WorktreeRemove": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/scripts/claude-worktree-remove.sh"
}
]
}
]
}
}
```
If the project already has a `settings.json`, **merge** the `hooks` block. Do not overwrite.
## `scripts/claude-worktree-create.sh` & `claude-worktree-remove.sh`
These wrappers exist because Claude's `WorktreeCreate` hook has Claude-specific responsibilities the shared `scripts/worktree-up.sh` cannot handle:
1. Call `git worktree add` itself (Claude does not do it).
2. Print the worktree path - and **only** the path - to stdout.
3. Send progress output to a log file (and to `/dev/tty` only when available).
**Do not inline the wrapper code into this doc.** The full, battle-tested wrappers live in `examples/scripts/claude-worktree-create.sh` and `claude-worktree-remove.sh`. Always start from those — they encode every gotcha listed in "Hook Robustness" below. The wrappers do these things in order:
1. Read `name` from stdin JSON, compute `WORKTREE` and `BRANCH`.
2. Create the worktree (reusing a matching branch if it exists).
3. Open a log file at `<worktree>/.worktree-setup.log`.
4. Source `nvm` or `fnm` and activate the worktree's `.nvmrc` (so child CLIs see the right Node).
5. Run `scripts/worktree-up.sh` with `ROOT_WORKTREE_PATH`, `CLAUDE_WORKTREE_NAME`, and `CI=1` set, under a watchdog that hard-kills after `CLAUDE_WORKTREE_SETUP_TIMEOUT` (default 900s).
6. Echo the worktree path on stdout — and absolutely nothing else.
The remove wrapper mirrors steps 3–5 for cleanup, refuses a dirty worktree, then runs `git worktree remove` without force. It uses `git branch -d` and keeps any unmerged branch instead of destroying it.
`ROOT_WORKTREE_PATH` is set so the shared `scripts/worktree-up.sh` can locate the source checkout via the existing cascade (`CODEX_SOURCE_TREE_PATH` → `ROOT_WORKTREE_PATH`).
## Hook Robustness (lessons from a real project)
These five fixes turn a wrapper that "works on my machine" into one that survives Claude's actual hook execution context. Every one of these came from a real bug.
1. **`set -e` + `> /dev/tty` will abort the hook.** When Claude runs the hook as a child process, `/dev/tty` may exist but be non-writable, and the redirect failure under `set -e` kills the whole script before setup ever runs. Always wrap the redirect: `{ echo "$*" > /dev/tty; } 2>/dev/null || true`. Testing with `[[ -w /dev/tty ]]` is NOT enough — the perm test can pass while the open call still fails.
2. **Hooks inherit the shell default Node, not the worktree's `.nvmrc`.** If the project's CLI tools require a newer Node (Convex needs 24+; older Node crashes on regex `v` flag), the hook will instantly die. Source `~/.nvm/nvm.sh` or run `fnm env` at the top of the wrapper, then `nvm use` inside the worktree.
3. **Always log to a file.** A hook that crashes silently is impossible to debug from the UI. Open `<worktree>/.worktree-setup.log` early, append every step. The user can `tail -f` it during the next attempt.
4. **Add a watchdog timeout — macOS has no `timeout` command.** Background the setup, background a sleep+kill, `wait` on the setup. Without a watchdog, a single interactive CLI prompt (convex, prisma, vercel) can hang the hook forever — which is what users see as "the script is infinite."
5. **Set `CI=1` before calling the setup script.** Many CLIs read `CI` and switch to non-interactive mode (skip prompts, suppress spinners). It's the cheapest insurance against interactive hangs.
## Common Misconception: Agent Worktrees
The `WorktreeCreate` hook **only fires for `claude --worktree` from the CLI**. The Agent tool's `isolation: "worktree"` feature, and parallel-session worktrees created via the desktop app, create worktrees at `~/Developer/worktrees/<repo>/<name>/` on branch `claude/<name>` — those do **not** trigger this hook. If you need setup there, the user must run `scripts/worktree-up.sh` inside the agent worktree manually (or wire a different mechanism).
Also: **`.claude/settings.json` changes only take effect after restarting Claude.** A running session keeps the old hook config. Tell the user to restart before testing.
## Custom Slash Commands as Actions (optional)
Claude Code does not have a top-bar action menu. To expose the standard actions, write one file per action under `.claude/commands/`. Each is a tiny prompt that tells Claude to run the matching shared script or package-manager command.
See `examples/claude/commands/` for the four standard ones (`dev.md`, `typecheck.md`, `test.md`, `lint.md`).
## Guardrails
- **Never use `SessionStart` for one-time worktree setup** - it fires every session. Use `WorktreeCreate`.
- **Never let `git worktree add` write to stdout** in the `WorktreeCreate` hook - redirect with `>/dev/null 2>&1`. Anything on stdout besides the path breaks Claude's parser.
- **Read stdin once** with `INPUT=$(cat)` - stdin cannot be re-read.
- **Always merge** into the existing `.claude/settings.json`, never overwrite.
- The hook must live in `.claude/settings.json`, not `.claude/settings.local.json` - it must be checked in.
- Prefer `.worktreeinclude` over a bash `cp` loop for env files. Use the `cp` loop only when you need conditional or non-gitignored copies.
- Add `**/.claude/worktrees/` to `.gitignore` so worktree contents do not pollute the main repo's `git status`.
## Verification
```bash
python3 -c "import json; json.load(open('.claude/settings.json'))"
bash -n scripts/claude-worktree-create.sh scripts/claude-worktree-remove.sh
test -x scripts/claude-worktree-create.sh scripts/claude-worktree-remove.sh
grep -q '.claude/worktrees' .gitignore || echo "ADD: **/.claude/worktrees/ to .gitignore"
```
## Online References
- Run parallel sessions with worktrees: https://code.claude.com/docs/en/worktrees
- Hooks reference (WorktreeCreate, WorktreeRemove): https://code.claude.com/docs/en/hooks
- Community reference implementation: https://github.com/tfriedel/claude-worktree-hooks
- Open feature request for PostWorktreeCreate: https://github.com/anthropics/claude-code/issues/27744
references/codex.md
# Codex Linking
Wire the shared `scripts/worktree-up.sh`, `scripts/worktree-down.sh`, and `scripts/dev.sh` into Codex App Local Environments.
## What Codex Provides
Codex has the richest native model of the three IDEs:
1. **`.codex/environments/environment.toml`** - shareable config for setup, cleanup, and actions.
2. **`[setup] script`** - runs when Codex creates a new worktree for a new thread.
3. **`[cleanup] script`** - runs before Codex cleans up the worktree.
4. **`[[actions]]`** - top-bar buttons that run in the integrated terminal.
Worktrees only contain checked-in files, so ignored files like `.env` must be copied or recreated by setup.
## Environment Variables
| Variable | Meaning |
| :--- | :--- |
| `$CODEX_WORKTREE_PATH` | The new worktree path |
| `$CODEX_SOURCE_TREE_PATH` | The original project checkout |
## Files to Generate
```text
.codex/
└── environments/
└── environment.toml
```
The TOML file is small - it only points at the shared scripts and lists the actions.
## `.codex/environments/environment.toml`
```toml
# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY.
version = 1
name = "Project Worktree"
[setup]
script = "cd \"$CODEX_WORKTREE_PATH\"\nscripts/worktree-up.sh"
[cleanup]
script = "cd \"$CODEX_WORKTREE_PATH\"\nscripts/worktree-down.sh"
[[actions]]
name = "Dev"
icon = "tool"
command = "scripts/dev.sh"
[[actions]]
name = "Typecheck"
icon = "tool"
command = "pnpm ts"
[[actions]]
name = "Unit tests"
icon = "tool"
command = "pnpm test:ci"
[[actions]]
name = "Lint"
icon = "tool"
command = "pnpm lint:ci"
```
Adjust the action commands for the project's package manager (e.g. `bun run typecheck`).
## Guardrails
- Do not put large fragile shell programs directly in the TOML string. The TOML keeps two lines max - `cd` then call the script.
- Do not read or write `.conductor` directories.
- Do not create duplicate environments when the user already has one. Update the existing `.codex/environments/environment.toml`.
- Do not hardcode a dev port in the Dev action - call `scripts/dev.sh`, which finds a free port.
- Do not commit `.codex` only on a worktree - it must live on the source checkout / main branch so future worktrees pick it up.
## Verification
```bash
sed -n '1,120p' .codex/environments/environment.toml
```
If Python 3.11+ is available:
```bash
python3 - <<'PY'
import tomllib
with open(".codex/environments/environment.toml", "rb") as f:
tomllib.load(f)
PY
```
## Online References
- OpenAI Codex Local Environments: https://developers.openai.com/codex/app/local-environments
- OpenAI Codex Worktrees: https://developers.openai.com/codex/app/worktrees
- `CODEX_SOURCE_TREE_PATH` / `CODEX_WORKTREE_PATH` note: https://zenn.dev/route06/articles/c1f686b4479957
references/convex.md
# Convex Worktree Backend
Load this reference only when the target project uses Convex and the user selected backend isolation, seeding, env cloning, or Convex codegen.
Convex worktree setup is project-specific. Prefer the target repo's existing scripts over generic snippets. If the repo is `thumbfa.st` or follows the same style, inspect `scripts/worktree-context.sh`, `scripts/worktree-setup.sh`, `scripts/worktree-cleanup.sh`, and `.codex/environments/environment.toml` before editing; they show a useful pattern for isolated Convex deployments, env sync, auth cookie isolation, codegen, and cleanup.
## First Decision
Ask or infer which Convex mode the project should use:
| Mode | Use when | Setup shape |
| :--- | :--- | :--- |
| Local deployment | The worktree can run fully local Convex state. | Create/select local deployment, keep data local, run `convex dev --local --once` or the repo's equivalent. |
| Cloud dev deployment | Each worktree needs an isolated Convex dev deployment. | Create/select `dev/<worktree-slug>`, clone safe env vars, push schema/functions, seed selected data. |
| Existing deployment | The team wants to reuse the source checkout's selected deployment. | Copy env files and avoid destructive reset/import steps. |
Do not assume one mode globally. Convex projects vary a lot.
## Table Seeding Policy
Fresh worktrees usually need enough data to log in, select an organization, exercise billing/credits, and test the product workflow. They do not need a full copy of analytics, logs, generated outputs, webhook history, background jobs, embeddings, message transcripts, file storage, or other heavy/derived tables.
Before writing Convex seed code:
1. Inspect `convex/schema.ts` and nearby feature code.
2. Ask which tables are important enough to seed.
3. If the user does not know, propose a small allowlist. Default to core identity, org, billing, settings, and product fixture tables.
4. Read `examples/scripts/convex-selective-seed.sh` and adapt it to the repo.
Use this policy when adding Convex data copy:
- Require a small `CONVEX_IMPORT_TABLES` allowlist in the generated script or a project-specific helper.
- Prefer committed seed fixtures or a project helper that exports only those tables to JSONL/JSON.
- Import one table at a time with `npx convex import --table "$table" "$path"`.
- Do not use a broad `npx convex export` / `npx convex import --replace-all` as the default setup path. It is a project-specific choice, not the generic safe default.
- Do not include `_storage` unless the user explicitly says file storage is required for the workflow.
- If relationships require stable document IDs across tables, document that targeted imports may regenerate IDs and prefer a purpose-built seed mutation/helper over partial backup editing.
## Selective Seed Shape
Adapt table names, deployment flags, and seed paths to the repo:
```bash
# Convex selected-table seed. Keep this list short and project-specific.
CONVEX_IMPORT_DEPLOYMENT="${CONVEX_IMPORT_DEPLOYMENT:-dev}"
CONVEX_IMPORT_DIR="${CONVEX_IMPORT_DIR:-$SOURCE_PATH/.worktree-seed/convex}"
CONVEX_IMPORT_TABLES=(
users
organizations
organizationMembers
subscriptions
credits
settings
)
assert_non_prod_ref "$CONVEX_IMPORT_DEPLOYMENT"
convex_import_flags=()
case "$CONVEX_IMPORT_DEPLOYMENT" in
dev)
;;
preview:*)
convex_import_flags=(--preview-name "${CONVEX_IMPORT_DEPLOYMENT#preview:}")
;;
*)
echo "unknown Convex import target '$CONVEX_IMPORT_DEPLOYMENT'; use dev or preview:<name>" >&2
exit 1
;;
esac
if [[ -d "$CONVEX_IMPORT_DIR" ]]; then
for table in "${CONVEX_IMPORT_TABLES[@]}"; do
seed_file="$CONVEX_IMPORT_DIR/$table.jsonl"
if [[ -f "$seed_file" ]]; then
npx convex import "${convex_import_flags[@]}" --replace --table "$table" "$seed_file"
echo "seeded Convex table: $table"
else
echo "skipped Convex table without seed file: $table"
fi
done
else
echo "no Convex seed directory at $CONVEX_IMPORT_DIR, skipping selected-table seed"
fi
```
## Cloud Dev Deployment Pattern
When using isolated cloud dev deployments, generate or adapt helper functions instead of cramming everything into one giant script:
- Resolve worktree path, source tree, workspace slug, and deployment ref in a small helper such as `scripts/worktree-context.sh`.
- Use a deterministic dev deployment ref such as `dev/<worktree-slug>`, unless the user provides one.
- Reject production-looking refs before exports, imports, env cloning, or cleanup.
- Clone only safe env vars from the source dev deployment. If the project has a helper like `scripts/convex-sync-env.sh`, use it.
- Set worktree-specific auth/cookie/session prefixes when the app uses Better Auth or similar auth middleware.
- Push schema/functions with the repo's normal command, often `pnpm exec convex dev --once`.
- Run `pnpm exec convex codegen` or the repo's codegen command after deployment selection.
Thumbfa.st is a good local example of this shape: it resolves context in `scripts/worktree-context.sh`, creates/selects a Convex dev deployment in setup, syncs env, sets an auth cookie prefix, pushes Convex, generates code, and cleans local Convex state/env vars in cleanup. Borrow the structure, not every destructive command.
## Cleanup
Cleanup should only remove resources that the setup script created or selected explicitly.
Typical cleanup:
- Remove worktree-specific Convex env vars only for deterministic worktree deployment refs such as `dev/<slug>`.
- Remove local `.convex` state only if the user did not request keeping it.
- Remove copied env files after cleanup work is done.
- Never remove or mutate production deployments.
If the project creates cloud dev deployments and the Convex CLI supports deleting them in the current version, use researcher agents to verify the exact command and prompts before adding deletion. Otherwise leave deployment deletion manual and make cleanup idempotent.
## Guardrails
- Never import into or export from production by default.
- Never copy every table just because the CLI supports full deployment backups.
- Never log Convex deployment admin keys, auth secrets, webhooks, or API keys.
- Keep setup non-interactive: set `CI=1` from wrappers where possible and use explicit CLI flags.
- Keep reset explicit, for example `CONVEX_WORKTREE_RESET_DATA=1`.
- Prefer project helpers over generic snippets when they already exist.
references/cursor.md
# Cursor Linking
Wire the shared `scripts/worktree-up.sh`, `scripts/worktree-down.sh`, and `scripts/dev.sh` into Cursor.
## What Cursor Provides
Cursor has native worktree support via `.cursor/worktrees.json`. The file is searched first in the worktree, then in the project root.
Three setup keys are supported (Cursor picks the first one that matches the host OS):
- `setup-worktree-unix` - macOS/Linux (string filepath or array of commands)
- `setup-worktree-windows` - Windows (string filepath or array of commands)
- `setup-worktree` - generic fallback
Cursor does **not** support a cleanup script. Cleanup is timer-based via global settings (`cursor.worktreeCleanupIntervalHours`, `cursor.worktreeMaxCount`). To get a real cleanup hook, expose `scripts/worktree-down.sh` as a project command the user runs manually before `/delete-worktree`.
Built-in worktree slash commands the user already has: `/worktree`, `/best-of-n`, `/apply-worktree`, `/delete-worktree`.
## Environment Variables
Only one variable is documented:
- `$ROOT_WORKTREE_PATH` - absolute path to the root of the original checkout
- (On Windows: `%ROOT_WORKTREE_PATH%`)
The worktree path itself is just `pwd` inside the worktree.
## Files to Generate
```text
.cursor/
└── worktrees.json
```
That is it. No actions menu, no per-IDE command files - Cursor wires the setup script and stops.
## `.cursor/worktrees.json`
Preferred shape: point Cursor at the script files. Keep arrays only as a last resort.
```json
{
"setup-worktree-unix": "../scripts/worktree-up.sh"
}
```
The path is relative to the location of `.cursor/worktrees.json`. From `.cursor/worktrees.json` at the project root, `scripts/worktree-up.sh` is at `../scripts/worktree-up.sh`.
If the project also needs Windows support, add the second key:
```json
{
"setup-worktree-unix": "../scripts/worktree-up.sh",
"setup-worktree-windows": "../scripts/worktree-up.ps1"
}
```
If the user does not want a separate script and prefers inline commands:
```json
{
"setup-worktree": [
"cp $ROOT_WORKTREE_PATH/.env .env",
"pnpm install"
]
}
```
Inline is fine for two-line setups. For anything more, use a script - the worktree-up logic from the parent skill belongs in `scripts/worktree-up.sh`, not in JSON.
## Guardrails
- Do not put fragile multi-line setup inline. If it does not fit in two clean array entries, call the script.
- Do not put `.cursor/worktrees.json` only in a worktree - it must be on the source checkout / main branch so future worktrees pick it up.
- Do not rely on a cleanup hook - Cursor does not have one. Tell the user to run `scripts/worktree-down.sh` before `/delete-worktree`, or document it in the project README.
- Do not use `$CURSOR_WORKTREE_PATH` - that variable is not documented and not guaranteed. Use `pwd` inside the worktree.
## Verification
```bash
python3 -c "import json; json.load(open('.cursor/worktrees.json'))"
test -x scripts/worktree-up.sh
```
## Online References
- Cursor Worktrees docs: https://cursor.com/docs/configuration/worktrees
- Cursor 3 worktrees release notes: https://forum.cursor.com/t/cursor-3-worktrees-best-of-n
references/postgresql.md
# PostgreSQL Worktree Backend
Load this reference only when the target project uses PostgreSQL and the user selected backend isolation or cleanup.
## Goal
Create a worktree-specific PostgreSQL database, rewrite only local env values to point at it, run the project's schema/codegen steps, and drop only that database during cleanup.
Do not use this reference for other SQL engines by analogy. For MySQL, SQLite, Turso/libSQL, Neon branching, Supabase branching, PlanetScale, MongoDB, Redis, or any other backend, use researcher agents to inspect official docs and the repo's existing scripts before writing backend setup.
## `scripts/worktree-up.sh` Additions
Add this block after env copying and dependency install. Adapt the prefix, env vars, schema commands, and reset env var to the project.
```bash
# PostgreSQL worktree-isolated database.
worktree_slug="$(basename "$WORKTREE_PATH" | tr '/' '-' | tr '[:upper:]' '[:lower:]')"
db_name="<project>_${worktree_slug}"
if [[ -f .env ]]; then
if grep -qE '^DATABASE_URL=' .env; then
# Replace PROJECT_RESET_DB with a project-specific env var, e.g. MYAPP_RESET_DB.
if [[ "${PROJECT_RESET_DB:-0}" == "1" ]] && command -v dropdb >/dev/null 2>&1; then
dropdb --if-exists -d postgres "$db_name" || true
fi
if command -v createdb >/dev/null 2>&1; then
createdb -d postgres "$db_name" 2>/dev/null || true
else
echo "createdb not found; skipped PostgreSQL database creation"
fi
# Rewrite only the DB name in DATABASE_URL.
sed -i.bak -E "s|(DATABASE_URL=postgres://[^/]+)/[^?\"']+|\1/${db_name}|" .env && rm -f .env.bak
echo "PostgreSQL DB set to $db_name"
fi
fi
```
If the project uses `postgresql://`, multiple database env vars, SSL params, pooled URLs, direct URLs, or a hosted provider URL format, inspect existing env conventions and adjust carefully. Do not log secrets.
## Schema and Seed
Run only project-local schema steps after the URL rewrite. Examples:
```bash
if [[ -f prisma/schema.prisma ]]; then
pnpm prisma generate
pnpm prisma db push
fi
```
```bash
if [[ -f drizzle.config.ts ]]; then
pnpm drizzle-kit push
fi
```
Use project seed commands only when the user selected seeding or the repo already treats them as required for local startup.
## `scripts/worktree-down.sh` Additions
Read the database name from the worktree env file. Never derive it from the branch or worktree path during cleanup.
```bash
if [[ ! -f .env ]]; then
echo "no .env in worktree, nothing to clean"
exit 0
fi
db_name="$(grep -E '^DATABASE_URL=' .env | sed -E 's|.*/([^/?]+).*|\1|' | head -n1 || true)"
if [[ -n "${db_name:-}" ]] && command -v dropdb >/dev/null 2>&1; then
dropdb --if-exists -d postgres "$db_name" || true
echo "dropped PostgreSQL DB $db_name"
else
echo "no PostgreSQL DB cleanup performed"
fi
```
Remove copied env files only after cleanup has read the values it needs.
## Guardrails
- Never drop a database whose name was guessed from a branch.
- Never print full `DATABASE_URL` values.
- Keep reset explicit: `<PROJECT>_RESET_DB=1`.
- Prefer idempotent schema commands for setup.
- If database creation requires a hosted provider CLI or branching API, stop using this local PostgreSQL reference and research that provider directly.
SKILL.md
---
name: environments-manager
description: Set up per-worktree environments for Claude Code, Cursor, or Codex. Use for worktree-ready repos, IDE environment config, worktree-up/down scripts, or dev.sh wiring.
disable-model-invocation: true
user-invocable: true
---
Ask before writing. Use the host's question UI. Shared logic lives in `scripts/*.sh`; IDE configs only point at those files. Read a matching `examples/` file before writing, then adapt.
1. Ask what a fresh worktree should do (multi-select): copy ignored env files; install deps; isolate or seed a data backend; run codegen. Capture extra project steps in one follow-up.
2. Ask the backend. PostgreSQL → [postgresql.md](references/postgresql.md). Convex → [convex.md](references/convex.md). None → skip isolation. Any other backend: research official docs and an in-repo example; do not guess CLI flags; summarize before editing.
3. Ask which IDEs to wire (Claude Code, Cursor, Codex). Generate `scripts/` once. Then load [claude.md](references/claude.md), [cursor.md](references/cursor.md), and/or [codex.md](references/codex.md) for linking config only.
Required files: `scripts/worktree-up.sh`, `scripts/worktree-down.sh`, `scripts/dev.sh` (all must end in `.sh`). Put them on the source checkout, not only the current worktree.
Resolve paths as `WORKTREE_PATH="${CODEX_WORKTREE_PATH:-${CURSOR_WORKTREE_PATH:-$(pwd)}}"` and `SOURCE_PATH="${CODEX_SOURCE_TREE_PATH:-${ROOT_WORKTREE_PATH:-}}"`. Claude: use `WorktreeCreate`, never `SessionStart`; the wrapper adds the worktree then runs `worktree-up.sh` with `ROOT_WORKTREE_PATH` — see [claude.md](references/claude.md). Hook wrappers must survive a missing `/dev/tty`, a wrong default Node version, and CLI prompts.
`worktree-up.sh` is idempotent: `set -euo pipefail`, cd to the worktree, then the selected steps in order. Prefer `.worktreeinclude` for Claude env copy. Detect the lockfile for install. Read resource names from env/config or setup metadata; never invent them from branch names. Provide a reset escape hatch via `<PROJECT>_RESET_<RESOURCE>=1`.
`worktree-down.sh` cleans only resources this setup created. Missing tools: print one line and exit 0.
`dev.sh` picks a free port from `${DEV_PORT_START:-3910}`. Never hardcode a port.
Never commit or log secrets. Never embed multi-line programs in IDE config. Never destroy or import production data unless the user set an override env var. Put this guard in any import/replay helper:
```bash
assert_non_prod_ref() {
case "$1" in
prod|prod:*|prod/*|production|production:*|production/*)
echo "refusing prod source: $1" >&2; exit 1 ;;
*prod*|*production*)
[ "${ALLOW_PROD_SOURCE:-0}" = "1" ] || { echo "looks like prod: $1" >&2; exit 1; } ;;
esac
}
```
Expose Dev, typecheck, unit tests, and lint through the IDE; add E2E only if cheap.
After writing: `bash -n` the three scripts, `git status --short`, and parse generated TOML/JSON when those files exist.