---
title: "Errors"
description: "Find out why a tool call failed, and let the model recover from it."
canonical_url: "https://github-tools.com/errors"
---
# Errors

> Find out why a tool call failed, and let the model recover from it.

## Overview

GitHub tools turns each GitHub API failure it can classify into an error with a stable code, the real cause and a fix, because GitHub's own errors often point at the wrong cause. GitHub answers 404 for a private repository the token can't see, and an expired Vercel OIDC token surfaces as a bare "Not authorized". A model reads both as "the repository is private" or "permissions are missing", and reports the wrong cause.

<error-recovery>



</error-recovery>

The model receives the classified error instead, so it can correct its input or tell the user what to change. Your code gets the same fields to log or act on.

## What the model sees

Each error is an [evlog](https://evlog.dev) error with a code, a `why` and a `fix`:

```json
{
  "name": "EvlogError",
  "message": "GitHub resource not found (404): Not Found",
  "status": 404,
  "data": {
    "code": "github_tools.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"
  }
}
```

`link` points to the page that explains the cause, so the model or a person can read further.

<table>
<thead>
  <tr>
    <th>
      Integration
    </th>
    
    <th>
      A failed call
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <a href="https://github-tools.com/integrations/ai-sdk">
        AI SDK
      </a>
    </td>
    
    <td>
      Becomes a <code>
        tool-error
      </code>
      
       part, and the model gets the serialized error above on its next step. In UI messages, the part's <code>
        errorText
      </code>
      
       is the <code>
        message
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <a href="https://github-tools.com/integrations/eve">
        eve
      </a>
    </td>
    
    <td>
      Throws, so eve settles the call as <code>
        failed
      </code>
      
      . The model gets <code>
        { code, message, why, fix, link }
      </code>
      
       as JSON in the error result
    </td>
  </tr>
</tbody>
</table>

A failing tool doesn't stop `generateText` or `streamText`: the model reads the error and can correct its input or explain the cause.

## Look up a code

Every code starts with `github_tools.`, as in `github_tools.NOT_FOUND`.

### Auth and Vercel Connect

<table>
<thead>
  <tr>
    <th>
      Code
    </th>
    
    <th>
      When
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        TOKEN_REQUIRED
      </code>
    </td>
    
    <td>
      No token, token function result or <code>
        GITHUB_TOKEN
      </code>
      
       was available. See <a href="https://github-tools.com/auth">
        Auth
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        OIDC_TOKEN_EXPIRED
      </code>
    </td>
    
    <td>
      <code>
        VERCEL_OIDC_TOKEN
      </code>
      
       has expired. Thrown before any request, with the expiry time. Run <code>
        vercel env pull
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        CONNECT_NOT_AUTHORIZED
      </code>
    </td>
    
    <td>
      Connect refused the process's identity (403). The request never reached GitHub, so this isn't a GitHub permission problem
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        CONNECT_USER_NOT_CONNECTED
      </code>
    </td>
    
    <td>
      A <a href="https://github-tools.com/auth#per-user-tokens">
        user token
      </a>
      
       was requested for someone who hasn't connected GitHub
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        CONNECT_INSTALLATION_REQUIRED
      </code>
    </td>
    
    <td>
      The connector's GitHub App isn't installed on the target account, named in the message
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        SUBJECT_CONTEXT_REQUIRED
      </code>
    </td>
    
    <td>
      An eve <code>
        connect
      </code>
      
       or <code>
        connect.subject
      </code>
      
       function ran outside a tool call
    </td>
  </tr>
</tbody>
</table>

### Decision model and agent setup

<table>
<thead>
  <tr>
    <th>
      Code
    </th>
    
    <th>
      When
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        EVALUATION_UNAVAILABLE
      </code>
    </td>
    
    <td>
      <code>
        'auto'
      </code>
      
       was set but the installed <code>
        ai
      </code>
      
       is older than 7.0.105. Thrown when the tools or agent are created
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        EVALUATION_FAILED
      </code>
    </td>
    
    <td>
      The <a href="https://github-tools.com/approval#let-a-model-decide">
        decision model
      </a>
      
       call failed. Logged as a warning, not thrown: approval asks, and <code>
        preset: 'auto'
      </code>
      
       uses <code>
        repo-explorer
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        SESSION_SCOPE_NEEDS_TOOL_SELECTION
      </code>
    </td>
    
    <td>
      <code>
        createGithubAgent
      </code>
      
       got <code>
        scope: 'session'
      </code>
      
       with <code>
        selection: 'presets'
      </code>
      
      . See <a href="https://github-tools.com/presets#route-to-presets-instead">
        Presets
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        DURABLE_GENERATE_UNAVAILABLE
      </code>
    </td>
    
    <td>
      <code>
        createDurableGithubAgent(...).generate()
      </code>
      
       needs <code>
        @ai-sdk/workflow
      </code>
      
       2.0.59 or later. Use <code>
        .stream()
      </code>
      
       or upgrade
    </td>
  </tr>
</tbody>
</table>

### Guardrails

<table>
<thead>
  <tr>
    <th>
      Code
    </th>
    
    <th>
      Status
    </th>
    
    <th>
      When
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        GUARDRAIL_BLOCKED
      </code>
    </td>
    
    <td>
      403
    </td>
    
    <td>
      The call targets a repository or branch outside <a href="https://github-tools.com/approval#guardrails">
        <code>
          guardrails
        </code>
      </a>
      
      , or its input contains what looks like a credential. Checked before any request reaches GitHub, whoever approved the call. The message gives the reason
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        CALLER_FORBIDDEN
      </code>
    </td>
    
    <td>
      403
    </td>
    
    <td>
      On the eve extension with <a href="https://github-tools.com/integrations/eve#per-user-tokens">
        <code>
          actAs: 'caller'
        </code>
      </a>
      
      , someone who commented on GitHub asked for a call their role on the target repository doesn't allow. Checked before the call reaches GitHub. The message names the role they have and the one the call needs
    </td>
  </tr>
</tbody>
</table>

### GitHub API

<table>
<thead>
  <tr>
    <th>
      Code
    </th>
    
    <th>
      Status
    </th>
    
    <th>
      When
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        UNAUTHORIZED
      </code>
    </td>
    
    <td>
      401
    </td>
    
    <td>
      The token is invalid, expired or revoked
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        FORBIDDEN
      </code>
    </td>
    
    <td>
      403
    </td>
    
    <td>
      A missing permission, SAML enforcement, or a gist or notification tool called with an installation token
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        RATE_LIMITED
      </code>
    </td>
    
    <td>
      403, 429
    </td>
    
    <td>
      The rate limit is used up. The message has the remaining count and the reset time
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        NOT_FOUND
      </code>
    </td>
    
    <td>
      404
    </td>
    
    <td>
      The resource doesn't exist, or the token can't see it
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        VALIDATION_FAILED
      </code>
    </td>
    
    <td>
      422
    </td>
    
    <td>
      GitHub refused well-formed input, such as a duplicate or an unknown ref. GitHub's message names the field
    </td>
  </tr>
</tbody>
</table>

## Handle a code

To react to a failure in your own code, such as backing off on a rate limit, find the failed calls in the steps. `parseError` from evlog reads the fields back from any of these errors:

```ts [report.ts]
import { githubToolsErrors } from '@github-tools/sdk'
import { generateText } from 'ai'
import { parseError } from 'evlog'

const result = await generateText({ model, tools, prompt })

for (const part of result.steps.flatMap(step => step.content)) {
  if (part.type !== 'tool-error') continue
  const { code, why, fix } = parseError(part.error)
  if (code === githubToolsErrors.RATE_LIMITED.code) {
    // back off until the reset time in the message
  }
}
```

## Fail the same way in your own tools

When you write custom tools next to these, each entry of `githubToolsErrors` also builds the error, so your tools fail with the same shape:

```ts
throw githubToolsErrors.NOT_FOUND({ detail: 'No such workflow' })
```

## FAQ

<accordion>
<accordion-item label="Do setup errors reach the model?">

No, errors from your own setup, such as `TOKEN_REQUIRED` from `createGithubTools()`, throw from your code as usual.

</accordion-item>

<accordion-item label="Does the model see the request URL or GitHub's raw error?">

No, the request URL and GitHub's raw error stay on the error, as `internal` and `cause`, and are never sent to the model.

</accordion-item>

<accordion-item label="What about statuses without a code, such as 5xx?">

Statuses without a code pass through as Octokit's own error.

</accordion-item>
</accordion>


## Sitemap

See the full [sitemap](https://github-tools.com/sitemap.md) for all pages.
