How to Manage Copilot SDK Session Lifecycle Events: Start, End, and Agent Stop
The Copilot SDK emits type-safe session lifecycle events—session.start, session.resume, session.idle, session.shutdown, and subagent.* events—through an EventEmitter interface on client.session, with all events persisted to events.jsonl for replay.
The Copilot SDK models a session as the lifetime of a single interaction context with the runtime (CLI or VS Code extension). Understanding how to hook into Copilot SDK session lifecycle events is essential for building robust integrations that respond to state changes, track sub-agent execution, and recover from interruptions. This guide covers the five core event types, their payloads, and practical implementation patterns using the auto-generated types in nodejs/src/generated/session-events.ts.
Core Session Lifecycle Events
The SDK defines session lifecycle events through a discriminated union type in [nodejs/src/generated/session-events.ts](https://github.com/github/copilot-sdk/blob/main/nodejs/src/generated/session-events.ts). Each event carries a type discriminator string that you use when registering listeners.
| Event | Emitted When | Key Payload Fields |
|---|---|---|
session.start |
New session created (first client connection) | sessionId, copilotVersion, producer, startTime, optional context |
session.resume |
Client reconnects to existing on-disk session | sessionId, eventCount, resumeTime, continuePendingWork |
session.idle |
Runtime finishes all queued work and becomes quiescent | aborted? — true if previous turn was cancelled |
session.shutdown |
Session terminated (graceful or error) | shutdownType ("routine" | "error"), optional error details |
Handling Session Start and Resume
A session begins with session.start and may later emit session.resume if the client reconnects—for example, when VS Code reopens or a new terminal tab attaches to an existing session.
import { CopilotClient } from '@github/copilot-sdk';
const client = new CopilotClient();
client.session.on('session.start', (ev) => {
console.log('🟢 Session started:', ev.data.sessionId);
console.log('Copilot version:', ev.data.copilotVersion);
console.log('Working directory:', ev.data.context?.workingDirectory);
});
client.session.on('session.resume', (ev) => {
console.log('🔁 Resumed session:', ev.data.sessionId);
console.log('Previous events:', ev.data.eventCount);
console.log('Continuing pending work:', ev.data.continuePendingWork);
});
The continuePendingWork boolean is critical: when true, in-flight tool calls from the previous connection are preserved rather than cancelled.
Detecting Idle and Shutdown States
Use session.idle to trigger cleanup or UI updates when no work remains. The session.shutdown event signals final termination—distinguish between routine exits and errors via shutdownType.
client.session.on('session.idle', (ev) => {
if (ev.data.aborted) {
console.log('⚠️ Previous turn was aborted');
}
console.log('💤 Session idle — ready for new input');
});
client.session.on('session.shutdown', (ev) => {
const { shutdownType, error } = ev.data;
if (shutdownType === 'error') {
console.error('💥 Fatal error shutdown:', error?.message);
} else {
console.log('🔴 Routine shutdown');
}
});
Sub-Agent Lifecycle Events
The SDK treats sub-agents (skills or remote-steerable agents) as nested workloads with their own start, completion, and failure events. These are emitted through the same client.session emitter.
| Event | Purpose | Payload |
|---|---|---|
subagent.started |
Agent instance launched | agentId, optional metadata |
subagent.completed |
Agent finished successfully | agentId |
subagent.failed |
Agent crashed or aborted | agentId, error with message and optional stack |
Tracking Agent Start and Stop
Register listeners to monitor agent execution, implement timeouts, or update progress indicators.
client.session.on('subagent.started', (ev) => {
console.log(`🚀 Sub-agent "${ev.data.agentId}" started`);
// Start progress tracking, set timeouts, etc.
});
client.session.on('subagent.completed', (ev) => {
console.log(`✅ Sub-agent "${ev.data.agentId}" completed`);
// Cleanup progress state
});
client.session.on('subagent.failed', (ev) => {
console.error(`❌ Sub-agent "${ev.data.agentId}" failed:`);
console.error(ev.data.error.message);
if (ev.data.error.stack) {
console.error(ev.data.error.stack);
}
// Trigger retry logic or alert user
});
Sub-agents are created via the session.mcp.apps.launchAgent RPC. Their lifecycle events flow through the same emitter, enabling unified observation of nested workloads.
Event Persistence and Replay
All non-ephemeral events are appended to events.jsonl in the session directory. This log enables state reconstruction on resume and supports debugging or timeline UIs.
Replaying Events from Disk
import { readFile } from 'fs/promises';
async function replaySessionEvents(sessionDir: string): Promise<void> {
const filePath = `${sessionDir}/events.jsonl`;
const raw = await readFile(filePath, 'utf-8');
for (const line of raw.split('\n').filter(Boolean)) {
const event = JSON.parse(line);
switch (event.type) {
case 'session.start':
console.log('▶️ Start:', event.data.sessionId);
break;
case 'session.idle':
console.log('⏸️ Idle');
break;
case 'subagent.started':
console.log('🚀 Agent:', event.data.agentId);
break;
case 'subagent.completed':
console.log('✅ Agent done:', event.data.agentId);
break;
case 'session.shutdown':
console.log('⏹️ Shutdown:', event.data.shutdownType);
break;
}
}
}
The event log is read on resume to reconstruct session state, including eventCount and pending work status.
Key Implementation Files
These source files define and implement Copilot SDK session lifecycle events:
nodejs/src/generated/session-events.ts— Auto-generated TypeScript interfaces for all event types (StartEvent,ResumeEvent,ShutdownEvent,SubagentStartedEvent, etc.)nodejs/src/session.ts— Core session management: creates the EventEmitter, writes toevents.jsonl, implementsstart,resume, andshutdownlogicnodejs/src/client.ts— High-level SDK entry point exposingclient.session(the EventEmitter consumed by applications)docs/hooks/session-lifecycle.md— Official documentation of event semantics and usage patterns
Summary
- Five core events track session state:
session.start,session.resume,session.idle,session.shutdown, plussubagent.*events for nested workloads - Register listeners on
client.sessionusing thetypediscriminator strings for type-safe event handling - Persisted log at
events.jsonlenables session recovery and event replay - Sub-agent events provide granular visibility into skill and remote agent execution
- Auto-generated types in
session-events.tsensure compile-time validation and IDE autocomplete
Frequently Asked Questions
How do I distinguish between a new session and a resumed session?
Listen for session.start (new session) versus session.resume (reconnected session). The ResumeEvent payload includes eventCount and continuePendingWork to indicate prior state. In nodejs/src/session.ts, the session manager reads events.jsonl on resume to populate these fields.
What happens to in-flight work when a client disconnects?
The continuePendingWork field in ResumeEvent indicates whether pending tool calls are preserved. When true, the session resumes where it left off; when false, work was cancelled and session.idle will have aborted: true.
How can I handle sub-agent failures gracefully?
Register a listener for subagent.failed. The event payload includes agentId and an error object with message and optional stack. Implement retry logic, user notifications, or fallback behaviors based on agentId to route errors appropriately.
Where are session events actually persisted?
Events are appended to events.jsonl in the session directory, as implemented in nodejs/src/session.ts. This JSON Lines file is read on resume and can be parsed for debugging, audit trails, or UI timelines.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →