> ## Documentation Index
> Fetch the complete documentation index at: https://goldengoose.zue.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Hooks Reference

> Team Hooks v1 configuration, execution model, events, and script contracts.

Team Hooks let you run shell scripts on team lifecycle events and inject lifecycle context into agent sessions.

## v1 contract lock (hard cut-over)

These contracts are current and authoritative for v1:

* `post_teammate_remove` fires for `operation_source=agent_tool` and `operation_source=ui_command`.
* Native worktree mode is first-class when `worktree_name` is provided on `gg_team_manage(add)`.
* `gg_team_manage(add)` supports both create-new and existing-worktree reuse (`use_existing_worktree=true`) when `worktree_name` is set.
* In native worktree mode, runtime owns branch/worktree/cwd derivation; hook `spawn_template_mutation.cwd` does not override final native spawn cwd.
* `pre_teammate_add` scripts execute with process cwd set to `pre_teammate_add.template_source_session_cwd`.
* Native planning metadata fields in `pre_teammate_add` input are readable context only, not a control plane for native branch/cwd selection.
* Worktree init (`.agents/gg/worktree-init.sh`) is a separate runtime contract from Team Hooks and is documented at [Configure: Worktree Init](/configure/worktree-init).

## Team Hooks vs worktree init

Keep these boundaries explicit:

* `pre_teammate_add` runs before native worktree creation in the source session cwd (`pre_teammate_add.template_source_session_cwd`).
* Worktree init runs after new native worktree creation in the new worktree cwd.
* `run_pre_teammate_add_hooks=false` (Add Agent pre-hook checkbox off) bypasses hooks only; it does not disable worktree init.

## Supported events

Team Hooks v1 supports exactly six events:

* `pre_teammate_add`
* `post_teammate_add`
* `post_teammate_remove`
* `post_direct_message_send`
* `post_broadcast_message_send`
* `post_compaction`

## Config file locations

gg resolves hooks from two files:

* User-level: `~/.agents/gg/team-hooks.json`
* Project-level: `<workspace-or-session-cwd>/.agents/gg/team-hooks.json`

Both can exist at the same time.

## Merge and override rules

Effective hooks are merged per event using normalized hook name:

1. Load user hooks.
2. Load project hooks.
3. If `(event, name)` collides, project entry overrides user entry.
4. Unique names are unioned.
5. Missing files are treated as no-op.

Hook names are normalized (trim + lowercase + separators collapsed to `_`) before duplicate/override matching.

## Config schema

```json theme={null}
{
  "version": 1,
  "hooks": {
    "pre_teammate_add": [],
    "post_teammate_add": [],
    "post_teammate_remove": [],
    "post_direct_message_send": [],
    "post_broadcast_message_send": [],
    "post_compaction": []
  }
}
```

Each hook entry:

```json theme={null}
{
  "name": "worktree_setup",
  "command": "~/.agents/gg/hooks/pre-add.sh",
  "timeout_ms": 15000
}
```

Validation rules:

* `version` must be `1`.
* `name` and `command` are required.
* `timeout_ms` is optional (default `15000`).
* `timeout_ms` must be between `100` and `120000`.
* Unknown keys are rejected.
* Duplicate hook names within the same event are rejected after normalization.

## Execution model

Each hook runs as a shell command:

* macOS/Linux: `sh -lc "<command>"`
* Windows: `cmd /C "<command>"`

Runtime behavior:

* Input payload is written as JSON to `stdin`.
* Hook must write JSON to `stdout`.
* Non-zero exit is a failure.
* Empty or invalid JSON `stdout` is a failure.
* Hooks for the same event run concurrently.
* Aggregation is deterministic in effective hook order.

## Trust boundary

Team hooks are trusted local shell execution:

* Project and user hook scripts run as the desktop app user on the local machine.
* No hook sandboxing or separate hook permission gate is added in v1.
* Treat hook config and scripts as trusted code, not untrusted input.

## Lifecycle delivery lanes

When hook output is applied, gg uses two coordinated lanes:

* Model-context lane: text is injected with automation projection semantics (not a user message lane).
* UI lane: gg appends typed timeline `system_notice` rows for lifecycle visibility.

Hook lifecycle notice kinds:

* `team_hook_pre_teammate_add_notice`
* `team_hook_post_teammate_add_notice`
* `team_hook_post_teammate_remove_notice`
* `team_hook_post_direct_message_send_notice`
* `team_hook_post_broadcast_message_send_notice`
* `team_hook_post_compaction_notice`
* `team_member_compaction_notice` (baseline compaction notice kind used when no post-compaction override applies per recipient)

## Event firing semantics

| Event                         | Fires when                                             | Notes                                                                                                                                                                                                                                                                                                                                                                                  |
| ----------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pre_teammate_add`            | Before native worktree creation and session creation   | Blocking (fail-closed). Runs in source session cwd (`pre_teammate_add.template_source_session_cwd`). Can request spawn cwd mutation for non-native adds, append onboarding text, and apply `inject` after spawn with `team_hook_pre_teammate_add_notice`. In native mode (`worktree_name` set), runtime-derived branch/worktree/cwd is authoritative and hook cwd mutation is ignored. |
| `post_teammate_add`           | After canonical member-add commit                      | Only for `agent_tool` source. Applies caller/target injection via typed `team_hook_post_teammate_add_notice`.                                                                                                                                                                                                                                                                          |
| `post_teammate_remove`        | After canonical member-remove commit                   | For `agent_tool` and `ui_command` sources. Injection to removed member is suppressed; surviving recipients use `team_hook_post_teammate_remove_notice`.                                                                                                                                                                                                                                |
| `post_direct_message_send`    | After direct message reaches delivery-complete success | Only for `agent_tool` source. No fire on partial failure/terminal failure. Typed notice kind: `team_hook_post_direct_message_send_notice`.                                                                                                                                                                                                                                             |
| `post_broadcast_message_send` | After broadcast success by delivery rules              | Only for `agent_tool` source. Remove-race success is supported. Typed notice kind: `team_hook_post_broadcast_message_send_notice`.                                                                                                                                                                                                                                                     |
| `post_compaction`             | During compaction runtime handling                     | Runtime-source hook. Hook injection recipients use `team_hook_post_compaction_notice`. Baseline fallback recipients depend on runtime status (`executed`, `no_hooks`, `resolution_failed`, `execution_failed`) described below.                                                                                                                                                        |

## Source filtering rules

Source eligibility is event-specific:

* Allowed for `post_teammate_add`, `post_direct_message_send`, `post_broadcast_message_send`: `operation_source = agent_tool` only.
* Allowed for `post_teammate_remove`: `operation_source = agent_tool` and `operation_source = ui_command`.
* `pre_teammate_add` and `post_compaction` are source-agnostic at runtime.

Practical effect:

* Automation/system DMs (`tool_automation`) are excluded from message hooks.
* UI command-originated member removes do trigger `post_teammate_remove`.
* UI command-originated post-add/message operations remain filtered.

## Delivery-complete behavior (direct and broadcast)

`post_direct_message_send` fires only when the message is fully delivered.

`post_broadcast_message_send` fires on either:

* full delivery, or
* terminal completion where every recipient still currently in the team has been injected.

That second rule is what allows broadcast remove-race success:

* If recipient B is removed mid-flight, recipient B no longer blocks hook success.
* If an active remaining recipient fails delivery, hook does not fire.

## Failure behavior

* `pre_teammate_add`: fail-closed (operation is blocked on hook failure).
* All `post_*`: fail-open (core operation already committed and does not roll back).

Fail-open does not mean silent:

* Hook failures are logged and recorded in diagnostics.
* Successful hook outputs can still be applied when other hooks fail.

## `post_compaction` runtime status branches

Compaction fallback logic is intentionally branch-specific and should not be treated as one combined “no hook” behavior.

* `executed`: effective hooks resolved and ran. Hook output can override recipients/message text. Override notices use `team_hook_post_compaction_notice`.
* `no_hooks`: no effective `post_compaction` hooks were configured for the compacted member context. Runtime switches to `creator_and_subscribers_only` fallback targeting by default (the compacted member is excluded).
* `resolution_failed`: hook resolution failed (for example, config load/parse issues). Runtime falls back to the canonical baseline target contract.
* `execution_failed`: hooks resolved but execution failed. Runtime falls back to the canonical baseline target contract.

Canonical baseline fallback contract (used for `resolution_failed` and `execution_failed`) is not the same as `no_hooks`: it keeps standard baseline targeting semantics and baseline notice kind `team_member_compaction_notice` unless hook-produced overrides exist.

## Input contract

Every hook receives this base envelope:

```json theme={null}
{
  "schema_version": 1,
  "hook_event": "post_direct_message_send",
  "event_id": "evt_123",
  "occurred_at_ms": 1772478000000,
  "team_id": "team_3",
  "correlation": {
    "correlation_id": "gg_team_op:...",
    "idempotency_key": "auto:gg_team_op:..."
  },
  "actor": {
    "session_id": "sess_lead",
    "identity_alias": "military_coral"
  },
  "subject": {
    "kind": "message",
    "message_id": "msg_8",
    "sender_agent_id": "sess_lead",
    "recipient_agent_ids": ["sess_member"]
  },
  "source": {
    "operation_source": "agent_tool",
    "ingress": "team_tool"
  }
}
```

### Event-specific input examples

#### `pre_teammate_add`

```json theme={null}
{
  "schema_version": 1,
  "hook_event": "pre_teammate_add",
  "event_id": "pre_teammate_add:op_123",
  "occurred_at_ms": 1772478000000,
  "team_id": "team_3",
  "correlation": {
    "correlation_id": "gg_team_hook:pre_teammate_add:op_123",
    "idempotency_key": "gg_team_hook:pre_teammate_add:op_123:v1"
  },
  "actor": {
    "session_id": "sess_lead",
    "identity_alias": "military_coral"
  },
  "subject": {
    "kind": "member",
    "member_agent_ids": []
  },
  "source": {
    "operation_source": "agent_tool",
    "ingress": "team_tool"
  },
  "pre_teammate_add": {
    "caller_agent_id": "sess_lead",
    "template_source_session_id": "sess_lead",
    "native_worktree_requested": true,
    "member_title": "frontend",
    "onboarding_prompt": "Review repo docs first.",
    "model_preset": "gpt-5.3-codex",
    "requested_worktree_name": "native-feature",
    "planned_branch_name": "gg/native-feature",
    "planned_worktree_cwd": "/Users/me/.gg/worktrees/Users__me__code__repo/gg--native-feature",
    "planned_worktree_root": "/Users/me/.gg/worktrees/Users__me__code__repo",
    "planned_unified_workspace_path": "Users__me__code__repo",
    "template_source_session_cwd": "/Users/me/code/repo",
    "requested_spawn_template_mutation": {
      "cwd": "/tmp/team-a"
    }
  }
}
```

#### `post_teammate_add`

```json theme={null}
{
  "schema_version": 1,
  "hook_event": "post_teammate_add",
  "event_id": "evt_member_add",
  "occurred_at_ms": 1772478001234,
  "team_id": "team_3",
  "correlation": {
    "correlation_id": "gg_team_op:team_tool:member_add:seed",
    "idempotency_key": "auto:gg_team_op:team_tool:member_add:seed"
  },
  "actor": {
    "session_id": "sess_lead",
    "identity_alias": "military_coral"
  },
  "subject": {
    "kind": "member",
    "member_agent_ids": ["sess_new_member"]
  },
  "source": {
    "operation_source": "agent_tool",
    "ingress": "team_tool"
  }
}
```

#### `post_teammate_remove`

```json theme={null}
{
  "schema_version": 1,
  "hook_event": "post_teammate_remove",
  "event_id": "evt_member_remove",
  "occurred_at_ms": 1772478002345,
  "team_id": "team_3",
  "correlation": {
    "correlation_id": "gg_team_op:team_tool:member_remove:seed",
    "idempotency_key": "auto:gg_team_op:team_tool:member_remove:seed"
  },
  "actor": {
    "session_id": "sess_lead",
    "identity_alias": "military_coral"
  },
  "subject": {
    "kind": "member",
    "member_agent_ids": ["sess_removed_member"]
  },
  "source": {
    "operation_source": "agent_tool",
    "ingress": "team_tool"
  }
}
```

#### `post_direct_message_send`

```json theme={null}
{
  "schema_version": 1,
  "hook_event": "post_direct_message_send",
  "event_id": "evt_dm",
  "occurred_at_ms": 1772478003456,
  "team_id": "team_3",
  "correlation": {
    "correlation_id": "gg_team_op:team_tool:message_direct_send:seed",
    "idempotency_key": "post_direct_idem"
  },
  "actor": {
    "session_id": "sess_sender",
    "identity_alias": "military_coral"
  },
  "subject": {
    "kind": "message",
    "message_id": "msg_direct_1",
    "sender_agent_id": "sess_sender",
    "recipient_agent_ids": ["sess_recipient"]
  },
  "source": {
    "operation_source": "agent_tool",
    "ingress": "team_tool"
  }
}
```

#### `post_broadcast_message_send`

```json theme={null}
{
  "schema_version": 1,
  "hook_event": "post_broadcast_message_send",
  "event_id": "evt_broadcast",
  "occurred_at_ms": 1772478004567,
  "team_id": "team_3",
  "correlation": {
    "correlation_id": "gg_team_op:team_tool:message_broadcast_send:seed",
    "idempotency_key": "post_broadcast_idem"
  },
  "actor": {
    "session_id": "sess_sender",
    "identity_alias": "military_coral"
  },
  "subject": {
    "kind": "message",
    "message_id": "msg_broadcast_1",
    "sender_agent_id": "sess_sender",
    "recipient_agent_ids": ["sess_a", "sess_b", "sess_c"]
  },
  "source": {
    "operation_source": "agent_tool",
    "ingress": "team_tool"
  }
}
```

#### `post_compaction`

```json theme={null}
{
  "schema_version": 1,
  "hook_event": "post_compaction",
  "event_id": "evt_compaction",
  "occurred_at_ms": 1772478005678,
  "team_id": "team_3",
  "correlation": {
    "correlation_id": "gg_team_op:runtime:compaction_signal:seed",
    "idempotency_key": "auto:gg_team_op:runtime:compaction_signal:seed"
  },
  "actor": {
    "session_id": "sess_compacted",
    "identity_alias": "silver_lynx"
  },
  "subject": {
    "kind": "compaction",
    "compacted_member_session_id": "sess_compacted",
    "subscriber_agent_ids": ["sess_lead"],
    "creator_member_session_id": "sess_creator",
    "creator_member_identity_alias": "military_coral",
    "turn_id": "turn_123",
    "source": "context_compaction_item",
    "confidence": "high",
    "detected_at_ms": 1772478005678
  },
  "source": {
    "operation_source": "runtime",
    "ingress": "runtime"
  }
}
```

## Output contract

Base output shape:

```json theme={null}
{
  "inject": {
    "caller": {
      "text": "Optional caller text"
    },
    "targets": [
      {
        "agent_id": "sess_target",
        "text": "Optional target text"
      }
    ]
  }
}
```

### `pre_teammate_add` output extension

```json theme={null}
{
  "pre_teammate_add": {
    "spawn_template_mutation": {
      "cwd": "/path/to/worktree"
    },
    "onboarding_context_append_text": "Worktree initialized and ready."
  },
  "inject": {
    "caller": {
      "text": "Prepared teammate workspace."
    },
    "targets": [
      {
        "agent_id": "__new_member__",
        "text": "Use /path/to/worktree as your cwd."
      }
    ]
  }
}
```

Notes:

* `pre_teammate_add` extension is valid only for the `pre_teammate_add` event.
* `inject.caller.text` is applied to the caller session context after the new member session exists.
* `inject.targets[].agent_id = "__new_member__"` is a reserved token for `pre_teammate_add` that resolves to the spawned member session after creation.
* For `pre_teammate_add`, target IDs other than `__new_member__` are treated as concrete session IDs.
* If multiple pre hooks return conflicting `spawn_template_mutation.cwd` values, pre-hook execution fails.
* If `native_worktree_requested=true`, runtime-derived native branch/worktree/cwd remains authoritative and hook-provided `spawn_template_mutation.cwd` is ignored for final spawn cwd selection.
* Native planning metadata fields (`requested_worktree_name`, `planned_branch_name`, `planned_worktree_cwd`, `planned_worktree_root`, `planned_unified_workspace_path`) are readable by hooks but are not a control plane for native branch/cwd selection.
* `pre_teammate_add` hooks execute with process cwd set to `pre_teammate_add.template_source_session_cwd`.
* `run_pre_teammate_add_hooks=false` hard-bypasses all pre-hook side effects (cwd mutation, onboarding append text, and inject output) but does not disable worktree init for create-new native worktree adds.
* For `post_teammate_remove`, runtime suppresses any injection targeted at removed member session IDs.

## Applicability and suppression rules

* Message-hook applicability is delivery-aware: `post_direct_message_send` requires full delivery; `post_broadcast_message_send` also permits terminal success when all still-eligible recipients were injected.
* Post hook execution is source-filtered by event:
* `post_teammate_add`, `post_direct_message_send`, `post_broadcast_message_send`: `operation_source=agent_tool`.
* `post_teammate_remove`: `operation_source=agent_tool` and `operation_source=ui_command`.
* `pre_teammate_add` and `post_compaction` are source-agnostic.
* Recipient applicability is delivery-aware and membership-aware: recipients no longer in the team are excluded from broadcast terminal-success evaluation and post-remove target injection.

## Example scripts for all six events

These are minimal shell examples. Replace paths and logic for your workflow.

### 1) `pre_teammate_add`

```bash theme={null}
#!/usr/bin/env bash
set -euo pipefail

payload="$(cat)"
native_requested="$(echo "$payload" | jq -r '.pre_teammate_add.native_worktree_requested // false')"
template_cwd="$(echo "$payload" | jq -r '.pre_teammate_add.requested_spawn_template_mutation.cwd // ""')"

if [[ "$native_requested" == "true" ]]; then
  jq -nc '{
    inject: {
      caller: { text: "Native worktree requested; runtime-managed branch/cwd will be used." },
      targets: [{ agent_id: "__new_member__", text: "Runtime created your native worktree and set cwd automatically." }]
    }
  }'
  exit 0
fi

if [[ -n "$template_cwd" ]]; then
  worktree="$template_cwd"
else
  worktree="/tmp/gg-worktrees/$(date +%s)"
fi

mkdir -p "$worktree"

jq -nc --arg cwd "$worktree" '{
  pre_teammate_add: {
    spawn_template_mutation: { cwd: $cwd },
    onboarding_context_append_text: ("Workspace ready at " + $cwd)
  },
  inject: {
    caller: { text: ("Prepared workspace " + $cwd) },
    targets: [{ agent_id: "__new_member__", text: ("Use " + $cwd + " as your cwd.") }]
  }
}'
```

### 2) `post_teammate_add`

```bash theme={null}
#!/usr/bin/env bash
set -euo pipefail
payload="$(cat)"
member="$(echo "$payload" | jq -r '.subject.member_agent_ids[0] // ""')"
jq -nc --arg member "$member" '{
  inject: {
    caller: { text: ("Added teammate: " + $member) },
    targets: [{ agent_id: $member, text: "Welcome. Start with README.md and TODOs." }]
  }
}'
```

### 3) `post_teammate_remove`

```bash theme={null}
#!/usr/bin/env bash
set -euo pipefail
payload="$(cat)"
removed="$(echo "$payload" | jq -r '.subject.member_agent_ids[]?')"
jq -nc --arg removed "$removed" '{
  inject: {
    caller: { text: ("Removed teammate: " + $removed) }
  }
}'
```

### 4) `post_direct_message_send`

```bash theme={null}
#!/usr/bin/env bash
set -euo pipefail
payload="$(cat)"
recipient="$(echo "$payload" | jq -r '.subject.recipient_agent_ids[0] // ""')"
jq -nc --arg recipient "$recipient" '{
  inject: {
    caller: { text: ("Direct message delivered to " + $recipient) }
  }
}'
```

### 5) `post_broadcast_message_send`

```bash theme={null}
#!/usr/bin/env bash
set -euo pipefail
payload="$(cat)"
count="$(echo "$payload" | jq '.subject.recipient_agent_ids | length')"
jq -nc --arg count "$count" '{
  inject: {
    caller: { text: ("Broadcast delivery completed for " + $count + " recipients") }
  }
}'
```

### 6) `post_compaction`

```bash theme={null}
#!/usr/bin/env bash
set -euo pipefail
payload="$(cat)"
compacted="$(echo "$payload" | jq -r '.subject.compacted_member_session_id')"
lead="$(echo "$payload" | jq -r '.subject.subscriber_agent_ids[0] // ""')"

jq -nc --arg compacted "$compacted" --arg lead "$lead" '{
  inject: {
    caller: { text: ("Compaction detected for " + $compacted + ". Summarize recent decisions before continuing.") },
    targets: (
      if $lead == "" then []
      else [{ agent_id: $lead, text: ("Compaction handled for " + $compacted) }]
      end
    )
  }
}'
```

## Timeline rendering contract

Lifecycle hook notices are rendered from typed timeline `system_notice` rows, not from non-transport wrapper parsing.

Legacy non-transport `team_hook_*` wrapper notices are not a supported runtime path.

## Related pages

* [How It Works: Hooks](/how-it-works/hooks)
* [How It Works: Team Management](/how-it-works/team-management)
* [Configure: Worktree Init](/configure/worktree-init)
