{"id":94665,"date":"2026-03-24T09:00:00","date_gmt":"2026-03-24T16:00:00","guid":{"rendered":"https:\/\/github.blog\/?p=94665"},"modified":"2026-04-22T13:46:26","modified_gmt":"2026-04-22T20:46:26","slug":"building-ai-powered-github-issue-triage-with-the-copilot-sdk","status":"publish","type":"post","link":"https:\/\/github.blog\/ai-and-ml\/github-copilot\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\/","title":{"rendered":"Building AI-powered GitHub issue triage with the Copilot SDK"},"content":{"rendered":"<!DOCTYPE html PUBLIC \"-\/\/W3C\/\/DTD HTML 4.0 Transitional\/\/EN\" \"http:\/\/www.w3.org\/TR\/REC-html40\/loose.dtd\">\n<html><body><p class=\"wp-block-paragraph\">The Copilot SDK lets you add the same AI that powers <a href=\"https:\/\/copilot.microsoft.com\/\">Copilot Chat<\/a> to your own applications. I wanted to see what that looks like in practice, so I built an issue triage app called IssueCrush. Here&rsquo;s what I learned and how you can get started.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you&rsquo;ve ever maintained an open source project, or worked on a team with active repositories, you know the feeling. You open GitHub and see that notification badge: 47 issues. Some are bugs, some are feature requests, some are questions that should be discussions, and some are duplicates of issues from three years ago.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The mental overhead of triaging issues is real. Each one requires context-switching: read the title, scan the description, check the labels, think about priority, decide what to do. Multiply that by dozens of issues across multiple repositories, and suddenly your brain is mush.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I wanted to make this faster. And with the GitHub Copilot SDK, I found a way.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-enter-issuecrush-swipe-right-to-ship\">Enter IssueCrush: Swipe right to ship<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">IssueCrush shows your GitHub issues as swipeable cards. Left to close, right to keep. When you tap &ldquo;Get AI Summary,&rdquo; Copilot reads the issue and tells you what it&rsquo;s about and what to do with it. Instead of reading through every lengthy description, maintainers can get instant, actionable context to make faster triage decisions. Here&rsquo;s how I integrated the GitHub Copilot SDK to make it happen.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"1305\" height=\"835\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2026\/03\/Screenshot-2026-03-19-at-11.37.43-AM.png?resize=1305%2C835\" alt=\"Screenshot of a developer workspace showing a VS Code editor open to the IssueCrush project, with a package.json file visible and a terminal running a server that logs AI-generated issue summaries using the GitHub Copilot SDK. On the right, an iPhone simulator displays the IssueCrush mobile app, listing a GitHub issue with labels, a progress indicator, and a &lsquo;Get AI Summary&rsquo; button, along with swipe-style approve and reject buttons at the bottom.\" class=\"wp-image-94667\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2026\/03\/Screenshot-2026-03-19-at-11.37.43-AM.png?w=1305 1305w, https:\/\/github.blog\/wp-content\/uploads\/2026\/03\/Screenshot-2026-03-19-at-11.37.43-AM.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2026\/03\/Screenshot-2026-03-19-at-11.37.43-AM.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2026\/03\/Screenshot-2026-03-19-at-11.37.43-AM.png?w=1024 1024w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-the-architecture-challenge\">The architecture challenge<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The first technical decision was figuring out where to run the Copilot SDK. React Native apps can&rsquo;t directly use Node.js packages, and the Copilot SDK requires a Node.js runtime. Internally, the SDK manages a local Copilot CLI process and communicates with it over JSON-RPC. Because of this dependency on the CLI binary and a Node environment, the integration must run server-side rather than directly in a React Native app. This means the server must have the Copilot CLI installed and available on the system PATH.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I settled on a server-side integration pattern:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"1318\" height=\"921\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2026\/03\/Screenshot-2026-03-19-at-11.39.25-AM.png?resize=1318%2C921\" alt=\"Architecture diagram showing a React Native and web client communicating over HTTPS with a Node.js server. The server runs the GitHub Copilot SDK, which manages a local Copilot CLI process via JSON-RPC. The CLI connects to the GitHub Copilot service, while the client separately interacts with GitHub OAuth and the GitHub REST API for issue data.\" class=\"wp-image-94668\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2026\/03\/Screenshot-2026-03-19-at-11.39.25-AM.png?w=1318 1318w, https:\/\/github.blog\/wp-content\/uploads\/2026\/03\/Screenshot-2026-03-19-at-11.39.25-AM.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2026\/03\/Screenshot-2026-03-19-at-11.39.25-AM.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2026\/03\/Screenshot-2026-03-19-at-11.39.25-AM.png?w=1024 1024w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Here&rsquo;s why this setup works:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Single SDK instance shared across all clients<\/strong>, so you&rsquo;re not spinning up a new connection per mobile client. The server manages one instance for every request. Less overhead, fewer auth handshakes, simpler cleanup.<\/li>\n\n\n\n<li><strong>Server-side secrets for Copilot authentication<\/strong>, to keep credentials secure. Your API tokens never touch the client. They live on the server where they belongnot inside a React Native bundle someone can decompile.<\/li>\n\n\n\n<li><strong>Graceful degradation when AI is unavailable<\/strong>, so you can still triage issues even if the Copilot service goes down or times out. The app falls back to a basic summary. AI makes triage faster, but it shouldn&rsquo;t be a single point of failure.<\/li>\n\n\n\n<li><strong>Logging of requests for debugging and monitoring<\/strong>, because every prompt and response passes through your server. You can track latency, catch failures, and debug prompt issues without bolting instrumentation onto the mobile client.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Before you build something like this, you need:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>The <a href=\"https:\/\/docs.github.com\/en\/copilot\/how-tos\/set-up\/install-copilot-cli\">Copilot CLI<\/a> installed on your server.<\/li>\n\n\n\n<li>A GitHub Copilot subscription, or a <a href=\"https:\/\/github.com\/github\/copilot-sdk\/blob\/main\/docs\/auth\/byok.md\">BYOK configuration<\/a> with your own API keys.<\/li>\n\n\n\n<li>The Copilot CLI authenticated. Run copilot auth on your server, or set a <code>COPILOT_GITHUB_TOKEN<\/code> environment variable.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-implement-the-copilot-sdk-integration\">How to implement the Copilot SDK integration<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The Copilot SDK uses a session-based model. You start a client (which spawns the CLI process), create a session, send messages, then clean up.<\/p>\n\n\n<div class=\"wp-block-code-wrapper\">\n<pre class=\"wp-block-code language-typescript\"><code>const { CopilotClient, approveAll } = await import('@github\/copilot-sdk');\n \nlet client = null; \nlet session = null; \n \ntry { \n   \/\/ 1. Initialize the client (spawns Copilot CLI in server mode) \n   client = new CopilotClient(); \n   await client.start(); \n \n  \/\/ 2. Create a session with your preferred model \n  session = await client.createSession({\n  model: 'gpt-4.1',\n  onPermissionRequest: approveAll,\n});\n \n  \/\/ 3. Send your prompt and wait for response \n   const response = await session.sendAndWait({ prompt }); \n \n  \/\/ 4. Extract the content \n   if (response &amp;&amp; response.data &amp;&amp; response.data.content) { \n     const summary = response.data.content; \n     \/\/ Use the summary... \n   } \n \n} finally { \n   \/\/ 5. Always clean up \n   if (session) await session.disconnect().catch(() =&gt; {}); \n   if (client) await client.stop().catch(() =&gt; {}); \n} <\/code><\/pre>\n<clipboard-copy aria-label=\"Copy\" class=\"code-copy-btn\" data-copy-feedback=\"Copied!\" value=\"const { CopilotClient, approveAll } = await import('@github\/copilot-sdk');\n \nlet client = null; \nlet session = null; \n \ntry { \n   \/\/ 1. Initialize the client (spawns Copilot CLI in server mode) \n   client = new CopilotClient(); \n   await client.start(); \n \n  \/\/ 2. Create a session with your preferred model \n  session = await client.createSession({\n  model: 'gpt-4.1',\n  onPermissionRequest: approveAll,\n});\n \n  \/\/ 3. Send your prompt and wait for response \n   const response = await session.sendAndWait({ prompt }); \n \n  \/\/ 4. Extract the content \n   if (response &amp;&amp; response.data &amp;&amp; response.data.content) { \n     const summary = response.data.content; \n     \/\/ Use the summary... \n   } \n \n} finally { \n   \/\/ 5. Always clean up \n   if (session) await session.disconnect().catch(() =&gt; {}); \n   if (client) await client.stop().catch(() =&gt; {}); \n}\" tabindex=\"0\" role=\"button\"><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-copy js-clipboard-copy-icon\"><path d=\"M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z\"><\/path><path d=\"M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z\"><\/path><\/svg><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-check js-clipboard-check-icon\"><path d=\"M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z\"><\/path><\/svg><\/clipboard-copy><\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-key-sdk-patterns\">Key SDK patterns<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-1-lifecycle-management\">1. Lifecycle management<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The SDK follows a strict lifecycle: <code>start() &rarr; createSession() &rarr; sendAndWait() &rarr; disconnect() &rarr; stop()<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here&rsquo;s something I learned the hard way: failing to clean up sessions leaks resources. I spent two hours debugging memory issues before realizing I&rsquo;d forgotten a <code>disconnect()<\/code> call. Wrap every session interaction in try\/finally. The <code>.catch(() =&gt; {})<\/code> on cleanup calls prevents cleanup errors from masking the original error.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-2-prompt-engineering-for-triage\">2. Prompt engineering for triage<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Prompt structure gives the model enough context to do its job. I provide structured information about the issue rather than dumping raw text:<\/p>\n\n\n<div class=\"wp-block-code-wrapper\">\n<pre class=\"wp-block-code language-typescript\"><code>const prompt = `You are analyzing a GitHub issue to help a developer quickly understand it and decide how to handle it. \n \nIssue Details: \n- Title: ${issue.title} \n- Number: #${issue.number} \n- Repository: ${issue.repository?.full_name || 'Unknown'} \n- State: ${issue.state} \n- Labels: ${issue.labels?.length ? issue.labels.map(l =&gt; l.name).join(', ') : 'None'} \n- Created: ${issue.created_at} \n- Author: ${issue.user?.login || 'Unknown'} \n \nIssue Body: \n${issue.body || 'No description provided.'} \n \nProvide a concise 2-3 sentence summary that: \n1. Explains what the issue is about \n2. Identifies the key problem or request \n3. Suggests a recommended action (e.g., \"needs investigation\", \"ready to implement\", \"assign to backend team\", \"close as duplicate\") \n \nKeep it clear, actionable, and helpful for quick triage. No markdown formatting.`; <\/code><\/pre>\n<clipboard-copy aria-label=\"Copy\" class=\"code-copy-btn\" data-copy-feedback=\"Copied!\" value=\"const prompt = `You are analyzing a GitHub issue to help a developer quickly understand it and decide how to handle it. \n \nIssue Details: \n- Title: ${issue.title} \n- Number: #${issue.number} \n- Repository: ${issue.repository?.full_name || 'Unknown'} \n- State: ${issue.state} \n- Labels: ${issue.labels?.length ? issue.labels.map(l =&gt; l.name).join(', ') : 'None'} \n- Created: ${issue.created_at} \n- Author: ${issue.user?.login || 'Unknown'} \n \nIssue Body: \n${issue.body || 'No description provided.'} \n \nProvide a concise 2-3 sentence summary that: \n1. Explains what the issue is about \n2. Identifies the key problem or request \n3. Suggests a recommended action (e.g., &quot;needs investigation&quot;, &quot;ready to implement&quot;, &quot;assign to backend team&quot;, &quot;close as duplicate&quot;) \n \nKeep it clear, actionable, and helpful for quick triage. No markdown formatting.`;\" tabindex=\"0\" role=\"button\"><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-copy js-clipboard-copy-icon\"><path d=\"M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z\"><\/path><path d=\"M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z\"><\/path><\/svg><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-check js-clipboard-check-icon\"><path d=\"M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z\"><\/path><\/svg><\/clipboard-copy><\/div>\n\n\n<p class=\"wp-block-paragraph\">The labels and author context matter more than you&rsquo;d think. An issue from a first-time contributor needs different handling than one from a core maintainer, and the AI uses this information to adjust its summary.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-3-response-handling\">3. Response handling<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>sendAndWait()<\/code> method returns the assistant&rsquo;s response once the session goes idle. Always validate that the response chain exists before accessing nested properties:<\/p>\n\n\n<div class=\"wp-block-code-wrapper\">\n<pre class=\"wp-block-code language-typescript\"><code>const response = await session.sendAndWait({ prompt }, 30000); \/\/ 30 second timeout \n \nlet summary; \nif (response &amp;&amp; response.data &amp;&amp; response.data.content) { \n   summary = response.data.content; \n} else { \n   throw new Error('No content received from Copilot'); \n} <\/code><\/pre>\n<clipboard-copy aria-label=\"Copy\" class=\"code-copy-btn\" data-copy-feedback=\"Copied!\" value=\"const response = await session.sendAndWait({ prompt }, 30000); \/\/ 30 second timeout \n \nlet summary; \nif (response &amp;&amp; response.data &amp;&amp; response.data.content) { \n   summary = response.data.content; \n} else { \n   throw new Error('No content received from Copilot'); \n}\" tabindex=\"0\" role=\"button\"><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-copy js-clipboard-copy-icon\"><path d=\"M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z\"><\/path><path d=\"M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z\"><\/path><\/svg><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-check js-clipboard-check-icon\"><path d=\"M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z\"><\/path><\/svg><\/clipboard-copy><\/div>\n\n\n<p class=\"wp-block-paragraph\">The second argument to <code>sendAndWait()<\/code> is a timeout in milliseconds. Set it high enough for complex issues but low enough that users aren&rsquo;t staring at a spinner. I&rsquo;ve seen enough &ldquo;undefined is not an object&rdquo; errors to know you should never skip the null checks on the response chain.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-client-side-service-layer\">Client-side service layer<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">On the React Native side, I wrap the API calls in a service class that handles initialization and error states:<\/p>\n\n\n<div class=\"wp-block-code-wrapper\">\n<pre class=\"wp-block-code language-typescript\"><code>\/\/ src\/lib\/copilotService.ts \nimport type { GitHubIssue } from '..\/api\/github'; \nimport { getToken } from '.\/tokenStorage'; \nexport interface SummaryResult { \n   summary: string; \n   fallback?: boolean; \n   requiresCopilot?: boolean; \n} \n \nexport class CopilotService { \n   private backendUrl = process.env.EXPO_PUBLIC_API_URL || 'http:\/\/localhost:3000'; \n \n  async initialize(): Promise&lt;{ copilotMode: string }&gt; { \n     try { \n       const response = await fetch(`${this.backendUrl}\/health`); \n       const data = await response.json(); \n \n      console.log('Backend health check:', data); \n       return { copilotMode: data.copilotMode || 'unknown' }; \n     } catch (error) { \n       console.error('Failed to connect to backend:', error); \n       throw new Error('Backend server not available'); \n     } \n   } \n \n  async summarizeIssue(issue: GitHubIssue): Promise&lt;SummaryResult&gt; { \n     try { \n       const token = await getToken(); \n \n      if (!token) { \n         throw new Error('No GitHub token available'); \n       } \n \n      const response = await fetch(`${this.backendUrl}\/api\/ai-summary`, { \n         method: 'POST', \n         headers: { \n           'Content-Type': 'application\/json', \n         }, \n         body: JSON.stringify({ issue, token }), \n       }); \n \n      const data = await response.json(); \n \n      if (!response.ok) { \n         if (response.status === 403 &amp;&amp; data.requiresCopilot) { \n           return { \n             summary: data.message || 'AI summaries require a GitHub Copilot subscription.', \n             requiresCopilot: true, \n           }; \n         } \n         throw new Error(data.error || 'Failed to generate summary'); \n       } \n \n      return { \n         summary: data.summary || 'Unable to generate summary', \n         fallback: data.fallback || false, \n       }; \n     } catch (error) { \n       console.error('Copilot summarization error:', error); \n       throw error; \n     } \n   } \n} \n \nexport const copilotService = new CopilotService(); <\/code><\/pre>\n<clipboard-copy aria-label=\"Copy\" class=\"code-copy-btn\" data-copy-feedback=\"Copied!\" value=\"\/\/ src\/lib\/copilotService.ts \nimport type { GitHubIssue } from '..\/api\/github'; \nimport { getToken } from '.\/tokenStorage'; \nexport interface SummaryResult { \n   summary: string; \n   fallback?: boolean; \n   requiresCopilot?: boolean; \n} \n \nexport class CopilotService { \n   private backendUrl = process.env.EXPO_PUBLIC_API_URL || 'http:\/\/localhost:3000'; \n \n  async initialize(): Promise&lt;{ copilotMode: string }&gt; { \n     try { \n       const response = await fetch(`${this.backendUrl}\/health`); \n       const data = await response.json(); \n \n      console.log('Backend health check:', data); \n       return { copilotMode: data.copilotMode || 'unknown' }; \n     } catch (error) { \n       console.error('Failed to connect to backend:', error); \n       throw new Error('Backend server not available'); \n     } \n   } \n \n  async summarizeIssue(issue: GitHubIssue): Promise&lt;SummaryResult&gt; { \n     try { \n       const token = await getToken(); \n \n      if (!token) { \n         throw new Error('No GitHub token available'); \n       } \n \n      const response = await fetch(`${this.backendUrl}\/api\/ai-summary`, { \n         method: 'POST', \n         headers: { \n           'Content-Type': 'application\/json', \n         }, \n         body: JSON.stringify({ issue, token }), \n       }); \n \n      const data = await response.json(); \n \n      if (!response.ok) { \n         if (response.status === 403 &amp;&amp; data.requiresCopilot) { \n           return { \n             summary: data.message || 'AI summaries require a GitHub Copilot subscription.', \n             requiresCopilot: true, \n           }; \n         } \n         throw new Error(data.error || 'Failed to generate summary'); \n       } \n \n      return { \n         summary: data.summary || 'Unable to generate summary', \n         fallback: data.fallback || false, \n       }; \n     } catch (error) { \n       console.error('Copilot summarization error:', error); \n       throw error; \n     } \n   } \n} \n \nexport const copilotService = new CopilotService();\" tabindex=\"0\" role=\"button\"><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-copy js-clipboard-copy-icon\"><path d=\"M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z\"><\/path><path d=\"M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z\"><\/path><\/svg><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-check js-clipboard-check-icon\"><path d=\"M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z\"><\/path><\/svg><\/clipboard-copy><\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-react-native-integration\">React Native integration<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The UI is straightforward React state management. Tap the button, call the service, cache the result:<\/p>\n\n\n<div class=\"wp-block-code-wrapper\">\n<pre class=\"wp-block-code language-typescript\"><code>const [loadingAiSummary, setLoadingAiSummary] = useState(false); \n \nconst handleGetAiSummary = async () =&gt; { \n   const issue = issues[currentIndex]; \n   if (!issue || issue.aiSummary) return; \n \n  setLoadingAiSummary(true); \n   try { \n     const result = await copilotService.summarizeIssue(issue); \n     setIssues(prevIssues =&gt; \n       prevIssues.map((item, index) =&gt; \n         index === currentIndex ? { ...item, aiSummary: result.summary } : item \n       ) \n     ); \n   } catch (error) { \n     console.error('AI Summary error:', error); \n   } finally { \n     setLoadingAiSummary(false); \n   } \n}; <\/code><\/pre>\n<clipboard-copy aria-label=\"Copy\" class=\"code-copy-btn\" data-copy-feedback=\"Copied!\" value=\"const [loadingAiSummary, setLoadingAiSummary] = useState(false); \n \nconst handleGetAiSummary = async () =&gt; { \n   const issue = issues[currentIndex]; \n   if (!issue || issue.aiSummary) return; \n \n  setLoadingAiSummary(true); \n   try { \n     const result = await copilotService.summarizeIssue(issue); \n     setIssues(prevIssues =&gt; \n       prevIssues.map((item, index) =&gt; \n         index === currentIndex ? { ...item, aiSummary: result.summary } : item \n       ) \n     ); \n   } catch (error) { \n     console.error('AI Summary error:', error); \n   } finally { \n     setLoadingAiSummary(false); \n   } \n};\" tabindex=\"0\" role=\"button\"><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-copy js-clipboard-copy-icon\"><path d=\"M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z\"><\/path><path d=\"M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z\"><\/path><\/svg><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-check js-clipboard-check-icon\"><path d=\"M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z\"><\/path><\/svg><\/clipboard-copy><\/div>\n\n\n<p class=\"wp-block-paragraph\">Once a summary exists on the issue object, the card swaps the button for the summary text. If the user swipes away and comes back, the cached version renders instantly. No second API call needed.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-graceful-degradation\">Graceful degradation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">AI services can fail. Network issues, rate limits, and service outages happen. The server handles two failure modes: subscription errors return a 403 so the client can show a clear message, and everything else falls back to a summary built from issue metadata.<\/p>\n\n\n<div class=\"wp-block-code-wrapper\">\n<pre class=\"wp-block-code language-typescript\"><code>} catch (error) { \n   \/\/ Clean up on error \n   try { \n     if (session) await session.disconnect().catch(() =&gt; {}); \n     if (client) await client.stop().catch(() =&gt; {}); \n   } catch (cleanupError) { \n     \/\/ Ignore cleanup errors \n   } \n \n  const errorMessage = error.message.toLowerCase(); \n \n  \/\/ Copilot subscription errors get a clear 403 \n   if (errorMessage.includes('unauthorized') || \n       errorMessage.includes('forbidden') || \n       errorMessage.includes('copilot') || \n       errorMessage.includes('subscription')) { \n     return res.status(403).json({ \n       error: 'Copilot access required', \n       message: 'AI summaries require a GitHub Copilot subscription.', \n       requiresCopilot: true \n     }); \n   } \n \n  \/\/ Everything else falls back to a metadata-based summary \n   const fallbackSummary = generateFallbackSummary(issue); \n   res.json({ summary: fallbackSummary, fallback: true }); \n} <\/code><\/pre>\n<clipboard-copy aria-label=\"Copy\" class=\"code-copy-btn\" data-copy-feedback=\"Copied!\" value=\"} catch (error) { \n   \/\/ Clean up on error \n   try { \n     if (session) await session.disconnect().catch(() =&gt; {}); \n     if (client) await client.stop().catch(() =&gt; {}); \n   } catch (cleanupError) { \n     \/\/ Ignore cleanup errors \n   } \n \n  const errorMessage = error.message.toLowerCase(); \n \n  \/\/ Copilot subscription errors get a clear 403 \n   if (errorMessage.includes('unauthorized') || \n       errorMessage.includes('forbidden') || \n       errorMessage.includes('copilot') || \n       errorMessage.includes('subscription')) { \n     return res.status(403).json({ \n       error: 'Copilot access required', \n       message: 'AI summaries require a GitHub Copilot subscription.', \n       requiresCopilot: true \n     }); \n   } \n \n  \/\/ Everything else falls back to a metadata-based summary \n   const fallbackSummary = generateFallbackSummary(issue); \n   res.json({ summary: fallbackSummary, fallback: true }); \n}\" tabindex=\"0\" role=\"button\"><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-copy js-clipboard-copy-icon\"><path d=\"M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z\"><\/path><path d=\"M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z\"><\/path><\/svg><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-check js-clipboard-check-icon\"><path d=\"M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z\"><\/path><\/svg><\/clipboard-copy><\/div>\n\n\n<p class=\"wp-block-paragraph\">The fallback builds a useful summary from what we already have:<\/p>\n\n\n<div class=\"wp-block-code-wrapper\">\n<pre class=\"wp-block-code language-typescript\"><code>function generateFallbackSummary(issue) { \n   const parts = [issue.title]; \n \n  if (issue.labels?.length) { \n     parts.push(`\\nLabels: ${issue.labels.map(l =&gt; l.name).join(', ')}`); \n   } \n \n  if (issue.body) { \n     const firstSentence = issue.body.split(\/[.!?]\\s\/)[0]; \n     if (firstSentence &amp;&amp; firstSentence.length &lt; 200) { \n       parts.push(`\\n\\n${firstSentence}.`); \n     } \n   } \n \n  parts.push('\\n\\nReview the full issue details to determine next steps.'); \n   return parts.join(''); \n} <\/code><\/pre>\n<clipboard-copy aria-label=\"Copy\" class=\"code-copy-btn\" data-copy-feedback=\"Copied!\" value=\"function generateFallbackSummary(issue) { \n   const parts = [issue.title]; \n \n  if (issue.labels?.length) { \n     parts.push(`\\nLabels: ${issue.labels.map(l =&gt; l.name).join(', ')}`); \n   } \n \n  if (issue.body) { \n     const firstSentence = issue.body.split(\/[.!?]\\s\/)[0]; \n     if (firstSentence &amp;&amp; firstSentence.length &lt; 200) { \n       parts.push(`\\n\\n${firstSentence}.`); \n     } \n   } \n \n  parts.push('\\n\\nReview the full issue details to determine next steps.'); \n   return parts.join(''); \n}\" tabindex=\"0\" role=\"button\"><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-copy js-clipboard-copy-icon\"><path d=\"M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z\"><\/path><path d=\"M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z\"><\/path><\/svg><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-check js-clipboard-check-icon\"><path d=\"M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z\"><\/path><\/svg><\/clipboard-copy><\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-a-few-other-patterns-worth-noting\">A few other patterns worth noting<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The server exposes a <code>\/health<\/code> endpoint that signals AI availability. Clients check it on startup and hide the summary button entirely if the backend can&rsquo;t support it. No broken buttons.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Summaries are generated on -demand, not preemptively. This keeps API costs down and avoids wasted calls when users swipe past an issue without reading it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The SDK is loaded with await <code>import('@github\/copilot-sdk')<\/code> instead of a top-level require. This lets the server start even if the SDK has issues, which makes deployment and debugging smoother.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-dependencies\">Dependencies<\/h2>\n\n\n<div class=\"wp-block-code-wrapper\">\n<pre class=\"wp-block-code language-json\"><code>{ \n   \"dependencies\": { \n     \"@github\/copilot-sdk\": \"^0.1.14\", \n     \"express\": \"^5.2.1\" \n   } \n} <\/code><\/pre>\n<clipboard-copy aria-label=\"Copy\" class=\"code-copy-btn\" data-copy-feedback=\"Copied!\" value='{ \n   \"dependencies\": { \n     \"@github\/copilot-sdk\": \"^0.1.14\", \n     \"express\": \"^5.2.1\" \n   } \n}' tabindex=\"0\" role=\"button\"><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-copy js-clipboard-copy-icon\"><path d=\"M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z\"><\/path><path d=\"M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z\"><\/path><\/svg><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-check js-clipboard-check-icon\"><path d=\"M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z\"><\/path><\/svg><\/clipboard-copy><\/div>\n\n\n<p class=\"wp-block-paragraph\">The SDK communicates with the Copilot CLI process via JSON-RPC. You need the <a href=\"https:\/\/docs.github.com\/en\/copilot\/how-tos\/set-up\/install-copilot-cli\">Copilot CLI<\/a> installed and available in your PATH. Check the <a href=\"https:\/\/github.com\/github\/copilot-sdk\">SDK&rsquo;s package requirements<\/a> for the minimum Node.js version.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-what-i-learned-building-this\">What I learned building this<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Server-side is the right call<\/strong>. The SDK needs the Copilot CLI binary, and you&rsquo;re not installing that on a phone. Running it on a server keeps AI logic in one place, simplifies the mobile client, and means credentials never leave the backend.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Prompt structure matters more than prompt length<\/strong>. Feeding the model organized metadata like title, labels, and author produces much better summaries than dumping the entire issue body as raw text. Give the model something to work with, and it&rsquo;ll give you something useful back.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Always have a fallback<\/strong>. AI services go down. Rate limits happen. Design for graceful degradation from day one. Your users should still be able to triage issues even if the AI piece is offline.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Clean up your sessions<\/strong>. The SDK requires explicit cleanup: <code>disconnect()<\/code> then <code>stop()<\/code>. I skipped a <code>disconnect()<\/code> call once and spent two hours chasing a memory leak. Use <code>try\/finally<\/code> every time.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Cache the results<\/strong>. Once you have a summary, store it on the issue object. If the user swipes away and comes back, the cached version renders instantly. No second API call, no wasted money, no extra latency.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>AI can make maintainership sustainable<\/strong>. Triage is one of those invisible tasks that burns people out. Nobody thanks you for it, and it piles up fast. If you can cut the time it takes to process 50 issues in half, that&rsquo;s time back for code review, mentoring, or just not dreading your notification badge. The Copilot SDK is one tool, but the bigger idea matters more: look at the parts of maintaining that drain you and ask if AI can take a first pass.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-try-it-yourself\">Try it yourself<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>@github\/copilot-sdk<\/code> opens real possibilities for building intelligent developer tools. Combined with React Native&rsquo;s cross-platform reach, you can bring AI-powered workflows to mobile in a way that feels native and fast.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you&rsquo;re building something similar, start with the server-side pattern I&rsquo;ve outlined here. It&rsquo;s the simplest path to a working integration, and it scales with your app. The source code is available on GitHub: <a href=\"https:\/\/github.com\/AndreaGriffiths11\/IssueCrush\">AndreaGriffiths11\/IssueCrush<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/github\/copilot-sdk\">Get started with the Copilot SDK<\/a> to see what else you can build. The <a href=\"https:\/\/github.com\/github\/copilot-sdk#getting-started\">Getting Started guide<\/a> walks you through your first integration in about five lines of code. Have feedback or ideas? Join the conversation in the <a href=\"https:\/\/github.com\/github\/copilot-sdk\/discussions\">SDK discussions<\/a>.<\/p>\n<\/body><\/html>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to integrate the Copilot SDK into a React Native app to generate AI-powered issue summaries, with production patterns for graceful degradation and caching.<\/p>\n","protected":false},"author":1825,"featured_media":93169,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_gh_post_show_toc":"yes","_gh_post_is_no_robots":"","_gh_post_is_featured":"yes","_gh_post_is_excluded":"","_gh_post_is_unlisted":"","_gh_post_related_link_1":"","_gh_post_related_link_2":"","_gh_post_related_link_3":"","_gh_post_sq_img":"https:\/\/github.blog\/wp-content\/uploads\/2026\/01\/generic-github-copilot-logo-1-e1767926300906.png","_gh_post_sq_img_id":"93192","_gh_post_cta_title":"","_gh_post_cta_text":"","_gh_post_cta_link":"","_gh_post_cta_button":"","_gh_post_recirc_hide":"","_gh_post_recirc_col_1":"","_gh_post_recirc_col_2":"","_gh_post_recirc_col_3":"","_gh_post_recirc_col_4":"","_featured_video":"","_gh_post_additional_query_params":"","_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"_wpas_customize_per_network":false,"jetpack_post_was_ever_published":false,"_links_to":"","_links_to_target":""},"categories":[3293,3299,3298,3295],"tags":[3826,3802,3685,3825],"coauthors":[2539],"class_list":["post-94665","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai-and-ml","category-application-development","category-developer-skills","category-github-copilot","tag-ai-integration","tag-github-copilot-sdk","tag-github-issue","tag-react-native"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v28.6 (Yoast SEO v28.6) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Building AI-powered GitHub issue triage with the Copilot SDK - The GitHub Blog<\/title>\n<meta name=\"description\" content=\"Learn how to integrate the Copilot SDK into a React Native app to generate AI-powered issue summaries.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/github.blog\/ai-and-ml\/github-copilot\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building AI-powered GitHub issue triage with the Copilot SDK\" \/>\n<meta property=\"og:description\" content=\"Learn how to integrate the Copilot SDK into a React Native app to generate AI-powered issue summaries.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/github.blog\/ai-and-ml\/github-copilot\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\/\" \/>\n<meta property=\"og:site_name\" content=\"The GitHub Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-03-24T16:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-04-22T20:46:26+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/github.blog\/wp-content\/uploads\/2026\/01\/generic-copilot-logo-github.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1920\" \/>\n\t<meta property=\"og:image:height\" content=\"1080\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Andrea Griffiths\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@acolombiadev\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Andrea Griffiths\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"7 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/github.blog\\\/ai-and-ml\\\/github-copilot\\\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/github.blog\\\/ai-and-ml\\\/github-copilot\\\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\\\/\"},\"author\":{\"name\":\"Andrea Griffiths\",\"@id\":\"https:\\\/\\\/github.blog\\\/#\\\/schema\\\/person\\\/a1a244472a4104a81587cbe29e5a1ac1\"},\"headline\":\"Building AI-powered GitHub issue triage with the Copilot SDK\",\"datePublished\":\"2026-03-24T16:00:00+00:00\",\"dateModified\":\"2026-04-22T20:46:26+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/github.blog\\\/ai-and-ml\\\/github-copilot\\\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\\\/\"},\"wordCount\":1403,\"image\":{\"@id\":\"https:\\\/\\\/github.blog\\\/ai-and-ml\\\/github-copilot\\\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2026\\\/01\\\/generic-copilot-logo-github.png?fit=1920%2C1080\",\"keywords\":[\"AI integration\",\"GitHub Copilot SDK\",\"GitHub Issue\",\"React Native\"],\"articleSection\":[\"AI &amp; ML\",\"Application development\",\"Developer skills\",\"GitHub Copilot\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/github.blog\\\/ai-and-ml\\\/github-copilot\\\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\\\/\",\"url\":\"https:\\\/\\\/github.blog\\\/ai-and-ml\\\/github-copilot\\\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\\\/\",\"name\":\"Building AI-powered GitHub issue triage with the Copilot SDK - The GitHub Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/github.blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/github.blog\\\/ai-and-ml\\\/github-copilot\\\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/github.blog\\\/ai-and-ml\\\/github-copilot\\\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2026\\\/01\\\/generic-copilot-logo-github.png?fit=1920%2C1080\",\"datePublished\":\"2026-03-24T16:00:00+00:00\",\"dateModified\":\"2026-04-22T20:46:26+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/github.blog\\\/#\\\/schema\\\/person\\\/a1a244472a4104a81587cbe29e5a1ac1\"},\"description\":\"Learn how to integrate the Copilot SDK into a React Native app to generate AI-powered issue summaries.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/github.blog\\\/ai-and-ml\\\/github-copilot\\\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/github.blog\\\/ai-and-ml\\\/github-copilot\\\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/github.blog\\\/ai-and-ml\\\/github-copilot\\\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\\\/#primaryimage\",\"url\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2026\\\/01\\\/generic-copilot-logo-github.png?fit=1920%2C1080\",\"contentUrl\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2026\\\/01\\\/generic-copilot-logo-github.png?fit=1920%2C1080\",\"width\":1920,\"height\":1080,\"caption\":\"Copilot hovering beside a cluster of geometric cubes in a decorative scene.\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/github.blog\\\/ai-and-ml\\\/github-copilot\\\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/github.blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"AI &amp; ML\",\"item\":\"https:\\\/\\\/github.blog\\\/ai-and-ml\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"GitHub Copilot\",\"item\":\"https:\\\/\\\/github.blog\\\/ai-and-ml\\\/github-copilot\\\/\"},{\"@type\":\"ListItem\",\"position\":4,\"name\":\"Building AI-powered GitHub issue triage with the Copilot SDK\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/github.blog\\\/#website\",\"url\":\"https:\\\/\\\/github.blog\\\/\",\"name\":\"The GitHub Blog\",\"description\":\"Updates, ideas, and inspiration from GitHub to help developers build and design software.\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/github.blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/github.blog\\\/#\\\/schema\\\/person\\\/a1a244472a4104a81587cbe29e5a1ac1\",\"name\":\"Andrea Griffiths\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2025\\\/08\\\/Andrea-Griffiths_avatar_1755783168-96x96.jpeg9bbe944fe21f5e84b359f268822378d2\",\"url\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2025\\\/08\\\/Andrea-Griffiths_avatar_1755783168-96x96.jpeg\",\"contentUrl\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2025\\\/08\\\/Andrea-Griffiths_avatar_1755783168-96x96.jpeg\",\"caption\":\"Andrea Griffiths\"},\"description\":\"Andrea is a Senior Developer Advocate at GitHub with over a decade of experience in developer tools. She combines technical depth with a mission to make advanced technologies more accessible. After transitioning from Army service and construction management to software development, she brings a unique perspective to bridging complex engineering concepts with practical implementation. She lives in Florida with her Welsh partner, two sons, and two dogs, where she continues to drive innovation and support open source through GitHub's global initiatives. Find her online @acolombiadev.\",\"sameAs\":[\"https:\\\/\\\/andreagriffiths.dev\\\/\",\"https:\\\/\\\/www.linkedin.com\\\/in\\\/acolombiadev\\\/\",\"https:\\\/\\\/x.com\\\/acolombiadev\",\"https:\\\/\\\/www.youtube.com\\\/@acolombiadev\"],\"jobTitle\":\"Senior Developer Advocate\",\"worksFor\":\"GitHub\",\"url\":\"https:\\\/\\\/github.blog\\\/author\\\/andreagriffiths11\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Building AI-powered GitHub issue triage with the Copilot SDK - The GitHub Blog","description":"Learn how to integrate the Copilot SDK into a React Native app to generate AI-powered issue summaries.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/github.blog\/ai-and-ml\/github-copilot\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\/","og_locale":"en_US","og_type":"article","og_title":"Building AI-powered GitHub issue triage with the Copilot SDK","og_description":"Learn how to integrate the Copilot SDK into a React Native app to generate AI-powered issue summaries.","og_url":"https:\/\/github.blog\/ai-and-ml\/github-copilot\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\/","og_site_name":"The GitHub Blog","article_published_time":"2026-03-24T16:00:00+00:00","article_modified_time":"2026-04-22T20:46:26+00:00","og_image":[{"width":1920,"height":1080,"url":"https:\/\/github.blog\/wp-content\/uploads\/2026\/01\/generic-copilot-logo-github.png","type":"image\/png"}],"author":"Andrea Griffiths","twitter_card":"summary_large_image","twitter_creator":"@acolombiadev","twitter_misc":{"Written by":"Andrea Griffiths","Est. reading time":"7 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/github.blog\/ai-and-ml\/github-copilot\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\/#article","isPartOf":{"@id":"https:\/\/github.blog\/ai-and-ml\/github-copilot\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\/"},"author":{"name":"Andrea Griffiths","@id":"https:\/\/github.blog\/#\/schema\/person\/a1a244472a4104a81587cbe29e5a1ac1"},"headline":"Building AI-powered GitHub issue triage with the Copilot SDK","datePublished":"2026-03-24T16:00:00+00:00","dateModified":"2026-04-22T20:46:26+00:00","mainEntityOfPage":{"@id":"https:\/\/github.blog\/ai-and-ml\/github-copilot\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\/"},"wordCount":1403,"image":{"@id":"https:\/\/github.blog\/ai-and-ml\/github-copilot\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\/#primaryimage"},"thumbnailUrl":"https:\/\/github.blog\/wp-content\/uploads\/2026\/01\/generic-copilot-logo-github.png?fit=1920%2C1080","keywords":["AI integration","GitHub Copilot SDK","GitHub Issue","React Native"],"articleSection":["AI &amp; ML","Application development","Developer skills","GitHub Copilot"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/github.blog\/ai-and-ml\/github-copilot\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\/","url":"https:\/\/github.blog\/ai-and-ml\/github-copilot\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\/","name":"Building AI-powered GitHub issue triage with the Copilot SDK - The GitHub Blog","isPartOf":{"@id":"https:\/\/github.blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/github.blog\/ai-and-ml\/github-copilot\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\/#primaryimage"},"image":{"@id":"https:\/\/github.blog\/ai-and-ml\/github-copilot\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\/#primaryimage"},"thumbnailUrl":"https:\/\/github.blog\/wp-content\/uploads\/2026\/01\/generic-copilot-logo-github.png?fit=1920%2C1080","datePublished":"2026-03-24T16:00:00+00:00","dateModified":"2026-04-22T20:46:26+00:00","author":{"@id":"https:\/\/github.blog\/#\/schema\/person\/a1a244472a4104a81587cbe29e5a1ac1"},"description":"Learn how to integrate the Copilot SDK into a React Native app to generate AI-powered issue summaries.","breadcrumb":{"@id":"https:\/\/github.blog\/ai-and-ml\/github-copilot\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/github.blog\/ai-and-ml\/github-copilot\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/github.blog\/ai-and-ml\/github-copilot\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\/#primaryimage","url":"https:\/\/github.blog\/wp-content\/uploads\/2026\/01\/generic-copilot-logo-github.png?fit=1920%2C1080","contentUrl":"https:\/\/github.blog\/wp-content\/uploads\/2026\/01\/generic-copilot-logo-github.png?fit=1920%2C1080","width":1920,"height":1080,"caption":"Copilot hovering beside a cluster of geometric cubes in a decorative scene."},{"@type":"BreadcrumbList","@id":"https:\/\/github.blog\/ai-and-ml\/github-copilot\/building-ai-powered-github-issue-triage-with-the-copilot-sdk\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/github.blog\/"},{"@type":"ListItem","position":2,"name":"AI &amp; ML","item":"https:\/\/github.blog\/ai-and-ml\/"},{"@type":"ListItem","position":3,"name":"GitHub Copilot","item":"https:\/\/github.blog\/ai-and-ml\/github-copilot\/"},{"@type":"ListItem","position":4,"name":"Building AI-powered GitHub issue triage with the Copilot SDK"}]},{"@type":"WebSite","@id":"https:\/\/github.blog\/#website","url":"https:\/\/github.blog\/","name":"The GitHub Blog","description":"Updates, ideas, and inspiration from GitHub to help developers build and design software.","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/github.blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/github.blog\/#\/schema\/person\/a1a244472a4104a81587cbe29e5a1ac1","name":"Andrea Griffiths","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/github.blog\/wp-content\/uploads\/2025\/08\/Andrea-Griffiths_avatar_1755783168-96x96.jpeg9bbe944fe21f5e84b359f268822378d2","url":"https:\/\/github.blog\/wp-content\/uploads\/2025\/08\/Andrea-Griffiths_avatar_1755783168-96x96.jpeg","contentUrl":"https:\/\/github.blog\/wp-content\/uploads\/2025\/08\/Andrea-Griffiths_avatar_1755783168-96x96.jpeg","caption":"Andrea Griffiths"},"description":"Andrea is a Senior Developer Advocate at GitHub with over a decade of experience in developer tools. She combines technical depth with a mission to make advanced technologies more accessible. After transitioning from Army service and construction management to software development, she brings a unique perspective to bridging complex engineering concepts with practical implementation. She lives in Florida with her Welsh partner, two sons, and two dogs, where she continues to drive innovation and support open source through GitHub's global initiatives. Find her online @acolombiadev.","sameAs":["https:\/\/andreagriffiths.dev\/","https:\/\/www.linkedin.com\/in\/acolombiadev\/","https:\/\/x.com\/acolombiadev","https:\/\/www.youtube.com\/@acolombiadev"],"jobTitle":"Senior Developer Advocate","worksFor":"GitHub","url":"https:\/\/github.blog\/author\/andreagriffiths11\/"}]}},"jetpack_publicize_connections":[],"jetpack_shortlink":"https:\/\/wp.me\/pamS32-oCR","jetpack_sharing_enabled":true,"jetpack_featured_media_url":"https:\/\/github.blog\/wp-content\/uploads\/2026\/01\/generic-copilot-logo-github.png?fit=1920%2C1080","_links":{"self":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts\/94665","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/users\/1825"}],"replies":[{"embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/comments?post=94665"}],"version-history":[{"count":9,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts\/94665\/revisions"}],"predecessor-version":[{"id":95482,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts\/94665\/revisions\/95482"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/media\/93169"}],"wp:attachment":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/media?parent=94665"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/categories?post=94665"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/tags?post=94665"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/coauthors?post=94665"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}