Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions content/copilot/reference/hooks-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,158 @@ if [ "$RESULT_TYPE" = "failure" ]; then
fi
```

### Agent stop hook

Executed when the main agent finishes responding to a prompt and is about to stop. Use this hook to log the end of an agent turn or to inject a follow-up instruction by blocking the stop. To handle full session completion, use the session end hook. When you block, the `reason` you provide is enqueued as the next user prompt, so the agent continues with that input.

**Example input JSON:**

```json copy
{
"timestamp": 1704614750000,
"cwd": "/path/to/project",
"sessionId": "01HW2X3Y4Z5...",
"transcriptPath": "/path/to/transcript.jsonl",
"stopReason": "end_turn"
}
```

**Fields:**

* `timestamp`: Unix timestamp in milliseconds
* `cwd`: Current working directory
* `sessionId`: The unique identifier of the current session
* `transcriptPath`: Path to the JSONL transcript file for the session
* `stopReason`: Why the agent is stopping (currently always `"end_turn"`)

**Output JSON (optional):**

```json copy
{
"decision": "block",
"reason": "Run the test suite before stopping."
}
```

**Output fields:**

* `decision`: Set to `"block"` to keep the agent running by enqueueing `reason` as the next user prompt. Omit the field, or return `{}`, to allow the stop.
* `reason`: The text to feed back into the agent as a new prompt when blocking. Required when `decision` is `"block"`.

**Example script that asks the agent to summarize before stopping:**

```shell copy
#!/bin/bash
INPUT=$(cat)

# Avoid an infinite loop: only inject a follow-up if no summary marker exists yet
TRANSCRIPT=$(echo "$INPUT" | jq -r '.transcriptPath')
if [ -f "$TRANSCRIPT" ] && grep -q "## Session summary" "$TRANSCRIPT"; then
echo "{}"
exit 0
fi

echo '{"decision":"block","reason":"Before you stop, write a one-paragraph session summary under a `## Session summary` heading."}'
```

### Subagent stop hook

Executed when a subagent finishes its turn, before its output is returned to the parent agent. Use this hook to log subagent activity or to keep the subagent running by injecting follow-up instructions.

**Example input JSON:**

```json copy
{
"timestamp": 1704614760000,
"cwd": "/path/to/project",
"sessionId": "01HW2X3Y4Z5...",
"transcriptPath": "/path/to/subagent-transcript.jsonl",
"agentName": "researcher",
"agentDisplayName": "Research Agent",
"stopReason": "end_turn"
}
```

**Fields:**

* `timestamp`: Unix timestamp in milliseconds
* `cwd`: Current working directory
* `sessionId`: The unique identifier of the subagent session
* `transcriptPath`: Path to the JSONL transcript file for the subagent
* `agentName`: The internal name of the subagent
* `agentDisplayName`: The human-readable display name of the subagent
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agentDisplayName is optional in the CLI hook payload (it may be absent for some subagents). The field list currently reads as if it’s always present; update the description to mark it as optional and consider noting what to expect when it isn’t provided.

Suggested change
* `agentDisplayName`: The human-readable display name of the subagent
* `agentDisplayName` (optional): The human-readable display name of the subagent. This field is omitted for some subagents.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. I checked three surfaces in the @github/copilot v1.0.32 bundle and they all agree this field is required:

  • The CLI runtime schema marks it as Se().describe("Human-readable display name of the sub-agent") (no .optional()).
  • The exported SDK types (@github/copilot/sdk) declare it as agentDisplayName: z.ZodString and the inferred TypeScript type is agentDisplayName: string (not string | undefined).
  • The public JSON Schema (schemas/session-events.schema.json) lists agentDisplayName in the required array for both subagent.started and subagent.completed event data.

For comparison, sibling fields like model and durationMs are explicitly marked optional in all three surfaces, so the absence of optional markers on agentDisplayName is intentional. Going to leave the description as is.

* `stopReason`: Why the subagent is stopping (currently always `"end_turn"`)

**Output JSON (optional):**

```json copy
{
"decision": "block",
"reason": "Cite at least three sources before returning."
}
```

**Output fields:**

* `decision`: Set to `"block"` to keep the subagent running by injecting `reason` as the next message. Omit, or return `{}`, to let the subagent return its result to the parent.
* `reason`: The text to feed back into the subagent. Required when `decision` is `"block"`.

**Example script that logs every subagent completion:**

```shell copy
#!/bin/bash
INPUT=$(cat)
NAME=$(echo "$INPUT" | jq -r '.agentName')
SESSION=$(echo "$INPUT" | jq -r '.sessionId')
echo "$(date -Iseconds) subagent=$NAME session=$SESSION" >> ~/.copilot/subagent-activity.log
echo "{}"
```

### Pre-compact hook

Executed just before the conversation is compacted to free space in the context window. Compaction summarizes earlier messages and discards the originals, so this is a useful point to persist details that the summary may not preserve.

**Example input JSON:**

```json copy
{
"timestamp": 1704614800000,
"cwd": "/path/to/project",
"sessionId": "01HW2X3Y4Z5...",
"transcriptPath": "/path/to/transcript.jsonl",
"trigger": "auto",
"customInstructions": ""
}
```

**Fields:**

* `timestamp`: Unix timestamp in milliseconds
* `cwd`: Current working directory
* `sessionId`: The unique identifier of the current session
* `transcriptPath`: Path to the JSONL transcript file for the session
* `trigger`: How compaction was started. Either `"auto"` (the context window approached its limit) or `"manual"` (the user requested compaction)
* `customInstructions`: Any custom instructions the user provided to guide the compaction. Empty string when none were provided.

**Output:** Ignored. Use this hook for side effects such as exporting the transcript or notifying an external system.

**Example script that archives the transcript before each compaction:**

```shell copy
#!/bin/bash
INPUT=$(cat)
TRANSCRIPT=$(echo "$INPUT" | jq -r '.transcriptPath')
SESSION=$(echo "$INPUT" | jq -r '.sessionId')
TRIGGER=$(echo "$INPUT" | jq -r '.trigger')

ARCHIVE_DIR="$HOME/.copilot/transcripts"
mkdir -p "$ARCHIVE_DIR"

if [ -f "$TRANSCRIPT" ]; then
cp "$TRANSCRIPT" "$ARCHIVE_DIR/${SESSION}-pre-compact-${TRIGGER}-$(date +%s).jsonl"
fi
```

### Error occurred hook

Executed when an error occurs during agent execution.
Expand Down
Loading