Skip to content

GitHub

Overview#

The GitHub integration enables your Cognest assistant to interact with repositories through the GitHub REST API and receive real-time events via webhooks. It can create and manage issues, open pull requests, post review comments, apply labels, and respond to pushes, merges, and other repository activity. Authentication supports both Personal Access Tokens (PATs) and GitHub App installations for fine-grained permissions.

Prerequisites#

  1. A GitHub account with access to the target repositories
  2. One of: a Personal Access Token (classic or fine-grained) or a GitHub App installed on the repository/organization
  3. Token scopes (PAT): repo, issues:write, pull_requests:write, webhooks
  4. A webhook secret for verifying incoming webhook payloads (recommended)

GitHub Apps vs PATs

For production deployments, we recommend using a GitHub App installation instead of a PAT. GitHub Apps have higher rate limits (5,000 requests/hour vs 5,000/hour for PATs), fine-grained permissions per repository, and don't expire unless manually revoked. Create a GitHub App at github.com/settings/apps.

Configuration#

Add the GitHub integration to your cognest.config.yaml:

cognest.config.yaml
integrations:
  github:
    enabled: true
    credentials:
      token: ${GITHUB_TOKEN}
      webhook_secret: ${GITHUB_WEBHOOK_SECRET}
    settings:
      default_owner: "your-org"           # Default repo owner for shorthand
      default_repo: "your-repo"           # Default repo name for shorthand
      webhook_events:                     # Events to listen for
        - issues
        - pull_request
        - push
        - issue_comment
        - pull_request_review
      auto_label: true                    # Auto-apply labels via Think Engine
      stale_issue_days: 30                # Days before an issue is considered stale

Set the corresponding environment variables in your .env file:

.env
# For PAT authentication
GITHUB_TOKEN=ghp_your_personal_access_token
GITHUB_WEBHOOK_SECRET=your-webhook-secret

# For GitHub App authentication (alternative)
# GITHUB_APP_ID=123456
# GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n..."
# GITHUB_APP_INSTALLATION_ID=78901234

SDK Usage#

Access the GitHub integration through the @cognest/sdk and @cognest/integrations/github:

skills/issue-workflow.ts
import { Cognest } from "@cognest/sdk";
import { GitHubIntegration } from "@cognest/integrations/github";

const cognest = new Cognest();
const github = cognest.integration<GitHubIntegration>("github");

// Create a new issue
const issue = await github.createIssue({
  owner: "your-org",
  repo: "your-repo",
  title: "Bug: Login page returns 500 on invalid email",
  body: [
    "## Description",
    "Submitting an invalid email format on the login page causes a 500 error.",
    "",
    "## Steps to Reproduce",
    "1. Navigate to /login",
    '2. Enter "notanemail" in the email field',
    "3. Click Submit",
    "",
    "## Expected Behavior",
    "A validation error should be shown to the user.",
  ].join("\n"),
  labels: ["bug", "priority:high"],
  assignees: ["developer-username"],
});

// Create a pull request
const pr = await github.createPR({
  owner: "your-org",
  repo: "your-repo",
  title: "fix: validate email format on login page",
  body: `Closes #${issue.number}\n\nAdds client-side and server-side email validation.`,
  head: "fix/login-email-validation",
  base: "main",
  draft: false,
});

// Add a comment to the issue
await github.createComment({
  owner: "your-org",
  repo: "your-repo",
  issue_number: issue.number,
  body: `Fix submitted in #${pr.number}. Awaiting review.`,
});

Available Methods#

MethodDescriptionReturns
`createIssue(options)`Create a new issue in a repository`Promise<GitHubIssue>`
`createComment(options)`Add a comment to an issue or pull request`Promise<GitHubComment>`
`createPR(options)`Open a new pull request`Promise<GitHubPullRequest>`
`getRepo(options)`Get repository metadata (stars, forks, default branch, etc.)`Promise<GitHubRepo>`
`listIssues(options)`List and filter issues in a repository`Promise<GitHubIssue[]>`
`addLabel(options)`Add one or more labels to an issue or PR`Promise<GitHubLabel[]>`
`closeIssue(options)`Close an issue with an optional closing comment`Promise<GitHubIssue>`

Events#

Subscribe to GitHub webhook events delivered to your assistant:

EventTriggerPayload
`issue:opened`A new issue is created in the repository`{ action, issue, repository, sender }`
`issue:closed`An issue is closed`{ action, issue, repository, sender }`
`pr:opened`A new pull request is opened`{ action, pull_request, repository, sender }`
`pr:merged`A pull request is merged into the base branch`{ action, pull_request, repository, sender }`
`push`One or more commits are pushed to a branch`{ ref, commits, repository, sender, head_commit }`
`comment`A comment is posted on an issue or PR`{ action, comment, issue, repository, sender }`

Example#

This example builds an intelligent issue triage skill that automatically labels, prioritizes, and assigns new issues using the Think Engine:

skills/issue-triage.ts
import { Cognest, Skill, IncomingEvent } from "@cognest/sdk";
import {
  GitHubIntegration,
  GitHubIssueEvent,
  GitHubPushEvent,
} from "@cognest/integrations/github";

const cognest = new Cognest();
const github = cognest.integration<GitHubIntegration>("github");

const issueTriage = new Skill({
  name: "issue-triage",
  description: "Auto-triage, label, and assign new GitHub issues",
});

// Auto-triage new issues
issueTriage.on(
  "github:issue:opened",
  async (event: IncomingEvent<GitHubIssueEvent>) => {
    const { issue, repository } = event.payload;
    const { owner, repo } = {
      owner: repository.owner.login,
      repo: repository.name,
    };

    // Use the Think Engine to classify the issue
    const triage = await cognest.think({
      prompt: `You are a GitHub issue triage assistant for the ${repo} repository.

Analyze this issue and classify it.

Title: ${issue.title}
Body: ${issue.body?.slice(0, 1500) ?? "(empty)"}
Author: ${issue.user.login}

Respond with JSON:
{
  "category": "bug" | "feature" | "docs" | "question" | "chore",
  "priority": "low" | "medium" | "high" | "critical",
  "estimated_effort": "small" | "medium" | "large",
  "suggested_assignee": "username or null",
  "summary": "one-line summary"
}`,
      responseFormat: "json",
    });

    const { category, priority, estimated_effort, suggested_assignee, summary } =
      triage.parsed;

    // Apply labels
    const labels = [
      category,
      `priority:${priority}`,
      `effort:${estimated_effort}`,
    ];

    await github.addLabel({
      owner,
      repo,
      issue_number: issue.number,
      labels,
    });

    // Post a triage summary comment
    await github.createComment({
      owner,
      repo,
      issue_number: issue.number,
      body: [
        "## Triage Summary",
        "",
        `| Field | Value |`,
        `|---|---|`,
        `| **Category** | ${category} |`,
        `| **Priority** | ${priority} |`,
        `| **Effort** | ${estimated_effort} |`,
        `| **Summary** | ${summary} |`,
        "",
        suggested_assignee
          ? `Suggested assignee: @${suggested_assignee}`
          : "No assignee suggested — please self-assign if this is in your area.",
        "",
        "*This issue was triaged automatically by Cognest.*",
      ].join("\n"),
    });

    // Auto-close duplicate detection
    const existingIssues = await github.listIssues({
      owner,
      repo,
      state: "open",
      labels: category,
      per_page: 50,
    });

    const duplicateCheck = await cognest.think({
      prompt: `Given this new issue:
Title: "${issue.title}"
Summary: "${summary}"

And these existing open issues:
${existingIssues
  .filter((i) => i.number !== issue.number)
  .slice(0, 20)
  .map((i) => `#${i.number}: ${i.title}`)
  .join("\n")}

Is this a duplicate of any existing issue? Respond with JSON:
{ "is_duplicate": boolean, "duplicate_of": number | null }`,
      responseFormat: "json",
    });

    if (duplicateCheck.parsed.is_duplicate && duplicateCheck.parsed.duplicate_of) {
      await github.createComment({
        owner,
        repo,
        issue_number: issue.number,
        body: `This appears to be a duplicate of #${duplicateCheck.parsed.duplicate_of}. Closing as duplicate.\n\n*Detected by Cognest issue triage.*`,
      });

      await github.addLabel({
        owner,
        repo,
        issue_number: issue.number,
        labels: ["duplicate"],
      });

      await github.closeIssue({
        owner,
        repo,
        issue_number: issue.number,
        reason: "not_planned",
      });
    }

    cognest.log.info(
      `Triaged issue #${issue.number} as ${category}/${priority} in ${owner}/${repo}`
    );
  }
);

// Post a summary comment on push events with multiple commits
issueTriage.on(
  "github:push",
  async (event: IncomingEvent<GitHubPushEvent>) => {
    const { commits, repository, ref } = event.payload;
    const branch = ref.replace("refs/heads/", "");

    // Only summarize pushes to main with 3+ commits
    if (branch !== "main" || commits.length < 3) return;

    const summary = await cognest.think({
      prompt: `Summarize these ${commits.length} commits pushed to main in 2-3 sentences:

${commits.map((c) => `- ${c.message}`).join("\n")}`,
    });

    cognest.log.info(
      `Push to ${repository.full_name}/main: ${summary.text}`
    );
  }
);

cognest.register(issueTriage);
cognest.start();

Troubleshooting#

Common Issues

**"Bad credentials" or 401 errors** — Your `GITHUB_TOKEN` is invalid, expired, or lacks required scopes. Classic PATs start with `ghp_`, fine-grained tokens start with `github_pat_`. Regenerate the token at github.com/settings/tokens. **Webhooks not arriving** — Verify the webhook URL is publicly accessible and the secret matches your `GITHUB_WEBHOOK_SECRET`. Check the webhook delivery log at github.com/your-org/your-repo/settings/hooks for failed deliveries and response codes. **"Resource not accessible by integration" (403)** — The token does not have permission for the requested action. For fine-grained PATs, ensure the specific repository and permission (e.g., Issues: Read and Write) are granted. For GitHub Apps, check the installation permissions. **Rate limiting (403 with `X-RateLimit-Remaining: 0`)** — GitHub allows 5,000 requests/hour for PATs and GitHub Apps. Cognest tracks rate limit headers and queues requests automatically. If you hit limits frequently, consider using a GitHub App (higher limits) or caching responses with `cognest.cache()`.