Build an AI Agent That Turns Meetings Into GitHub Issues
Learn to build a practical AI agent that transcribes meetings, extracts action items, and creates GitHub issues automatically.
Before you start
You just left a 45-minute planning meeting. Three action items were assigned, two decisions were made, and one deadline was set. By the time you get back to your desk, half of that is already fuzzy. The other half never makes it into your issue tracker.
This tutorial walks you through building a small AI agent that fixes that. It listens to a recorded meeting, transcribes the audio, extracts action items and decisions, and creates GitHub issues with labels, assignees, and due dates. You will end with a script you can run against any meeting recording, plus a clear picture of how to extend it.
You need Node.js 18 or newer, a GitHub personal access token, and an OpenAI API key (or any OpenAI-compatible endpoint). You also need ffmpeg installed if your recordings are not already in a supported format. Everything else is in the code you will write.
node --version
npm --version
ffmpeg -version- Node.js 18+ installed on your machine
- A GitHub personal access token with repo and issues scopes
- An OpenAI API key (or compatible endpoint) with access to gpt-4o-mini
- ffmpeg installed for audio conversion (optional if recordings are mp3/wav)
- A sample meeting recording to test with (or you can use a public one)
Step 1: Set up the project
Create a new directory and initialize a Node.js project. You will install a few dependencies: the OpenAI SDK, the GitHub REST client, and a library for parsing audio metadata.
Use npm to install the packages. The GitHub client is @octokit/rest, and we will use openai for the LLM calls. We also need dotenv to load environment variables from a .env file.
mkdir meeting-agent
cd meeting-agent
npm init -y
npm install openai @octokit/rest dotenv- mkdir meeting-agent && cd meeting-agent
- npm init -y
- npm install openai @octokit/rest dotenv
- Create a .env file with your keys (see below)
Step 2: Configure environment variables
Create a .env file in the project root. The agent needs your OpenAI API key, your GitHub token, and the repository where issues should be created. Use the format owner/repo for the repository.
Keep this file out of version control. Add .env to your .gitignore. The dotenv package will load these variables when the script runs.
OPENAI_API_KEY=your_openai_key_here
GITHUB_TOKEN=your_github_token_here
GITHUB_REPO=your-username/your-repoStep 3: Transcribe the meeting audio
The first piece of the agent is the transcription step. We will use OpenAI's Whisper API to convert the audio file into text. The API accepts mp3, mp4, mpeg, mpga, m4a, wav, and webm formats. If your file is in another format, convert it with ffmpeg first.
Write a function that takes the audio file path, reads it, and sends it to the transcription endpoint. The response will contain the full transcript text.
const fs = require('fs');
const { OpenAI } = require('openai');
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
async function transcribeAudio(filePath) {
const response = await openai.audio.transcriptions.create({
file: fs.createReadStream(filePath),
model: 'whisper-1',
language: 'en',
response_format: 'text'
});
return response;
}
module.exports = { transcribeAudio };Step 4: Extract action items with the LLM
Now that you have the transcript, you need to extract structured action items. We will use gpt-4o-mini with a carefully crafted prompt. The model should output JSON with a list of issues, each containing a title, body, labels, assignees, and a due date.
The prompt instructs the model to identify action items, decisions, and owners. It also asks for a confidence score so you can filter out low-confidence items if you want. Make sure to set response_format to json_object to get valid JSON.
const { OpenAI } = require('openai');
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
async function extractIssues(transcript) {
const prompt = `You are an assistant that extracts action items from meeting transcripts.
Return a JSON object with an "issues" array. Each issue has:
- title (string)
- body (string)
- labels (array of strings)
- assignees (array of GitHub usernames)
- due_date (ISO date string or null)
- confidence (number 0-1)
Transcript:
${transcript}`;
const response = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: prompt }],
response_format: { type: 'json_object' },
temperature: 0.2
});
return JSON.parse(response.choices[0].message.content);
}
module.exports = { extractIssues };Step 5: Create GitHub issues
The final step is to create the issues on GitHub. Use the Octokit REST client to POST to the issues endpoint. You can set labels, assignees, and a due date in the issue body (GitHub doesn't have native due dates, so we append it to the body).
Loop through the extracted issues, filter out low-confidence ones (below 0.7), and create each one. Log the issue number and URL so you can verify later.
const { Octokit } = require('@octokit/rest');
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
const [owner, repo] = process.env.GITHUB_REPO.split('/');
async function createIssue(issue) {
if (issue.confidence < 0.7) {
console.log('Skipping low confidence issue:', issue.title);
return null;
}
const body = issue.body + (issue.due_date ? `\n\n**Due:** ${issue.due_date}` : '');
const response = await octokit.issues.create({
owner,
repo,
title: issue.title,
body,
labels: issue.labels,
assignees: issue.assignees
});
console.log(`Created issue #${response.data.number}: ${response.data.html_url}`);
return response.data;
}
module.exports = { createIssue };Step 6: Wire it together
Create an index.js that ties everything together. It should take the audio file path as a command-line argument, run transcription, extraction, and issue creation in sequence. Add error handling and a summary at the end.
This is the main entry point. You can run it with node index.js meeting.mp3. Make sure to load the .env file at the top.
require('dotenv').config();
const { transcribeAudio } = require('./transcribe');
const { extractIssues } = require('./extract');
const { createIssue } = require('./github');
async function main() {
const filePath = process.argv[2];
if (!filePath) {
console.error('Usage: node index.js <audio-file>');
process.exit(1);
}
console.log('Transcribing...');
const transcript = await transcribeAudio(filePath);
console.log('Transcript length:', transcript.length, 'characters');
console.log('Extracting action items...');
const { issues } = await extractIssues(transcript);
console.log('Found', issues.length, 'potential issues');
console.log('Creating GitHub issues...');
let created = 0;
for (const issue of issues) {
const result = await createIssue(issue);
if (result) created++;
}
console.log(`Done. Created ${created} issues.`);
}
main().catch(console.error);Step 7: Test with a sample recording
To test, you need an audio file. If you don't have one, you can record a short meeting using your phone or use a public sample. Make sure it's in a supported format (mp3, wav, m4a).
Run the script and observe the output. You should see the transcript length, the number of potential issues found, and the URLs of created issues. Check the GitHub repo to confirm the issues look correct.
node index.js meeting.mp3- If you get a 401 error, check your GitHub token permissions.
- If transcription fails, convert the file to mp3 with ffmpeg: ffmpeg -i input.webm -ar 16000 output.mp3
- If the JSON parsing fails, check the prompt and try increasing temperature.
- If issues are not created, verify the repo name and that you have write access.
Verify it worked
After running the script, open your GitHub repository. You should see new issues with the titles and labels you expected. Check that the assignees are correct and the due dates appear in the body.
You can also run the script again with a different recording to see how the extraction handles varied speech. The confidence filter helps avoid junk issues, but you can adjust the threshold in the code.
curl -H "Authorization: token $GITHUB_TOKEN" "https://api.github.com/repos/$GITHUB_REPO/issues?state=all" | jq '.[] | {number, title, labels: [.labels[].name]}'- List issues via the GitHub API: curl -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/repos/$GITHUB_REPO/issues
- Check the issue body for the due date line.
- Confirm labels are applied (e.g., 'meeting', 'action-item').
Recommended setup
For a production setup, you would wrap this script in a serverless function or a scheduled job. You could integrate with a meeting platform like Zoom or Google Meet to automatically process recordings after the meeting ends.
Here is a minimal Dockerfile to containerize the agent. Build the image and run it with the audio file mounted as a volume. This makes it easy to deploy to any cloud provider.
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
CMD ["node", "index.js"]- Use a queue (like BullMQ) to process recordings asynchronously.
- Store transcripts in a database for future reference.
- Add a webhook to trigger the agent when a new recording is uploaded.
- Set up monitoring to alert on failures.
Troubleshooting
If you run into issues, here are common problems and fixes. The most frequent issue is authentication: ensure your GitHub token has the 'repo' scope. For OpenAI, check that you have credits and the model ID is correct.
If the transcription returns empty, check the audio file format and length. Whisper works best with clear audio, so try to reduce background noise.
- Error: 'Invalid authentication' - Check GITHUB_TOKEN and scopes.
- Error: 'Model not found' - Ensure you have access to gpt-4o-mini or change the model.
- Error: 'File too large' - Whisper API has a 25MB limit; compress or split the audio.
- JSON parse error - The LLM output might be malformed; add more examples to the prompt.
- No issues created - Lower the confidence threshold in github.js.
FAQ
- Q: Can I use a different LLM? A: Yes, any OpenAI-compatible API works. Change the baseURL in the OpenAI client.
- Q: How do I handle multiple speakers? A: Whisper can include speaker diarization if you use the api with timestamps, but for simplicity we ignore it.
- Q: Can I create issues in multiple repos? A: Modify the script to loop over repos, or pass the repo as a parameter.
- Q: What about security? A: Never commit your .env file. Use a secrets manager in production.
Next steps
You now have a working AI agent that turns meeting recordings into GitHub issues. The next step is to integrate it into your workflow. Try running it on a real meeting recording and see how the extraction quality feels.
Then consider adding features like automatic labeling based on project, or sending a summary to Slack. The core loop is solid, and you can extend it in any direction.
Try it on code.live
You can test your JSON extraction logic or format the LLM output using the JSON Formatter tool on code.live. The JSON Formatter helps you validate and pretty-print the response from the LLM before you parse it in code.
Key takeaways
- Apply one concrete change from this post before collecting more reading.
- Prefer browser-side tools when the work involves secrets, tokens, or PII.
- Document the why next to the how so the next reviewer inherits context.
FAQ
- Who is this guide on ai-agents for?
- Working developers who need a practical take on build an ai agent that turns meetings into github issues — not a marketing overview. Skim the sections, apply one tip, then come back when you hit an edge case.
- Do I need an account to use the related tools?
- No. code.live tools run in your browser with no signup. Nothing you paste is uploaded to a server for the client-side utilities linked from this post.
- How often is this article updated?
- This post was published November 7, 2026. Fundamentals stay stable; check linked tool pages and official docs when version-specific behavior matters.