Skip to main content

Overview

This project shows how to build an AI agent using the Claude Agent SDK that explores GitHub commits, selectively fetches diffs for ambiguous changes, and writes a categorized, developer- friendly changelog — all streamed to the frontend in real time.

Tech stack

  • Next.js — frontend using the App Router
  • Claude Agent SDK — Anthropic’s SDK for building AI agents with custom tools
  • Trigger.dev — workflow orchestration, real-time streaming, and observability
  • Octokit — GitHub API client for fetching commits and diffs

Demo video

GitHub repo

View the changelog generator repo

Fork this repo to use it as a starting point for your own changelog automation.

How it works

1

Receive the request

The user provides a GitHub repository URL and a date range in the Next.js UI.
2

List commits

Claude calls the list_commits tool to fetch all commits in the date range via the GitHub API.
3

Analyze each commit

Claude categorizes each commit:
  • Skip trivial commits (typos, formatting)
  • Include obvious features or improvements directly
  • Fetch the full diff for ambiguous commits before deciding
4

Generate the changelog

Claude writes a categorized markdown changelog grouped by change type.
5

Stream to the frontend

The changelog text streams to the browser in real time via Trigger.dev Realtime as it’s generated.

trigger.config.ts

The Claude Agent SDK must be marked as external to prevent it from being bundled:
trigger.config.ts
import { defineConfig } from "@trigger.dev/sdk";

export default defineConfig({
  project: process.env.TRIGGER_PROJECT_REF!,
  runtime: "node",
  logLevel: "log",
  maxDuration: 300,
  build: {
    external: ["@anthropic-ai/claude-agent-sdk"],
  },
  machine: "small-2x",
});
Adding a package to external prevents it from being bundled into the task. This is required for the Claude Agent SDK. See the build configuration docs for more details.

Key task code

The main task uses the Claude Agent SDK with two custom tools: list_commits and get_commit_diff. Claude calls these autonomously to gather the information it needs.
trigger/generate-changelog.ts
import { task, metadata, runs } from "@trigger.dev/sdk";
import Anthropic from "@anthropic-ai/sdk";

export const generateChangelog = task({
  id: "generate-changelog",
  maxDuration: 300,
  run: async (payload: {
    repoUrl: string;
    since: string;
    until: string;
    githubToken?: string;
  }) => {
    const { repoUrl, since, until, githubToken } = payload;

    metadata.set("phase", "listing-commits");

    const client = new Anthropic();

    // Define custom tools the agent can call
    const tools: Anthropic.Tool[] = [
      {
        name: "list_commits",
        description: "List commits in the repository within the date range",
        input_schema: {
          type: "object" as const,
          properties: {
            since: { type: "string", description: "ISO 8601 start date" },
            until: { type: "string", description: "ISO 8601 end date" },
          },
          required: ["since", "until"],
        },
      },
      {
        name: "get_commit_diff",
        description: "Get the full diff for a specific commit SHA",
        input_schema: {
          type: "object" as const,
          properties: {
            sha: { type: "string", description: "The commit SHA" },
          },
          required: ["sha"],
        },
      },
    ];

    const messages: Anthropic.MessageParam[] = [
      {
        role: "user",
        content: `Generate a developer-friendly changelog for ${repoUrl} between ${since} and ${until}.
        Use the list_commits tool first, then fetch diffs only for ambiguous commits.
        Write the changelog in markdown with sections: Features, Bug Fixes, Improvements, Other.`,
      },
    ];

    let changelog = "";

    // Agentic loop
    while (true) {
      const response = await client.messages.create({
        model: "claude-opus-4-5",
        max_tokens: 4096,
        tools,
        messages,
      });

      metadata.set("phase", "analyzing");

      if (response.stop_reason === "end_turn") {
        // Extract the final text response
        for (const block of response.content) {
          if (block.type === "text") {
            changelog = block.text;
          }
        }
        break;
      }

      if (response.stop_reason === "tool_use") {
        const toolResults: Anthropic.ToolResultBlockParam[] = [];

        for (const block of response.content) {
          if (block.type !== "tool_use") continue;

          let result = "";

          if (block.name === "list_commits") {
            // Call GitHub API via Octokit
            result = await listCommits(repoUrl, block.input as any, githubToken);
          } else if (block.name === "get_commit_diff") {
            result = await getCommitDiff(repoUrl, block.input as any, githubToken);
          }

          toolResults.push({
            type: "tool_result",
            tool_use_id: block.id,
            content: result,
          });
        }

        messages.push({ role: "assistant", content: response.content });
        messages.push({ role: "user", content: toolResults });
      }
    }

    return { changelog };
  },
});

Features

  • Two-phase analysis — lists all commits first, then selectively fetches diffs only for ambiguous ones (saves API tokens)
  • Real-time streaming — the changelog streams to the frontend as it’s generated via Trigger.dev Realtime
  • Live observability — agent phase, turn count, and tool calls are broadcast via run metadata
  • Private repo support — pass an optional GitHub token for private repositories

Relevant code files

FileDescription
trigger/generate-changelog.tsMain agent task with custom tools
trigger/changelog-stream.tsStream definition for real-time output
app/api/generate-changelog/route.tsAPI endpoint that triggers the task
app/response/[runId]/page.tsxStreaming display page

Learn more

AI Agents overview

Comprehensive guide for building AI agents with Trigger.dev

Trigger.dev Realtime

Stream task progress and output to your frontend

Scheduled tasks

Automate changelog generation on a schedule

Build configuration

Learn how to use the external option to avoid bundling certain packages

Build docs developers (and LLMs) love