# Implementing Prompt Enrichment in Copilot SDK: onSessionStart and onUserPromptSubmitted Hooks

> Learn to implement prompt enrichment in Copilot SDK using onSessionStart and onUserPromptSubmitted hooks. Enhance prompts at session start or intercept user messages for better AI responses.

- Repository: [GitHub/copilot-sdk](https://github.com/github/copilot-sdk)
- Tags: how-to-guide
- Published: 2026-08-02

---

**The Copilot SDK provides a dual-hook architecture that lets developers enrich prompts at session initialization via `onSessionStart` and intercept every user message via `onUserPromptSubmitted`, returning optional `initialPrompt` or `modifiedPrompt` strings that the SDK injects before model dispatch.**

The GitHub Copilot SDK exposes a lifecycle hook system that enables precise control over prompt flow without modifying core client code. By implementing `onSessionStart` and `onUserPromptSubmitted` handlers, you can automatically prepend system instructions, inject repository context, or sanitize user input before it reaches the language model. This guide examines the TypeScript implementation in `github/copilot-sdk` to show exactly how these hooks modify payload flow.

## Hook Dispatcher Architecture

Both hooks route through a single internal dispatcher in **`Session._handleHooksInvoke`** ([source lines 1881‑1883](https://github.com/github/copilot-sdk/blob/main/nodejs/src/session.ts#L1881-L1883)). The dispatcher first normalizes the incoming wire payload via `deserializeHookInput` (lines 1869‑1870), converting raw timestamps into native `Date` objects before invoking your handler.

```typescript
// Inside Session._handleHooksInvoke
const normalized = deserializeHookInput(input);
const handler = handlerMap[hookType];
if (handler) {
    const result = await handler(normalized, { sessionId: this.sessionId });
    return result;
}

```

The `handlerMap` lookup determines whether the SDK invokes the `sessionStart` or `userPromptSubmitted` logic based on the current lifecycle phase.

## Enriching Prompts at Session Creation with onSessionStart

The **`onSessionStart`** hook fires immediately after a new session is created—before any user messages are processed. It receives a **`SessionStartHookInput`** payload containing metadata like `timestamp`, `workingDirectory`, and `source` (indicating whether the session is `"new"` or `"resume"`).

To inject an initial prompt, return an object with the optional **`initialPrompt`** property. The SDK automatically dispatches this string as the first user message, eliminating the need to call `session.send` immediately after `createSession`.

```typescript
const client = new CopilotClient({ /* …credentials… */ });
await client.createSession({
  hooks: {
    onSessionStart: async (input) => ({
      initialPrompt: `You are an expert React developer. Respond concisely.\nWorking directory: ${input.workingDirectory}`
    })
  }
});

```

This pattern is ideal for **system instruction injection** or establishing persistent context that applies to every interaction in the session.

## Modifying User Input in Real-Time with onUserPromptSubmitted

Every call to `session.send` triggers the **`onUserPromptSubmitted`** hook via the same dispatcher. The handler receives a **`UserPromptSubmittedHookInput`** containing the raw `prompt`, `timestamp`, and `workingDirectory`. Return an object with **`modifiedPrompt`** to replace the user’s text before it reaches the model.

```typescript
const session = await client.createSession({
  hooks: {
    onUserPromptSubmitted: async (input) => ({
      modifiedPrompt: `${input.prompt.trim()}\n\nRespond ONLY with valid JSON.`
    })
  }
});

await session.send({ prompt: "List the top three files in the repo." });

```

Use this hook for **dynamic enrichment** such as appending recent git diffs, enforcing output formats, or masking sensitive literals.

## Error Handling and Resilience

The SDK implements defensive fault isolation for hook failures. If your handler throws an exception, the dispatcher catches it at lines 1894‑1898, logs the error, and returns `undefined`, allowing the session to continue unaffected. This ensures that enrichment logic never crashes the underlying Copilot conversation.

## Complete Implementation Example

The following example combines both hooks to establish a static system persona at startup and append dynamic project context to every subsequent user message:

```typescript
import { CopilotClient } from "@github/copilot-sdk";

async function main() {
  const client = new CopilotClient({
    // …credentials and config…
  });

  const session = await client.createSession({
    hooks: {
      // 1️⃣ Session-start: inject static system instructions
      onSessionStart: async (input) => ({
        initialPrompt:
          "You are a helpful assistant. Answer in plain English.\n" +
          `Working directory: ${input.workingDirectory}`
      }),

      // 2️⃣ User-prompt-submitted: prepend dynamic context
      onUserPromptSubmitted: async (input) => {
        const projectSummary = await fetchProjectSummary();
        return {
          modifiedPrompt: `${projectSummary}\n---\n${input.prompt}`
        };
      },
    },
  });

  // Hooks execute automatically on send()
  const reply = await session.send({ prompt: "Explain the core data flow." });
  console.log(reply);
}

async function fetchProjectSummary(): Promise<string> {
  return "Project: Copilot SDK – multi-language AI extension library.";
}

main().catch(console.error);

```

## Key Source Files and References

| File | Purpose | Link |
|------|---------|------|
| [`nodejs/src/session.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/session.ts) | Core `Session` class; contains `_handleHooksInvoke` dispatcher and hook routing logic. | [session.ts](https://github.com/github/copilot-sdk/blob/main/nodejs/src/session.ts) |
| [`nodejs/src/types.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/types.ts) | Defines `SessionStartHookInput`, `UserPromptSubmittedHookInput`, and return types. | [types.ts](https://github.com/github/copilot-sdk/blob/main/nodejs/src/types.ts) |
| [`docs/hooks/session-lifecycle.md`](https://github.com/github/copilot-sdk/blob/main/docs/hooks/session-lifecycle.md) | Documentation for `sessionStart` hook fields and initialization patterns. | [session-lifecycle.md](https://github.com/github/copilot-sdk/blob/main/docs/hooks/session-lifecycle.md) |
| [`docs/hooks/user-prompt-submitted.md`](https://github.com/github/copilot-sdk/blob/main/docs/hooks/user-prompt-submitted.md) | Documentation for `userPromptSubmitted` payload structure and modification rules. | [user-prompt-submitted.md](https://github.com/github/copilot-sdk/blob/main/docs/hooks/user-prompt-submitted.md) |
| [`nodejs/test/e2e/hooks_extended.e2e.test.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/test/e2e/hooks_extended.e2e.test.ts) | End-to-end tests verifying hook execution and prompt mutation. | [hooks_extended.e2e.test.ts](https://github.com/github/copilot-sdk/blob/main/nodejs/test/e2e/hooks_extended.e2e.test.ts) |

## Summary

- **`onSessionStart`** fires once during `createSession`, accepting an `initialPrompt` that becomes the first message in the conversation.
- **`onUserPromptSubmitted`** fires on every `session.send`, accepting a `modifiedPrompt` that replaces the raw user input before model dispatch.
- Both hooks normalize input via `deserializeHookInput` and route through `Session._handleHooksInvoke` at lines 1881‑1883.
- Error handling at lines 1894‑1898 ensures session continuity even if enrichment logic fails.
- Implementation requires importing from `@github/copilot-sdk` and providing an async handler in the `hooks` configuration object.

## Frequently Asked Questions

### What happens if a hook handler throws an error?

If either `onSessionStart` or `onUserPromptSubmitted` throws, the SDK catches the exception at lines 1894‑1898 in [`session.ts`](https://github.com/github/copilot-sdk/blob/main/session.ts), logs the failure, and treats the hook as returning `undefined`. The session continues with the original prompt flow unchanged, ensuring that enrichment bugs cannot crash the Copilot client.

### Can I use both hooks simultaneously in the same session?

Yes. You can register both `onSessionStart` and `onUserPromptSubmitted` in the same `hooks` object passed to `createSession`. They operate independently: `onSessionStart` runs once at initialization, while `onUserPromptSubmitted` runs before every subsequent `session.send` call.

### Does `modifiedPrompt` completely replace the original user message?

Yes. When you return a `modifiedPrompt` string from `onUserPromptSubmitted`, the SDK substitutes it entirely for the original `prompt` value before sending the request to the model. To preserve the original text, explicitly include `input.prompt` in your return value, as shown in the concatenation examples above.

### Are these hooks available in all Copilot SDK language bindings?

The hook signatures and payload structures are identical across all official SDK language bindings. While this guide focuses on the TypeScript implementation in `github/copilot-sdk`, the same `onSessionStart` and `onUserPromptSubmitted` patterns exist in Python and other supported runtimes, routing through the equivalent dispatcher logic in each language’s session manager.