# Prepare Worktree Hooks Groundcrew can run one repo-preparation hook after it creates a task worktree and before it launches the agent. Add a repo-local `.groundcrew/config.json`: ```json { "version": 1, "hooks": { "prepareWorktree": "npm ci && npm run codegen:types" } } ``` If the file or hook is absent, Groundcrew skips this phase. There is no implicit `npm install`, `uv sync`, or legacy setup script convention. `prepareWorktree` must be non-interactive, idempotent, and limited to recurring worktree preparation the agent needs: lockfile installs, dependency downloads, or type/code generation required for navigation and tests. Do not put human onboarding in this hook: no prompts, global installs, auth setup, runtime manager bootstrap (`nvm`, `pyenv`, `rustup`, `mise`, `asdf`), db seeds, husky, pre-commit, or local package linking. The hook runs from the worktree root unless the repo entry sets `workdir`; in that case it runs from that subdirectory. Under every runner: - `safehouse`: inside a profile-neutral Safehouse wrap before the agent wrap. - `sdx`: inside the Docker Sandbox before the agent command. - `none`: on the host shell before the agent command. Hook failures are advisory. Groundcrew logs the non-zero exit and still launches the agent so a flaky package registry or stale lockfile does not block the session. ## Task-level opt-out Some tasks need the repository checkout but not its dependency installation or code generation. Add the exact Linear label `groundcrew-skip-prepare` to skip worktree preparation for that task. Shell task sources can emit `"worktreePreparation": "skip"` in the issue JSON for the same behavior. The opt-out skips every preparation layer, including the operator-only `unsandboxedHooks.prepareWorktree` hook. Groundcrew still creates the worktree and launches the agent normally. This policy does not make the worktree read-only; it only skips preparation commands. Dry-run output includes `prepareWorktree skipped` when the policy applies. ## Precedence `prepareWorktree` resolves through three layers, highest priority first: | Layer | Where | Author | | ------------------------------------------------- | -------------------------- | ---------------- | | `.groundcrew/config.json` `hooks.prepareWorktree` | committed in the repo | repo maintainers | | `knownRepositories[].hooks.prepareWorktree` | `crew.config.ts`, per-repo | operator | | `defaults.hooks.prepareWorktree` | `crew.config.ts`, global | operator | The repo-committed file wins (it travels with the code and stays in sync with the repo's build), then the per-repo operator layer, then the global default. A repo with none of the three set skips the phase. ### Global default For repos without local config, set a fallback in `crew.config.ts`: ```ts export default { defaults: { hooks: { prepareWorktree: "test ! -f package-lock.json || npm ci", }, }, // ... }; ``` Repo-local `.groundcrew/config.json` wins for that hook. A repo-local file without `hooks.prepareWorktree` still falls back to the `crew.config.ts` default. ### Per-repo operator hook When you can't (or don't want to) commit a `.groundcrew/config.json` into a repo — a third-party repo, or one where adding groundcrew files is undesirable — set the hook for just that repo from your own `crew.config.ts`: ```ts export default { workspace: { projectDir: "~/dev", knownRepositories: [ "your-org/your-repo", { name: "other-org/their-repo", hooks: { prepareWorktree: "uv sync --dev --frozen", }, }, ], }, // ... }; ``` This reuses the same `hooks` container and `prepareWorktree` contract as the other two layers. It beats `defaults.hooks` but still yields to a committed `.groundcrew/config.json` in that repo. ## `unsandboxedHooks` (operator-only host setup) Some setup cannot run in the sandbox — it needs host toolchains, host network posture, or writes outside the worktree. For those cases an operator may grant a **per-repository** host hook. It mirrors the `hooks` container's `prepareWorktree` phase, on the opposite side of the sandbox boundary: ```ts // crew.config.ts (operator only) knownRepositories: [ { name: "catalog-admin", hooks: { prepareWorktree: "npm ci" }, // sandboxed unsandboxedHooks: { prepareWorktree: "bin/setup" }, // HOST, explicit opt-in }, ]; ``` When both are set, `unsandboxedHooks.prepareWorktree` runs first on the host, then `hooks.prepareWorktree` runs sandboxed, then the agent starts. **The trust granted.** `unsandboxedHooks.prepareWorktree` runs on the host shell outside any sandbox, with the operator's full host authority, against repo-controlled code (lifecycle scripts, `Gemfile`, the repo's own `bin/setup`). Grant it only to repositories you trust to run arbitrary code on the host. **Constraints.** - **Operator-only.** It is honored only from `crew.config.ts`. Setting `unsandboxedHooks` in a repo-committed `.groundcrew/config.json` (top-level or under `hooks`) is a hard config error. - **Per-repository only.** There is no `defaults.unsandboxedHooks`; host execution is never a fleet-wide default. - **Runner support.** Runs on the host for `safehouse` and `none`. The `sdx` runner rejects it at launch — a container has no host to run it on. - **Credentials.** Build secrets are available so `npm`/`bundle` can authenticate; the agent's `preLaunchEnv` names are scrubbed so the command cannot read agent credentials. ## Examples Python with uv: ```json { "version": 1, "hooks": { "prepareWorktree": "uv sync --dev --frozen" } } ``` Node with npm: ```json { "version": 1, "hooks": { "prepareWorktree": "npm ci" } } ``` Docs-only or manually prepared repos can omit the file. To scaffold `.groundcrew/config.json` with a coding agent, see [setup-hook-agent-prompt.md](./setup-hook-agent-prompt.md).