# Repository hooks

> `orca.yaml` setup/archive scripts, issue-command defaults, hook command-source policies, trust prompts, and CLI `--run-hooks` behavior on create/remove.

- Repository: stablyai/orca
- GitHub: https://github.com/stablyai/orca
- Human docs: https://grok-wiki.com/public/docs/stablyai-orca-2036d532bf1c
- Complete Markdown: https://grok-wiki.com/public/docs/stablyai-orca-2036d532bf1c/llms-full.txt

## Source Files

- `orca.yaml`
- `src/shared/types.ts`
- `src/renderer/src/components/settings/RepositoryHooksSection.tsx`
- `src/renderer/src/lib/ensure-hooks-confirmed.ts`
- `src/cli/specs/core.ts`
- `src/shared/setup-runner-command.ts`

---

---
title: "Repository hooks"
description: "`orca.yaml` setup/archive scripts, issue-command defaults, hook command-source policies, trust prompts, and CLI `--run-hooks` behavior on create/remove."
---

Repository hooks are repo-scoped automation defined in committed `orca.yaml`, optional per-machine overrides in Orca settings and `.orca/issue-command`, and trust-gated execution in the desktop app. Setup runs after a worktree is created; archive runs before a worktree is removed or archived. The `orca` CLI can create and delete worktrees without the renderer, but shared `orca.yaml` scripts only run when you pass `--run-hooks` or when repository policy and `setupDecision` allow setup on create.

## `orca.yaml` shape

Orca reads `orca.yaml` at the repository root (and from the created worktree path when resolving setup for a new checkout). The parser supports only `scripts` and `issueCommand`; other top-level keys may trigger an “unrecognized keys / update Orca” warning in settings while still leaving hook scripts unreadable.

| Key | When it runs | Notes |
| --- | --- | --- |
| `scripts.setup` | After worktree creation | Multiline block scalar (`\|`) or inline shell string |
| `scripts.archive` | Before worktree removal | Same formats as setup |
| `issueCommand` | When a workspace launches with linked issue automation | Template string; not a shell hook |

Example (from the Orca repo itself):

```yaml
scripts:
  setup: |
    node config/scripts/run-internal-dev-setup.mjs
    pnpm install
```

Settings UI also documents a fuller template including `archive` and `issueCommand`:

```yaml
scripts:
  setup: |
    pnpm worktree:setup
  archive: |
    echo "Cleaning up before archive"
issueCommand: |
  Complete {{artifact_url}}
```

<Note>
If `orca.yaml` exists but none of `scripts.setup`, `scripts.archive`, or `issueCommand` parse successfully, Orca treats the file as missing for hook purposes. Binary `orca.yaml` files are ignored.
</Note>

## Lifecycle

```mermaid
sequenceDiagram
  participant User as User / CLI
  participant UI as Desktop renderer
  participant RT as Runtime / main
  participant Git as Git worktree
  participant Hook as Hook runner

  User->>UI: Create worktree (composer / UI)
  UI->>UI: ensureHooksConfirmed(setup)
  UI->>RT: worktree.create(setupDecision)
  RT->>Git: git worktree add
  alt setup allowed and trusted
    RT->>RT: createSetupRunnerScript
    RT->>User: setup launch in first terminal
  end

  User->>RT: worktree rm (CLI, no --run-hooks)
  RT->>RT: skip archive unless runHooks
  User->>RT: worktree rm --run-hooks
  RT->>Hook: runHook(archive)
  Hook->>Git: teardown while path exists
  RT->>Git: git worktree remove
```

| Phase | Desktop default | CLI / headless runtime |
| --- | --- | --- |
| Setup on create | Follows `setupRunPolicy` + user `setupDecision` after trust prompt | Skipped unless `--run-hooks` (forces `setupDecision: run`) or explicit `setupDecision` |
| Archive on remove | Runs after trust prompt unless user skips | Skipped unless `--run-hooks` |

Archive hooks run while the worktree directory still exists so teardown scripts can read files and env vars. Failed archive hooks are logged; removal can still proceed depending on force/clean preflight.

## Effective commands: shared vs local

Two layers combine:

1. **Shared** — `orca.yaml` in the repo (committed).
2. **Local** — per-repo `hookSettings.scripts` stored in Orca settings (not in git).

`commandSourcePolicy` on `RepoHookSettings` controls merging:

| Policy | Setup / archive behavior |
| --- | --- |
| `shared-only` | Use `orca.yaml` only; ignore local script fields |
| `local-only` | Use settings scripts only; ignore `orca.yaml` for execution |
| `run-both` | Run shared script, then local (newline-separated) |

If `commandSourcePolicy` is unset and a local setup or archive script is non-empty, effective policy defaults to **`local-only`**. Otherwise it defaults to **`shared-only`**. Legacy persisted value `shared-first` normalizes to `shared-only`.

Setup and archive can resolve different effective strings when local scripts differ per kind.

## Setup run policy and create-time decisions

`setupRunPolicy` (default **`run-by-default`**) applies when `setupDecision` is `inherit`:

| `setupRunPolicy` | Behavior |
| --- | --- |
| `ask` | Composer must collect an explicit run/skip choice before create |
| `run-by-default` | Setup runs unless user chooses skip |
| `skip-by-default` | Setup skipped unless user chooses run |

`setupDecision` on create:

| Value | Meaning |
| --- | --- |
| `inherit` | Use `setupRunPolicy` |
| `run` | Force setup |
| `skip` | Force skip (also used when user declines trust) |

CLI `--run-hooks` on `worktree create` sets the effective decision to **`run`** and sets **`activate`** so the new worktree is revealed and the setup runner can attach to the first terminal—matching desktop runner behavior instead of a hidden background shell.

## Setup runner and environment

When setup runs with a visible Orca window, main writes a runner under the worktree git dir (for example `.git/orca/setup-runner.sh` or `.cmd` on native Windows) and returns a `WorktreeSetupLaunch` with `runnerScriptPath` and env vars. The renderer launches `buildSetupRunnerCommand(runnerScriptPath)` (bash on POSIX, `cmd.exe /c` on Windows, WSL UNC paths translated for cross-boundary runs).

Hook and runner environments inject:

| Variable | Role |
| --- | --- |
| `ORCA_ROOT_PATH` | Registered repo root |
| `ORCA_WORKTREE_PATH` | New worktree path |
| `ORCA_WORKSPACE_NAME` | Worktree directory basename |
| `CONDUCTOR_ROOT_PATH` / `GHOSTX_ROOT_PATH` | Compatibility aliases for Conductor-style scripts |

Posix runners use `set -e`; Windows batch runners wrap each line with `call` and exit on non-zero. Direct `runHook` execution (headless path) uses a **2 minute** timeout and the platform shell (`/bin/bash` or `cmd.exe`), with a dedicated WSL `wsl.exe` path when the worktree is on WSL.

## Issue command defaults

Issue automation is separate from setup/archive shell hooks.

| Source | Path | Trust |
| --- | --- | --- |
| Shared | `orca.yaml` `issueCommand` | Requires trust prompt (content hash) |
| Local override | `{repoRoot}/.orca/issue-command` | User-owned; no `orca.yaml` trust prompt |
| UI default | Built-in template when nothing configured | `Complete {{artifact_url}}` |

Resolution order for the effective command: **local file first**, then shared `orca.yaml`. Writing an empty override deletes `.orca/issue-command` and restores the shared default. First write creates `.orca/` and appends `.orca` to `.gitignore` when possible.

Template variables at launch:

| Placeholder | Substituted with |
| --- | --- |
| `{{artifact_url}}` | Linked work item URL |
| `{{issue}}` | Linked issue number (legacy) |

Long commands use an `issue-command-runner` script under the worktree git dir, same pattern as setup, to avoid PTY line-wrapping on paste.

## Trust prompts and persistence

Before shared `orca.yaml` content runs from the UI, `ensureHooksConfirmed` checks:

1. **`trustedOrcaHooks[repoId].all`** — “Always trust” for the repo.
2. **Command source** — `local-only` skips shared script inspection for setup/archive; local `issueCommand` skips trust.
3. **Content hash** — SHA-256 of trimmed script text; mismatch reopens the modal (“changed since you last approved”).
4. **Empty script** — No prompt; returns `run`.

The modal (`confirm-orca-yaml-hooks`) offers **Don’t run**, **Run hooks**, and **Always trust `orca.yaml` in {repo}**. Per-script approval stores `{ contentHash, approvedAt }` under `setup`, `archive`, or `issueCommand` in persisted UI state.

<Warning>
If hook inspection RPC fails, trust fails closed and the hook is skipped (`skip`).
</Warning>

Trust prompts are serialized so overlapping create/remove actions cannot replace the active modal callback.

## Repository settings UI

**Settings → Repository → Hooks** (`RepositoryHooksSection`) exposes:

- YAML presence state (loaded, missing, invalid, unrecognized keys / update available)
- Local setup/archive script editors and env var reference
- **Setup run policy** (`ask` / run by default / skip by default)
- **Script source** (`orca.yaml` only / local only / run both)
- Issue command override editor (backed by `repo.issueCommandRead` / `repo.issueCommandWrite`)
- Import candidates from other tools (`repo.setupScriptImports` — Conductor, Superset, Codex environment, Cmux, etc.)

Runtime RPC mirrors desktop checks: `repo.hooks`, `repo.hooksCheck`, `repo.issueCommandRead`, `repo.issueCommandWrite`.

## CLI `--run-hooks`

| Command | Flag | Effect |
| --- | --- | --- |
| `orca worktree create` | `--run-hooks` | Force setup; imply `--activate`; may print warning if setup still skipped |
| `orca worktree rm` | `--run-hooks` | Run archive hook before git removal |

Without `--run-hooks`, runtime RPC returns a warning string (also printed to stderr in text mode):

- `orca.yaml setup hook skipped for <path>; pass --run-hooks to run it.`
- `orca.yaml archive hook skipped for <path>; pass --run-hooks to run it.`

JSON output includes the same `warning` field on the result object.

Desktop `worktree rm` maps trust **skip** to `skipArchive` / `runHooks: false` on the runtime call. Desktop create does not use `--run-hooks`; it uses `setupDecision` and trust instead.

## SSH and remote runtimes

On SSH-connected repos, `orca.yaml` is read through the remote filesystem provider. Archive/setup execution uses remote non-interactive exec with the same env var semantics where applicable. Trust hashing for shared setup can use remote file content. Local-only policy still prevents trusting or running shared scripts you intentionally overrode in settings.

## Verification

<Steps>
<Step title="Confirm shared hooks are visible">
In Orca, open repository settings and confirm the `orca.yaml` card shows **Using `orca.yaml`** (or add the file from the example template).
</Step>
<Step title="Create with CLI hooks">
```bash
orca worktree create --repo path:/path/to/repo --name hook-test --run-hooks --json
```
Expect no setup `warning` in JSON when `scripts.setup` exists and policy allows run.
</Step>
<Step title="Remove with archive hook">
```bash
orca worktree rm --worktree path:/path/to/repo/.git/worktrees/hook-test --run-hooks --json
```
Archive runs before removal when `scripts.archive` is defined.
</Step>
<Step title="Trust gate in UI">
Change `scripts.setup` in `orca.yaml`, create a worktree from the composer, and confirm the trust dialog shows the new script hash before execution.
</Step>
</Steps>

## Related pages

<CardGroup>
<Card title="Worktrees and repos" href="/worktrees-and-repos">
Git worktree registration, create/remove semantics, and how Orca paths relate to hooks.
</Card>
<Card title="CLI core reference" href="/cli-core-reference">
Full `worktree create` and `worktree rm` flags including `--run-hooks` and `--activate`.
</Card>
<Card title="Terminals and agents" href="/terminals-and-agents">
How setup and issue-command runners attach to the first terminal and agent startup.
</Card>
<Card title="SSH remote worktrees" href="/ssh-remote-worktrees">
Remote `orca.yaml` reads and hook execution over SSH.
</Card>
<Card title="Settings reference" href="/settings-reference">
Where `RepoHookSettings` and global UI trust state persist.
</Card>
</CardGroup>
