Guide
Structured, agent-readable errors — every failure carries a stable code, the technical cause, and an actionable fix.

Raw API failures make models hallucinate. GitHub answers 404 for private repositories the token cannot see, and an expired Vercel OIDC token surfaces as a bare "Not authorized" — both read as "the repo is private" or "permissions are missing" to a model, which then confidently reports the wrong cause.

The SDK maps every failure it can classify to a structured error from an evlog catalog. Each error carries:

  • code — stable, machine-readable identifier (github_tools.NOT_FOUND)
  • message — what happened, with the GitHub or Connect detail embedded
  • why — the technical cause, including the non-obvious ones
  • fix — the actionable remedy
  • link — the relevant documentation page (this site for auth/Connect setup, GitHub's REST troubleshooting docs for API errors)

Error codes

Authentication and Vercel Connect

CodeWhen
TOKEN_REQUIREDNo token string, provider, or GITHUB_TOKEN env var was available
OIDC_TOKEN_EXPIREDVERCEL_OIDC_TOKEN is past its exp claim — thrown before any request is made, with the exact expiry time. Run vercel env pull locally
CONNECT_NOT_AUTHORIZEDConnect rejected the calling process's identity (403). The request never reached GitHub — this is not a GitHub permission problem
CONNECT_USER_NOT_CONNECTEDA { type: 'user' } subject was requested but that user has no active GitHub connection
CONNECT_INSTALLATION_REQUIREDThe connector's GitHub App is not installed on the target account
SUBJECT_CONTEXT_REQUIREDA connect.subject resolver ran outside a tool execution (no eve context)

GitHub API

CodeStatusWhen
UNAUTHORIZED401Token invalid, expired, or revoked
FORBIDDEN403Missing scope, SAML enforcement, or a user-token-only API (gists, notifications) called with an installation token
RATE_LIMITED403/429Rate limit exhausted — the message includes remaining/limit and the reset timestamp
NOT_FOUND404The resource does not exist or the token cannot see it — GitHub returns 404 instead of 403 for private resources
VALIDATION_FAILED422Well-formed input that GitHub refused, with GitHub's message embedded

Statuses without a catalog entry (5xx, redirects) pass through unchanged. The original Octokit RequestError stays reachable as cause; request coordinates live in internal and are never serialized toward the model.

What the model sees

In the eve extension, a failing tool returns the structure instead of a flat string:

{
  "error": {
    "code": "github_tools.NOT_FOUND",
    "message": "GitHub resource not found (404): Not Found",
    "why": "Either the resource does not exist, or the token cannot see it — GitHub deliberately returns 404 instead of 403 for private resources the token has no access to.",
    "fix": "Check the owner/repo/number input first. If it is correct, the token lacks access: grant the repository to the PAT or App installation, or use a Connect subject that has access.",
    "link": "https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api#404-not-found-for-an-existing-resource"
  }
}

internal stays off the wire (request URL, raw status). link is included so the model can fetch the docs page when why/fix are not enough.

AI SDK consumers

With generateText/streamText, tool execution errors are thrown and the framework forwards error.message to the model. The catalog messages are written to be self-sufficient for that path. If you need the full structure (error boundaries, logging, custom onError), use evlog's parseError:

import { parseError } from 'evlog'

try {
  await generateText({ model, tools, prompt })
}
catch (error) {
  const { code, message, why, fix } = parseError(error)
  // code autocompletes against the github_tools catalog
}

The catalog itself is exported as githubToolsErrors from @github-tools/sdk for consumers who want to match on specific factories or reuse the codes.