# 세션 수명 주기 후크

세션 수명 주기 후크를 사용하면 세션 시작 및 종료 이벤트에 응답할 수 있습니다. 이를 사용하여 다음을 수행합니다.

<!-- markdownlint-disable GHD046 GHD005 -->

<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->

* 세션이 시작될 때 컨텍스트 초기화
* 세션이 종료되면 리소스 정리
* 세션 메트릭 및 분석 추적
* 동적으로 세션 동작 구성

## 세션 시작 후크 {#session-start}

세션이 `onSessionStart` 시작될 때 후크가 호출됩니다(신규 또는 다시 시작).

### 후크 서명

<div class="ghd-codetabs">
<div class="ghd-codetab" data-lang="typescript" data-label="TypeScript"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">TypeScript</div>

```typescript
type SessionStartHandler = (
  input: SessionStartHookInput,
  invocation: HookInvocation
) => Promise<SessionStartHookOutput | null | undefined>;
```

</div>

<div class="ghd-codetab" data-lang="python" data-label="Python"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Python</div>

```python
SessionStartHandler = Callable[
    [SessionStartHookInput, dict[str, str]],
    Awaitable[SessionStartHookOutput | None]
]
```

</div>

<div class="ghd-codetab" data-lang="go" data-label="Go"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Go</div>

```golang
type SessionStartHandler func(
    input SessionStartHookInput,
    invocation HookInvocation,
) (*SessionStartHookOutput, error)
```

</div>

<div class="ghd-codetab" data-lang="dotnet" data-label=".NET"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">.NET</div>

```csharp
public delegate Task<SessionStartHookOutput?> SessionStartHandler(
    SessionStartHookInput input,
    HookInvocation invocation);
```

</div>

<div class="ghd-codetab" data-lang="java" data-label="Java"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Java</div>

```java
@FunctionalInterface
public interface SessionStartHandler {
    CompletableFuture<SessionStartHookOutput> handle(
        SessionStartHookInput input,
        HookInvocation invocation);
}
```

</div>

</div>

### 입력

| Field           | Type           | 설명                     |
| --------------- | -------------- | ---------------------- |
| `timestamp`     | number         | 후크가 트리거될 때의 Unix 타임스탬프 |
| `cwd`           | string         | 현재 작업 디렉터리             |
| `source`        |                |                        |
| `"startup"`     |                |                        |
| \|              |                |                        |
| `"resume"`      |                |                        |
| \|              |                |                        |
| `"new"`         |                |                        |
| 세션이 시작된 방법      |                |                        |
| `initialPrompt` | 정의되지 않은 문자열 \| | 제공된 경우 초기 프롬프트         |

### 출력

| Field               | Type   | 설명               |
| ------------------- | ------ | ---------------- |
| `additionalContext` | string | 세션 시작 시 추가할 컨텍스트 |
| `modifiedConfig`    | object | 세션 구성 재정의        |

### 예제

#### 시작 시 프로젝트 컨텍스트 추가

<div class="ghd-codetabs">
<div class="ghd-codetab" data-lang="typescript" data-label="TypeScript"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">TypeScript</div>

```typescript
const session = await client.createSession({
  hooks: {
    onSessionStart: async (input, invocation) => {
      console.log(`Session ${invocation.sessionId} started (${input.source})`);
      
      const projectInfo = await detectProjectType(input.cwd);
      
      return {
        additionalContext: `
This is a ${projectInfo.type} project.
Main language: ${projectInfo.language}
Package manager: ${projectInfo.packageManager}
        `.trim(),
      };
    },
  },
});
```

</div>

<div class="ghd-codetab" data-lang="python" data-label="Python"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Python</div>

```python
from copilot.session import PermissionHandler

async def on_session_start(input_data, invocation):
    print(f"Session {invocation['session_id']} started ({input_data['source']})")
    
    project_info = await detect_project_type(input_data["cwd"])
    
    return {
        "additionalContext": f"""
This is a {project_info['type']} project.
Main language: {project_info['language']}
Package manager: {project_info['packageManager']}
        """.strip()
    }

session = await client.create_session(on_permission_request=PermissionHandler.approve_all, hooks={"on_session_start": on_session_start})
```

</div>

</div>

#### 세션 재시작 처리

```typescript
const session = await client.createSession({
  hooks: {
    onSessionStart: async (input, invocation) => {
      if (input.source === "resume") {
        // Load previous session state
        const previousState = await loadSessionState(invocation.sessionId);
        
        return {
          additionalContext: `
Session resumed. Previous context:
- Last topic: ${previousState.lastTopic}
- Open files: ${previousState.openFiles.join(", ")}
          `.trim(),
        };
      }
      return null;
    },
  },
});
```

#### 사용자 기본 설정 로드

```typescript
const session = await client.createSession({
  hooks: {
    onSessionStart: async () => {
      const preferences = await loadUserPreferences();
      
      const contextParts = [];
      
      if (preferences.language) {
        contextParts.push(`Preferred language: ${preferences.language}`);
      }
      if (preferences.codeStyle) {
        contextParts.push(`Code style: ${preferences.codeStyle}`);
      }
      if (preferences.verbosity === "concise") {
        contextParts.push("Keep responses brief and to the point.");
      }
      
      return {
        additionalContext: contextParts.join("\n"),
      };
    },
  },
});
```

## 세션 종료 후크 {#session-end}

세션이 `onSessionEnd` 종료되면 후크가 호출됩니다.

### 후크 서명

<div class="ghd-codetabs">
<div class="ghd-codetab" data-lang="typescript" data-label="TypeScript"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">TypeScript</div>

```typescript
type SessionEndHandler = (
  input: SessionEndHookInput,
  invocation: HookInvocation
) => Promise<SessionEndHookOutput | null | undefined>;
```

</div>

<div class="ghd-codetab" data-lang="python" data-label="Python"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Python</div>

```python
SessionEndHandler = Callable[
    [SessionEndHookInput, dict[str, str]],
    Awaitable[SessionEndHookOutput | None]
]
```

</div>

<div class="ghd-codetab" data-lang="go" data-label="Go"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Go</div>

```golang
type SessionEndHandler func(
    input SessionEndHookInput,
    invocation HookInvocation,
) (*SessionEndHookOutput, error)
```

</div>

<div class="ghd-codetab" data-lang="dotnet" data-label=".NET"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">.NET</div>

```csharp
public delegate Task<SessionEndHookOutput?> SessionEndHandler(
    SessionEndHookInput input,
    HookInvocation invocation);
```

</div>

<div class="ghd-codetab" data-lang="java" data-label="Java"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Java</div>

```java
@FunctionalInterface
public interface SessionEndHandler {
    CompletableFuture<SessionEndHookOutput> handle(
        SessionEndHookInput input,
        HookInvocation invocation);
}
```

</div>

</div>

### 입력

| Field          | Type           | 설명                       |
| -------------- | -------------- | ------------------------ |
| `timestamp`    | number         | 후크가 트리거될 때의 Unix 타임스탬프   |
| `cwd`          | string         | 현재 작업 디렉터리               |
| `reason`       | string         | 세션이 종료된 이유(아래 참조)        |
| `finalMessage` | 정의되지 않은 문자열 \| | 세션의 마지막 메시지              |
| `error`        | 정의되지 않은 문자열 \| | 오류로 인해 세션이 종료된 경우 오류 메시지 |

#### 종료 이유

| Reason        | 설명                         |
| ------------- | -------------------------- |
| `"complete"`  | 세션이 정상적으로 완료됨              |
| `"error"`     | 오류로 인해 세션이 종료됨             |
| `"abort"`     | 사용자 또는 코드에 의해 세션이 중단되었습니다. |
| `"timeout"`   | 세션 시간이 초과됨                 |
| `"user_exit"` | 사용자가 세션을 명시적으로 종료했습니다.     |

### 출력

| Field            | Type      | 설명              |
| ---------------- | --------- | --------------- |
| `suppressOutput` | boolean   | 최종 세션 출력 표시 안 함 |
| `cleanupActions` | string\[] | 수행할 정리 작업 목록    |
| `sessionSummary` | string    | 로깅/분석을 위한 세션 요약 |

### 예제

#### 세션 메트릭 추적

<div class="ghd-codetabs">
<div class="ghd-codetab" data-lang="typescript" data-label="TypeScript"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">TypeScript</div>

```typescript
const sessionStartTimes = new Map<string, number>();

const session = await client.createSession({
  hooks: {
    onSessionStart: async (input, invocation) => {
      sessionStartTimes.set(invocation.sessionId, input.timestamp);
      return null;
    },
    onSessionEnd: async (input, invocation) => {
      const startTime = sessionStartTimes.get(invocation.sessionId);
      const duration = startTime ? input.timestamp - startTime : 0;
      
      await recordMetrics({
        sessionId: invocation.sessionId,
        duration,
        endReason: input.reason,
      });
      
      sessionStartTimes.delete(invocation.sessionId);
      return null;
    },
  },
});
```

</div>

<div class="ghd-codetab" data-lang="python" data-label="Python"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Python</div>

```python
from copilot.session import PermissionHandler

session_start_times = {}

async def on_session_start(input_data, invocation):
    session_start_times[invocation["session_id"]] = input_data["timestamp"]
    return None

async def on_session_end(input_data, invocation):
    start_time = session_start_times.get(invocation["session_id"])
    duration = input_data["timestamp"] - start_time if start_time else 0
    
    await record_metrics({
        "session_id": invocation["session_id"],
        "duration": duration,
        "end_reason": input_data["reason"],
    })
    
    session_start_times.pop(invocation["session_id"], None)
    return None

session = await client.create_session(on_permission_request=PermissionHandler.approve_all, hooks={
        "on_session_start": on_session_start,
        "on_session_end": on_session_end,
    })
```

</div>

</div>

#### 자원을 정리하세요

```typescript
const sessionResources = new Map<string, { tempFiles: string[] }>();

const session = await client.createSession({
  hooks: {
    onSessionStart: async (input, invocation) => {
      sessionResources.set(invocation.sessionId, { tempFiles: [] });
      return null;
    },
    onSessionEnd: async (input, invocation) => {
      const resources = sessionResources.get(invocation.sessionId);
      
      if (resources) {
        // Clean up temp files
        for (const file of resources.tempFiles) {
          await fs.unlink(file).catch(() => {});
        }
        sessionResources.delete(invocation.sessionId);
      }
      
      console.log(`Session ${invocation.sessionId} ended: ${input.reason}`);
      return null;
    },
  },
});
```

#### 다시 시작에 대한 세션 상태 저장

```typescript
const session = await client.createSession({
  hooks: {
    onSessionEnd: async (input, invocation) => {
      if (input.reason !== "error") {
        // Save state for potential resume
        await saveSessionState(invocation.sessionId, {
          endTime: input.timestamp,
          cwd: input.cwd,
          reason: input.reason,
        });
      }
      return null;
    },
  },
});
```

#### 로그 세션 요약

```typescript
const sessionData: Record<string, { prompts: number; tools: number; startTime: number }> = {};

const session = await client.createSession({
  hooks: {
    onSessionStart: async (input, invocation) => {
      sessionData[invocation.sessionId] = { 
        prompts: 0, 
        tools: 0, 
        startTime: input.timestamp 
      };
      return null;
    },
    onUserPromptSubmitted: async (_, invocation) => {
      sessionData[invocation.sessionId].prompts++;
      return null;
    },
    onPreToolUse: async (_, invocation) => {
      sessionData[invocation.sessionId].tools++;
      return { permissionDecision: "allow" };
    },
    onSessionEnd: async (input, invocation) => {
      const data = sessionData[invocation.sessionId];
      console.log(`
Session Summary:
  ID: ${invocation.sessionId}
  Duration: ${(input.timestamp - data.startTime) / 1000}s
  Prompts: ${data.prompts}
  Tool calls: ${data.tools}
  End reason: ${input.reason}
      `.trim());
      
      delete sessionData[invocation.sessionId];
      return null;
    },
  },
});
```

## 에이전트 중지 후크 {#agent-stop}

에이전트 중지 후크는 최상위 에이전트가 자연스럽게 턴의 끝에 도달하면 실행됩니다. 이는 `onSessionEnd`와는 별개입니다: 세션은 계속 활성 상태로 유지되며, 후크는 다른 에이전트 턴을 요청할 수 있습니다.

| 언어                  | 핸들러              |
| ------------------- | ---------------- |
| Node.js/ TypeScript | `onAgentStop`    |
| Python              | `on_agent_stop`  |
| Go                  | `OnAgentStop`    |
| .NET                | `OnAgentStop`    |
| 러스트                 | `on_agent_stop`  |
| Java                | `setOnAgentStop` |

### 입력

공용 멤버 이름은 각 언어의 대/소문자 규칙을 따릅니다.

| Meaning                        | Node.js/Python   | Go/.NET          | 러스트                | Java                  |
| ------------------------------ | ---------------- | ---------------- | ------------------ | --------------------- |
| 에이전트가 중지된 이유(예: `end_turn`     | `stopReason`     | `StopReason`     | `stop_reason`      | `getStopReason()`     |
| 디스크 내 세션 기록 경로                 | `transcriptPath` | `TranscriptPath` | `transcript_path`  | `getTranscriptPath()` |
| 이전 블록 결정이 이미 이 계속 진행을 강제했는지 여부 | `stopHookActive` | `StopHookActive` | `stop_hook_active` | `getStopHookActive()` |

### 출력

에이전트를 중지할 출력을 반환하지 않습니다. 다른 사용자 메시지를 큐에 넣기 위해 블록 결정을 반환하고 계속합니다.

```json
{
  "decision": "block",
  "reason": "Run the final validation and fix any failures."
}
```

이 후크로 인해 이미 계속된 에이전트를 반복적으로 차단하지 않도록 위에 나열된 활성 중지 멤버를 사용합니다. 또한 런타임은 연속 블록 결정의 한도를 지정합니다.

## 모범 사례

1. **빠르게 유지 `onSessionStart`** - 사용자가 세션이 준비되기를 기다리고 있습니다.

2. **모든 끝 이유 처리** - 세션이 완전히 종료된다고 가정하지 마세요. 오류 및 중단을 처리합니다.

3. **리소스 정리** - 세션 중에 할당된 모든 리소스를 해제하는 데 사용합니다 `onSessionEnd` .

4. **최소 상태 저장** - 세션 데이터를 추적하는 경우 경량으로 유지합니다.

5. **정리 작업이 멱등성을 갖도록 하세요** - `onSessionEnd` 프로세스가 충돌하면 호출되지 않을 수 있습니다.

## 참고하십시오

* [후크 사용](/ko/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/hooks)
* [오류 처리 후크](/ko/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/hooks/error-handling)
* [디버깅 가이드](/ko/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/troubleshooting/debugging)