> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beta.b3os.org/llms.txt
> Use this file to discover all available pages before exploring further.

# GitHub

The io.b3os/github pack: 17 actions and 2 triggers with call snippets, fixture outputs, connection setup and error codes, generated from version 1.0.0.

`io.b3os/github` 1.0.0 · `developer-tools`, `issue-tracking`, `source-control` · MIT

Work with GitHub repositories using your own token: read repositories, files, commits, releases and Actions runs; search, read, open, comment on, label and close issues; list and inspect pull requests; react to repository events through a webhook B3OS creates and removes for you, or poll for new issues. Uses the REST API version 2022-11-28 at api.github.com (not GitHub Enterprise Server). A fine-grained personal access token or GitHub App token with Contents, Issues, Pull requests and Actions read access covers the read actions; the write actions need Issues write, and the webhook trigger needs Webhooks write (admin:repo_hook for classic tokens).

Every call's result carries `upstream_status` and `timing`: `validate_ms`, `inject_ms`, `render_ms`, `upstream_ms`, `map_ms` and `total_ms`. `upstream` is the service's time; the others are B3OS's own ([call actions](/actions/call)). The snippets below show no timing values, because a fixture records none.

## Connection

`token`, type `bearer`: GitHub token.

A fine-grained personal access token, a classic token, or a GitHub App installation token. Grant only the repositories and permissions the actions you use need.

How to get one: [docs.github.com](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens).

The credential is sent only to `api.github.com`.

Store the credential once as a [vault connection](/vault) for this pack and key, then name its id in a call's `connections`. Leave the key out and B3OS uses the project's newest connection for the pack and key without a `subject` ([connections](/actions/call#connections)).

Hosts it reaches: `api.github.com`.

## Actions

| Action | Effect | MCP tool | Title |
|---|---|---|---|
| [`get-repo`](/actions/catalog/github#github-get-repo) | read | `github__get-repo` | Get repository |
| [`list-issues`](/actions/catalog/github#github-list-issues) | read | `github__list-issues` | List issues |
| [`get-issue`](/actions/catalog/github#github-get-issue) | read | `github__get-issue` | Get issue |
| [`search-issues`](/actions/catalog/github#github-search-issues) | read | `github__search-issues` | Search issues and pull requests |
| [`create-issue`](/actions/catalog/github#github-create-issue) | write | `github__create-issue` | Create issue |
| [`add-issue-comment`](/actions/catalog/github#github-add-issue-comment) | write | `github__add-issue-comment` | Comment on issue or pull request |
| [`list-issue-comments`](/actions/catalog/github#github-list-issue-comments) | read | `github__list-issue-comments` | List issue comments |
| [`close-issue`](/actions/catalog/github#github-close-issue) | write | `github__close-issue` | Close issue |
| [`add-labels`](/actions/catalog/github#github-add-labels) | write | `github__add-labels` | Add labels |
| [`list-pull-requests`](/actions/catalog/github#github-list-pull-requests) | read | `github__list-pull-requests` | List pull requests |
| [`get-pull-request`](/actions/catalog/github#github-get-pull-request) | read | `github__get-pull-request` | Get pull request |
| [`get-file-contents`](/actions/catalog/github#github-get-file-contents) | read | `github__get-file-contents` | Get file contents |
| [`list-commits`](/actions/catalog/github#github-list-commits) | read | `github__list-commits` | List commits |
| [`get-latest-release`](/actions/catalog/github#github-get-latest-release) | read | `github__get-latest-release` | Get latest release |
| [`list-workflow-runs`](/actions/catalog/github#github-list-workflow-runs) | read | `github__list-workflow-runs` | List Actions runs |
| [`create-webhook`](/actions/catalog/github#github-create-webhook) | write | `github__create-webhook` | Create repository webhook |
| [`delete-webhook`](/actions/catalog/github#github-delete-webhook) | irreversible | `github__delete-webhook` | Delete repository webhook |

get-repo

**Get repository.** One repository's metadata: description, visibility, default branch, primary language, topics, license, stars, forks, open issue and pull request count, archived flag and last push time.

Ref `io.b3os/github/get-repo@1.0.0` · effect `read` · connection `token` · MCP tool `github__get-repo`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/github/get-repo@1",
          "input": {"owner": "acme", "repo": "widgets"},
          "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/github/get-repo@1",
      input: { owner: "acme", repo: "widgets" },
      connections: { token: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    });
    console.log(result.output);
    ```
  **Python**

```python
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/github/get-repo@1",
        "input": {"owner": "acme", "repo": "widgets"},
        "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    })
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "github__get-repo",
        "arguments": {"owner": "acme", "repo": "widgets"}
      }
    }
    ```

    Expose the pack as tools with `--packs github` on the local server or `?packs=github` on the remote URL, and the agent calls `github__get-repo` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: Get repository (documented response shape, example values). From the bundle's `fixtures/get-repo.json`, not a live call.

```json
{
  "full_name": "acme/widgets",
  "description": "Widget toolkit for the acme storefront",
  "private": false,
  "html_url": "https://github.com/acme/widgets",
  "default_branch": "main",
  "language": "TypeScript",
  "topics": ["ui", "components"],
  "license": "MIT",
  "stargazers_count": 1380,
  "forks_count": 42,
  "open_issues_count": 17,
  "archived": false,
  "pushed_at": "2026-10-25T21:14:02Z"
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | Repository not found, or the token cannot see it. |
| `connection_unauthorized` | 401 | no | GitHub rejected the token. |
| `rate_limited` | 403, 429 | yes | GitHub rate limit reached; it resets at the time in the x-ratelimit-reset header. |
| `forbidden` | 403 | no | built from the service's response |
| `gone` | 410 | no | Issues are disabled for this repository. |
| `invalid_request` | 422 | no | built from the service's response |

list-issues

**List issues.** Issues in a repository with filters for state, labels, assignee and update time, newest first by default. GitHub counts pull requests as issues; is_pull_request tells them apart. One page per call; increase page to continue.

Ref `io.b3os/github/list-issues@1.0.0` · effect `read` · connection `token` · MCP tool `github__list-issues`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/github/list-issues@1",
          "input": {
            "owner": "acme",
            "repo": "widgets",
            "labels": ["bug"],
            "per_page": 2
          },
          "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/github/list-issues@1",
      input: { owner: "acme", repo: "widgets", labels: ["bug"], per_page: 2 },
      connections: { token: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    });
    console.log(result.output);
    ```
  **Python**

```python
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/github/list-issues@1",
        "input": {
            "owner": "acme",
            "repo": "widgets",
            "labels": ["bug"],
            "per_page": 2,
        },
        "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    })
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "github__list-issues",
        "arguments": {
          "owner": "acme",
          "repo": "widgets",
          "labels": ["bug"],
          "per_page": 2
        }
      }
    }
    ```

    Expose the pack as tools with `--packs github` on the local server or `?packs=github` on the remote URL, and the agent calls `github__list-issues` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: List issues (documented response shape, example values). From the bundle's `fixtures/list-issues.json`, not a live call.

```json
{
  "issues": [
    {
      "number": 42,
      "title": "Cart overflows on narrow screens",
      "state": "open",
      "state_reason": null,
      "html_url": "https://github.com/acme/widgets/issues/42",
      "author": "mona-example",
      "labels": ["bug"],
      "assignees": ["hubot-example"],
      "comments": 2,
      "created_at": "2026-10-24T17:11:20Z",
      "updated_at": "2026-10-25T09:02:41Z",
      "closed_at": null,
      "is_pull_request": false
    },
    {
      "number": 41,
      "title": "Typo in README",
      "state": "open",
      "state_reason": null,
      "html_url": "https://github.com/acme/widgets/issues/41",
      "author": "mona-example",
      "labels": [],
      "assignees": [],
      "comments": 0,
      "created_at": "2026-10-24T17:11:20Z",
      "updated_at": "2026-10-25T09:02:41Z",
      "closed_at": null,
      "is_pull_request": true
    }
  ]
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | Not found, or the token cannot see this repository. |
| `connection_unauthorized` | 401 | no | GitHub rejected the token. |
| `rate_limited` | 403, 429 | yes | GitHub rate limit reached; it resets at the time in the x-ratelimit-reset header. |
| `forbidden` | 403 | no | built from the service's response |
| `gone` | 410 | no | Issues are disabled for this repository. |
| `invalid_request` | 422 | no | built from the service's response |

get-issue

**Get issue.** One issue (or pull request) by number with its full Markdown body, labels, assignees, comment count and timestamps. Comments themselves are in list-issue-comments.

Ref `io.b3os/github/get-issue@1.0.0` · effect `read` · connection `token` · MCP tool `github__get-issue`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/github/get-issue@1",
          "input": {"owner": "acme", "repo": "widgets", "number": 42},
          "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/github/get-issue@1",
      input: { owner: "acme", repo: "widgets", number: 42 },
      connections: { token: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    });
    console.log(result.output);
    ```
  **Python**

```python
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/github/get-issue@1",
        "input": {"owner": "acme", "repo": "widgets", "number": 42},
        "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    })
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "github__get-issue",
        "arguments": {"owner": "acme", "repo": "widgets", "number": 42}
      }
    }
    ```

    Expose the pack as tools with `--packs github` on the local server or `?packs=github` on the remote URL, and the agent calls `github__get-issue` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: Get issue (documented response shape, example values). From the bundle's `fixtures/get-issue.json`, not a live call.

```json
{
  "number": 42,
  "title": "Cart overflows on narrow screens",
  "state": "open",
  "state_reason": null,
  "html_url": "https://github.com/acme/widgets/issues/42",
  "author": "mona-example",
  "labels": ["bug"],
  "assignees": ["hubot-example"],
  "comments": 2,
  "created_at": "2026-10-24T17:11:20Z",
  "updated_at": "2026-10-25T09:02:41Z",
  "closed_at": null,
  "is_pull_request": false,
  "body": "Steps to reproduce:\n\n1. Open the cart\n2. Resize to 375px"
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | No issue with this number, or the token cannot see the repository. |
| `connection_unauthorized` | 401 | no | GitHub rejected the token. |
| `rate_limited` | 403, 429 | yes | GitHub rate limit reached; it resets at the time in the x-ratelimit-reset header. |
| `forbidden` | 403 | no | built from the service's response |
| `gone` | 410 | no | Issues are disabled for this repository. |
| `invalid_request` | 422 | no | built from the service's response |

search-issues

**Search issues and pull requests.** Search issues and pull requests across GitHub with GitHub's search syntax, for example repo:acme/widgets is:open label:bug or is:pr author:mona review:required. Returns matches with total count; at most 1,000 results are reachable through paging.

Ref `io.b3os/github/search-issues@1.0.0` · effect `read` · connection `token` · MCP tool `github__search-issues`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/github/search-issues@1",
          "input": {
            "query": "repo:acme/widgets is:issue is:open label:bug",
            "per_page": 1
          },
          "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/github/search-issues@1",
      input: {
        query: "repo:acme/widgets is:issue is:open label:bug",
        per_page: 1,
      },
      connections: { token: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    });
    console.log(result.output);
    ```
  **Python**

```python
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/github/search-issues@1",
        "input": {
            "query": "repo:acme/widgets is:issue is:open label:bug",
            "per_page": 1,
        },
        "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    })
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "github__search-issues",
        "arguments": {
          "query": "repo:acme/widgets is:issue is:open label:bug",
          "per_page": 1
        }
      }
    }
    ```

    Expose the pack as tools with `--packs github` on the local server or `?packs=github` on the remote URL, and the agent calls `github__search-issues` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: Search issues and pull requests (documented response shape, example values). From the bundle's `fixtures/search-issues.json`, not a live call.

```json
{
  "total_count": 9,
  "incomplete_results": false,
  "items": [
    {
      "number": 42,
      "title": "Cart overflows on narrow screens",
      "state": "open",
      "state_reason": null,
      "html_url": "https://github.com/acme/widgets/issues/42",
      "author": "mona-example",
      "labels": ["bug"],
      "assignees": ["hubot-example"],
      "comments": 2,
      "created_at": "2026-10-24T17:11:20Z",
      "updated_at": "2026-10-25T09:02:41Z",
      "closed_at": null,
      "is_pull_request": false
    }
  ]
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | Not found, or the token cannot see this repository. |
| `connection_unauthorized` | 401 | no | GitHub rejected the token. |
| `rate_limited` | 403, 429 | yes | GitHub rate limit reached; it resets at the time in the x-ratelimit-reset header. |
| `forbidden` | 403 | no | built from the service's response |
| `gone` | 410 | no | Issues are disabled for this repository. |
| `invalid_request` | 422 | no | built from the service's response |

create-issue

**Create issue.** Open a new issue with a title, optional Markdown body, labels and assignees. Returns its number and URL. Not idempotent: calling it twice opens two issues.

Ref `io.b3os/github/create-issue@1.0.0` · effect `write` · connection `token` · MCP tool `github__create-issue`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Idempotency-Key: 7c1e2f6a-1d43-4a8e-b2a5-3f0c9d5e8b11" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/github/create-issue@1",
          "input": {
            "owner": "acme",
            "repo": "widgets",
            "title": "Flaky test in checkout flow",
            "body": "Seen three times this week on main.",
            "labels": ["bug"]
          },
          "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/github/create-issue@1",
      input: {
        owner: "acme",
        repo: "widgets",
        title: "Flaky test in checkout flow",
        body: "Seen three times this week on main.",
        labels: ["bug"],
      },
      connections: { token: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    }, { idempotencyKey: crypto.randomUUID() });
    console.log(result.output);
    ```
  **Python**

```python
    import uuid
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/github/create-issue@1",
        "input": {
            "owner": "acme",
            "repo": "widgets",
            "title": "Flaky test in checkout flow",
            "body": "Seen three times this week on main.",
            "labels": ["bug"],
        },
        "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    }, idempotency_key=str(uuid.uuid4()))
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "github__create-issue",
        "arguments": {
          "owner": "acme",
          "repo": "widgets",
          "title": "Flaky test in checkout flow",
          "body": "Seen three times this week on main.",
          "labels": ["bug"]
        }
      }
    }
    ```

    Expose the pack as tools with `--packs github` on the local server or `?packs=github` on the remote URL, and the agent calls `github__create-issue` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: Create issue (documented response shape, example values). From the bundle's `fixtures/create-issue.json`, not a live call.

```json
{
  "number": 43,
  "html_url": "https://github.com/acme/widgets/issues/43",
  "title": "Flaky test in checkout flow",
  "state": "open",
  "created_at": "2026-10-25T18:04:11Z"
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | Not found, or the token cannot see this repository. |
| `connection_unauthorized` | 401 | no | GitHub rejected the token. |
| `rate_limited` | 403, 429 | yes | GitHub rate limit reached; it resets at the time in the x-ratelimit-reset header. |
| `forbidden` | 403 | no | built from the service's response |
| `gone` | 410 | no | Issues are disabled for this repository. |
| `invalid_request` | 422 | no | built from the service's response |

add-issue-comment

**Comment on issue or pull request.** Add a Markdown comment to an issue or pull request (the conversation tab, not a code review comment). Returns the comment id and URL. Not idempotent.

Ref `io.b3os/github/add-issue-comment@1.0.0` · effect `write` · connection `token` · MCP tool `github__add-issue-comment`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Idempotency-Key: 7c1e2f6a-1d43-4a8e-b2a5-3f0c9d5e8b11" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/github/add-issue-comment@1",
          "input": {
            "owner": "acme",
            "repo": "widgets",
            "number": 42,
            "body": "Reproduced on main at 3f2c1d0; the flex container overflows below 400px."
          },
          "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/github/add-issue-comment@1",
      input: {
        owner: "acme",
        repo: "widgets",
        number: 42,
        body: "Reproduced on main at 3f2c1d0; the flex container overflows below 400px.",
      },
      connections: { token: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    }, { idempotencyKey: crypto.randomUUID() });
    console.log(result.output);
    ```
  **Python**

```python
    import uuid
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/github/add-issue-comment@1",
        "input": {
            "owner": "acme",
            "repo": "widgets",
            "number": 42,
            "body": "Reproduced on main at 3f2c1d0; the flex container overflows below 400px.",
        },
        "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    }, idempotency_key=str(uuid.uuid4()))
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "github__add-issue-comment",
        "arguments": {
          "owner": "acme",
          "repo": "widgets",
          "number": 42,
          "body": "Reproduced on main at 3f2c1d0; the flex container overflows below 400px."
        }
      }
    }
    ```

    Expose the pack as tools with `--packs github` on the local server or `?packs=github` on the remote URL, and the agent calls `github__add-issue-comment` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: Comment on issue or pull request (documented response shape, example values). From the bundle's `fixtures/add-issue-comment.json`, not a live call.

```json
{
  "id": 2412345678,
  "html_url": "https://github.com/acme/widgets/issues/42#issuecomment-2412345678",
  "created_at": "2026-10-25T18:20:00Z"
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | Not found, or the token cannot see this repository. |
| `connection_unauthorized` | 401 | no | GitHub rejected the token. |
| `rate_limited` | 403, 429 | yes | GitHub rate limit reached; it resets at the time in the x-ratelimit-reset header. |
| `forbidden` | 403 | no | built from the service's response |
| `gone` | 410 | no | Issues are disabled for this repository. |
| `invalid_request` | 422 | no | built from the service's response |

list-issue-comments

**List issue comments.** Comments on an issue or pull request conversation, oldest first: author, Markdown body and times. Page through long threads with page.

Ref `io.b3os/github/list-issue-comments@1.0.0` · effect `read` · connection `token` · MCP tool `github__list-issue-comments`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/github/list-issue-comments@1",
          "input": {"owner": "acme", "repo": "widgets", "number": 42},
          "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/github/list-issue-comments@1",
      input: { owner: "acme", repo: "widgets", number: 42 },
      connections: { token: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    });
    console.log(result.output);
    ```
  **Python**

```python
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/github/list-issue-comments@1",
        "input": {"owner": "acme", "repo": "widgets", "number": 42},
        "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    })
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "github__list-issue-comments",
        "arguments": {"owner": "acme", "repo": "widgets", "number": 42}
      }
    }
    ```

    Expose the pack as tools with `--packs github` on the local server or `?packs=github` on the remote URL, and the agent calls `github__list-issue-comments` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: List issue comments (documented response shape, example values). From the bundle's `fixtures/list-issue-comments.json`, not a live call.

```json
{
  "comments": [
    {
      "id": 2412345678,
      "author": "hubot-example",
      "body": "Reproduced on main at 3f2c1d0; the flex container overflows below 400px.",
      "html_url": "https://github.com/acme/widgets/issues/42#issuecomment-2412345678",
      "created_at": "2026-10-25T18:20:00Z",
      "author_association": "MEMBER"
    }
  ]
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | Not found, or the token cannot see this repository. |
| `connection_unauthorized` | 401 | no | GitHub rejected the token. |
| `rate_limited` | 403, 429 | yes | GitHub rate limit reached; it resets at the time in the x-ratelimit-reset header. |
| `forbidden` | 403 | no | built from the service's response |
| `gone` | 410 | no | Issues are disabled for this repository. |
| `invalid_request` | 422 | no | built from the service's response |

close-issue

**Close issue.** Close an issue as completed or as not planned. Closing an already closed issue succeeds and changes nothing.

Ref `io.b3os/github/close-issue@1.0.0` · effect `write` · connection `token` · MCP tool `github__close-issue`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Idempotency-Key: 7c1e2f6a-1d43-4a8e-b2a5-3f0c9d5e8b11" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/github/close-issue@1",
          "input": {
            "owner": "acme",
            "repo": "widgets",
            "number": 42,
            "reason": "completed"
          },
          "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/github/close-issue@1",
      input: { owner: "acme", repo: "widgets", number: 42, reason: "completed" },
      connections: { token: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    }, { idempotencyKey: crypto.randomUUID() });
    console.log(result.output);
    ```
  **Python**

```python
    import uuid
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/github/close-issue@1",
        "input": {
            "owner": "acme",
            "repo": "widgets",
            "number": 42,
            "reason": "completed",
        },
        "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    }, idempotency_key=str(uuid.uuid4()))
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "github__close-issue",
        "arguments": {
          "owner": "acme",
          "repo": "widgets",
          "number": 42,
          "reason": "completed"
        }
      }
    }
    ```

    Expose the pack as tools with `--packs github` on the local server or `?packs=github` on the remote URL, and the agent calls `github__close-issue` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: Close issue (documented response shape, example values). From the bundle's `fixtures/close-issue.json`, not a live call.

```json
{
  "number": 42,
  "state": "closed",
  "state_reason": "completed",
  "closed_at": "2026-10-26T08:00:00Z",
  "html_url": "https://github.com/acme/widgets/issues/42"
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | Not found, or the token cannot see this repository. |
| `connection_unauthorized` | 401 | no | GitHub rejected the token. |
| `rate_limited` | 403, 429 | yes | GitHub rate limit reached; it resets at the time in the x-ratelimit-reset header. |
| `forbidden` | 403 | no | built from the service's response |
| `gone` | 410 | no | Issues are disabled for this repository. |
| `invalid_request` | 422 | no | built from the service's response |

add-labels

**Add labels.** Add labels to an issue or pull request, keeping its existing ones. Labels that do not exist yet in the repository are created. Returns the full label list after the change.

Ref `io.b3os/github/add-labels@1.0.0` · effect `write` · connection `token` · MCP tool `github__add-labels`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Idempotency-Key: 7c1e2f6a-1d43-4a8e-b2a5-3f0c9d5e8b11" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/github/add-labels@1",
          "input": {
            "owner": "acme",
            "repo": "widgets",
            "number": 42,
            "labels": ["needs-triage"]
          },
          "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/github/add-labels@1",
      input: {
        owner: "acme",
        repo: "widgets",
        number: 42,
        labels: ["needs-triage"],
      },
      connections: { token: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    }, { idempotencyKey: crypto.randomUUID() });
    console.log(result.output);
    ```
  **Python**

```python
    import uuid
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/github/add-labels@1",
        "input": {
            "owner": "acme",
            "repo": "widgets",
            "number": 42,
            "labels": ["needs-triage"],
        },
        "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    }, idempotency_key=str(uuid.uuid4()))
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "github__add-labels",
        "arguments": {
          "owner": "acme",
          "repo": "widgets",
          "number": 42,
          "labels": ["needs-triage"]
        }
      }
    }
    ```

    Expose the pack as tools with `--packs github` on the local server or `?packs=github` on the remote URL, and the agent calls `github__add-labels` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: Add labels (documented response shape, example values). From the bundle's `fixtures/add-labels.json`, not a live call.

```json
{"labels": ["bug", "needs-triage"]}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | Not found, or the token cannot see this repository. |
| `connection_unauthorized` | 401 | no | GitHub rejected the token. |
| `rate_limited` | 403, 429 | yes | GitHub rate limit reached; it resets at the time in the x-ratelimit-reset header. |
| `forbidden` | 403 | no | built from the service's response |
| `gone` | 410 | no | Issues are disabled for this repository. |
| `invalid_request` | 422 | no | built from the service's response |

list-pull-requests

**List pull requests.** Pull requests in a repository by state, base branch or head (user:branch), sorted by creation, update or popularity. Returns title, author, branches, head commit, draft flag, labels and requested reviewers.

Ref `io.b3os/github/list-pull-requests@1.0.0` · effect `read` · connection `token` · MCP tool `github__list-pull-requests`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/github/list-pull-requests@1",
          "input": {"owner": "acme", "repo": "widgets", "per_page": 1},
          "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/github/list-pull-requests@1",
      input: { owner: "acme", repo: "widgets", per_page: 1 },
      connections: { token: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    });
    console.log(result.output);
    ```
  **Python**

```python
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/github/list-pull-requests@1",
        "input": {"owner": "acme", "repo": "widgets", "per_page": 1},
        "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    })
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "github__list-pull-requests",
        "arguments": {"owner": "acme", "repo": "widgets", "per_page": 1}
      }
    }
    ```

    Expose the pack as tools with `--packs github` on the local server or `?packs=github` on the remote URL, and the agent calls `github__list-pull-requests` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: List pull requests (documented response shape, example values). From the bundle's `fixtures/list-pull-requests.json`, not a live call.

```json
{
  "pull_requests": [
    {
      "number": 43,
      "title": "Fix cart overflow on narrow screens",
      "state": "open",
      "draft": false,
      "html_url": "https://github.com/acme/widgets/pull/43",
      "author": "mona-example",
      "head": "fix-cart",
      "base": "main",
      "head_sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
      "labels": ["bug"],
      "requested_reviewers": ["hubot-example"],
      "created_at": "2026-10-25T19:00:00Z",
      "updated_at": "2026-10-25T20:30:00Z",
      "merged_at": null
    }
  ]
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | Not found, or the token cannot see this repository. |
| `connection_unauthorized` | 401 | no | GitHub rejected the token. |
| `rate_limited` | 403, 429 | yes | GitHub rate limit reached; it resets at the time in the x-ratelimit-reset header. |
| `forbidden` | 403 | no | built from the service's response |
| `gone` | 410 | no | Issues are disabled for this repository. |
| `invalid_request` | 422 | no | built from the service's response |

get-pull-request

**Get pull request.** One pull request with its description and merge state: merged, mergeable and mergeable_state (clean, blocked, dirty, ...), commit count, additions, deletions and changed files. mergeable can be null while GitHub is still computing it; ask again a moment later.

Ref `io.b3os/github/get-pull-request@1.0.0` · effect `read` · connection `token` · MCP tool `github__get-pull-request`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/github/get-pull-request@1",
          "input": {"owner": "acme", "repo": "widgets", "number": 43},
          "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/github/get-pull-request@1",
      input: { owner: "acme", repo: "widgets", number: 43 },
      connections: { token: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    });
    console.log(result.output);
    ```
  **Python**

```python
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/github/get-pull-request@1",
        "input": {"owner": "acme", "repo": "widgets", "number": 43},
        "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    })
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "github__get-pull-request",
        "arguments": {"owner": "acme", "repo": "widgets", "number": 43}
      }
    }
    ```

    Expose the pack as tools with `--packs github` on the local server or `?packs=github` on the remote URL, and the agent calls `github__get-pull-request` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: Get pull request (documented response shape, example values). From the bundle's `fixtures/get-pull-request.json`, not a live call.

```json
{
  "number": 43,
  "title": "Fix cart overflow on narrow screens",
  "state": "open",
  "draft": false,
  "html_url": "https://github.com/acme/widgets/pull/43",
  "author": "mona-example",
  "head": "fix-cart",
  "base": "main",
  "head_sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
  "labels": ["bug"],
  "requested_reviewers": ["hubot-example"],
  "created_at": "2026-10-25T19:00:00Z",
  "updated_at": "2026-10-25T20:30:00Z",
  "merged_at": null,
  "body": "Fixes #42.",
  "merged": false,
  "mergeable": true,
  "mergeable_state": "blocked",
  "commits": 2,
  "additions": 18,
  "deletions": 4,
  "changed_files": 3
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | No pull request with this number, or the token cannot see the repository. |
| `connection_unauthorized` | 401 | no | GitHub rejected the token. |
| `rate_limited` | 403, 429 | yes | GitHub rate limit reached; it resets at the time in the x-ratelimit-reset header. |
| `forbidden` | 403 | no | built from the service's response |
| `gone` | 410 | no | Issues are disabled for this repository. |
| `invalid_request` | 422 | no | built from the service's response |

get-file-contents

**Get file contents.** The raw text of one file at a branch, tag or commit (default branch when ref is omitted), for example README.md or .github/workflows/ci.yml. Directories, symlinks and submodules come back as GitHub's JSON description instead. Very large files can exceed the runtime's response size limit.

Ref `io.b3os/github/get-file-contents@1.0.0` · effect `read` · connection `token` · MCP tool `github__get-file-contents`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/github/get-file-contents@1",
          "input": {
            "owner": "acme",
            "repo": "widgets",
            "path": ".github/workflows/ci.yml",
            "ref": "main"
          },
          "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/github/get-file-contents@1",
      input: {
        owner: "acme",
        repo: "widgets",
        path: ".github/workflows/ci.yml",
        ref: "main",
      },
      connections: { token: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    });
    console.log(result.output);
    ```
  **Python**

```python
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/github/get-file-contents@1",
        "input": {
            "owner": "acme",
            "repo": "widgets",
            "path": ".github/workflows/ci.yml",
            "ref": "main",
        },
        "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    })
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "github__get-file-contents",
        "arguments": {
          "owner": "acme",
          "repo": "widgets",
          "path": ".github/workflows/ci.yml",
          "ref": "main"
        }
      }
    }
    ```

    Expose the pack as tools with `--packs github` on the local server or `?packs=github` on the remote URL, and the agent calls `github__get-file-contents` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: Get file contents (documented response shape, example values). From the bundle's `fixtures/get-file-contents.json`, not a live call.

```json
{
  "path": ".github/workflows/ci.yml",
  "ref": "main",
  "content": "name: CI\non: [push, pull_request]\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - run: npm ci && npm test\n"
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | No file at this path and ref, or the token cannot see the repository. |
| `connection_unauthorized` | 401 | no | GitHub rejected the token. |
| `rate_limited` | 403, 429 | yes | GitHub rate limit reached; it resets at the time in the x-ratelimit-reset header. |
| `forbidden` | 403 | no | built from the service's response |
| `gone` | 410 | no | Issues are disabled for this repository. |
| `invalid_request` | 422 | no | built from the service's response |

list-commits

**List commits.** Commits on a branch or under a path, newest first, optionally by author or within a time window: SHA, first line and full message, author, date and URL.

Ref `io.b3os/github/list-commits@1.0.0` · effect `read` · connection `token` · MCP tool `github__list-commits`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/github/list-commits@1",
          "input": {"owner": "acme", "repo": "widgets", "sha": "main", "per_page": 1},
          "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/github/list-commits@1",
      input: { owner: "acme", repo: "widgets", sha: "main", per_page: 1 },
      connections: { token: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    });
    console.log(result.output);
    ```
  **Python**

```python
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/github/list-commits@1",
        "input": {
            "owner": "acme",
            "repo": "widgets",
            "sha": "main",
            "per_page": 1,
        },
        "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    })
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "github__list-commits",
        "arguments": {"owner": "acme", "repo": "widgets", "sha": "main", "per_page": 1}
      }
    }
    ```

    Expose the pack as tools with `--packs github` on the local server or `?packs=github` on the remote URL, and the agent calls `github__list-commits` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: List commits (documented response shape, example values). From the bundle's `fixtures/list-commits.json`, not a live call.

```json
{
  "commits": [
    {
      "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
      "message": "Fix cart overflow on narrow screens\n\nFixes #42.",
      "author_name": "Mona Example",
      "author_login": "mona-example",
      "date": "2026-10-25T20:11:09Z",
      "html_url": "https://github.com/acme/widgets/commit/6dcb09b5b57875f334f61aebed695e2e4193db5e"
    }
  ]
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | Not found, or the token cannot see this repository. |
| `connection_unauthorized` | 401 | no | GitHub rejected the token. |
| `rate_limited` | 403, 429 | yes | GitHub rate limit reached; it resets at the time in the x-ratelimit-reset header. |
| `forbidden` | 403 | no | built from the service's response |
| `gone` | 410 | no | Issues are disabled for this repository. |
| `invalid_request` | 422 | no | built from the service's response |

get-latest-release

**Get latest release.** The latest published release (not a draft or prerelease): tag, name, Markdown notes, publish time and downloadable assets with sizes and download counts. not_found when the repository has no releases.

Ref `io.b3os/github/get-latest-release@1.0.0` · effect `read` · connection `token` · MCP tool `github__get-latest-release`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/github/get-latest-release@1",
          "input": {"owner": "acme", "repo": "widgets"},
          "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/github/get-latest-release@1",
      input: { owner: "acme", repo: "widgets" },
      connections: { token: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    });
    console.log(result.output);
    ```
  **Python**

```python
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/github/get-latest-release@1",
        "input": {"owner": "acme", "repo": "widgets"},
        "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    })
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "github__get-latest-release",
        "arguments": {"owner": "acme", "repo": "widgets"}
      }
    }
    ```

    Expose the pack as tools with `--packs github` on the local server or `?packs=github` on the remote URL, and the agent calls `github__get-latest-release` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: Get latest release (documented response shape, example values). From the bundle's `fixtures/get-latest-release.json`, not a live call.

```json
{
  "tag_name": "v2.4.0",
  "name": "v2.4.0",
  "body": "## What's changed\n\n- Cart: fix overflow on narrow screens (#43)\n- Buttons: new compact size",
  "published_at": "2026-10-20T15:04:12Z",
  "html_url": "https://github.com/acme/widgets/releases/tag/v2.4.0",
  "assets": [
    {
      "name": "widgets-2.4.0.tgz",
      "size": 184320,
      "download_count": 311,
      "url": "https://github.com/acme/widgets/releases/download/v2.4.0/widgets-2.4.0.tgz"
    }
  ]
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | The repository has no published release, or the token cannot see it. |
| `connection_unauthorized` | 401 | no | GitHub rejected the token. |
| `rate_limited` | 403, 429 | yes | GitHub rate limit reached; it resets at the time in the x-ratelimit-reset header. |
| `forbidden` | 403 | no | built from the service's response |
| `gone` | 410 | no | Issues are disabled for this repository. |
| `invalid_request` | 422 | no | built from the service's response |

list-workflow-runs

**List Actions runs.** Recent GitHub Actions workflow runs, newest first, filtered by branch, triggering event or status (queued, in_progress, completed, success, failure, ...): workflow name, title, status, conclusion, branch, commit and timing.

Ref `io.b3os/github/list-workflow-runs@1.0.0` · effect `read` · connection `token` · MCP tool `github__list-workflow-runs`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/github/list-workflow-runs@1",
          "input": {"owner": "acme", "repo": "widgets", "per_page": 2},
          "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/github/list-workflow-runs@1",
      input: { owner: "acme", repo: "widgets", per_page: 2 },
      connections: { token: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    });
    console.log(result.output);
    ```
  **Python**

```python
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/github/list-workflow-runs@1",
        "input": {"owner": "acme", "repo": "widgets", "per_page": 2},
        "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    })
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "github__list-workflow-runs",
        "arguments": {"owner": "acme", "repo": "widgets", "per_page": 2}
      }
    }
    ```

    Expose the pack as tools with `--packs github` on the local server or `?packs=github` on the remote URL, and the agent calls `github__list-workflow-runs` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: List Actions runs (documented response shape, example values). From the bundle's `fixtures/list-workflow-runs.json`, not a live call.

```json
{
  "total_count": 2,
  "runs": [
    {
      "id": 11223344556,
      "workflow": "CI",
      "title": "Fix cart overflow on narrow screens",
      "run_number": 812,
      "event": "pull_request",
      "status": "completed",
      "conclusion": "failure",
      "branch": "fix-cart",
      "head_sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
      "html_url": "https://github.com/acme/widgets/actions/runs/11223344556",
      "created_at": "2026-10-25T19:01:00Z",
      "updated_at": "2026-10-25T19:07:31Z"
    },
    {
      "id": 11223344001,
      "workflow": "CI",
      "title": "Buttons: compact size",
      "run_number": 811,
      "event": "push",
      "status": "completed",
      "conclusion": "success",
      "branch": "main",
      "head_sha": "f1e2d3c4b5a6978877665544332211aabbccddee",
      "html_url": "https://github.com/acme/widgets/actions/runs/11223344001",
      "created_at": "2026-10-25T15:40:00Z",
      "updated_at": "2026-10-25T15:46:10Z"
    }
  ]
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | Not found, or the token cannot see this repository. |
| `connection_unauthorized` | 401 | no | GitHub rejected the token. |
| `rate_limited` | 403, 429 | yes | GitHub rate limit reached; it resets at the time in the x-ratelimit-reset header. |
| `forbidden` | 403 | no | built from the service's response |
| `gone` | 410 | no | Issues are disabled for this repository. |
| `invalid_request` | 422 | no | built from the service's response |

create-webhook

**Create repository webhook.** Create a repository webhook that POSTs the chosen events (issues, pull_request, push, ...) as JSON to a URL, signed with a secret (X-Hub-Signature-256). The repo-event trigger calls this for you; use it directly only for webhooks B3OS does not manage. Needs admin:repo_hook (classic) or Webhooks write (fine-grained).

Ref `io.b3os/github/create-webhook@1.0.0` · effect `write` · connection `token` · MCP tool `github__create-webhook`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Idempotency-Key: 7c1e2f6a-1d43-4a8e-b2a5-3f0c9d5e8b11" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/github/create-webhook@1",
          "input": {
            "owner": "acme",
            "repo": "widgets",
            "url": "https://beta-api.b3os.org/v1/actions/hooks/hk_fixture",
            "secret": "fixture-secret-not-a-real-key",
            "events": ["issues", "pull_request"]
          },
          "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/github/create-webhook@1",
      input: {
        owner: "acme",
        repo: "widgets",
        url: "https://beta-api.b3os.org/v1/actions/hooks/hk_fixture",
        secret: "fixture-secret-not-a-real-key",
        events: ["issues", "pull_request"],
      },
      connections: { token: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    }, { idempotencyKey: crypto.randomUUID() });
    console.log(result.output);
    ```
  **Python**

```python
    import uuid
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/github/create-webhook@1",
        "input": {
            "owner": "acme",
            "repo": "widgets",
            "url": "https://beta-api.b3os.org/v1/actions/hooks/hk_fixture",
            "secret": "fixture-secret-not-a-real-key",
            "events": ["issues", "pull_request"],
        },
        "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    }, idempotency_key=str(uuid.uuid4()))
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "github__create-webhook",
        "arguments": {
          "owner": "acme",
          "repo": "widgets",
          "url": "https://beta-api.b3os.org/v1/actions/hooks/hk_fixture",
          "secret": "fixture-secret-not-a-real-key",
          "events": ["issues", "pull_request"]
        }
      }
    }
    ```

    Expose the pack as tools with `--packs github` on the local server or `?packs=github` on the remote URL, and the agent calls `github__create-webhook` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: Create repository webhook (documented response shape, example values). From the bundle's `fixtures/create-webhook.json`, not a live call.

```json
{
  "id": 512345678,
  "events": ["issues", "pull_request"],
  "active": true,
  "created_at": "2026-10-26T12:00:00Z"
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | No such repository, or the token cannot manage its webhooks (needs admin:repo_hook). |
| `connection_unauthorized` | 401 | no | GitHub rejected the token. |
| `rate_limited` | 403, 429 | yes | GitHub rate limit reached; it resets at the time in the x-ratelimit-reset header. |
| `forbidden` | 403 | no | built from the service's response |
| `gone` | 410 | no | Issues are disabled for this repository. |
| `invalid_request` | 422 | no | built from the service's response |

delete-webhook

**Delete repository webhook.** Delete a repository webhook by id (from create-webhook). The repo-event trigger calls this when its subscription is deleted.

Ref `io.b3os/github/delete-webhook@1.0.0` · effect `irreversible` · connection `token` · MCP tool `github__delete-webhook`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Idempotency-Key: 7c1e2f6a-1d43-4a8e-b2a5-3f0c9d5e8b11" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/github/delete-webhook@1",
          "input": {"owner": "acme", "repo": "widgets", "hook_id": 512345678},
          "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/github/delete-webhook@1",
      input: { owner: "acme", repo: "widgets", hook_id: 512345678 },
      connections: { token: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    }, { idempotencyKey: crypto.randomUUID() });
    console.log(result.output);
    ```
  **Python**

```python
    import uuid
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/github/delete-webhook@1",
        "input": {"owner": "acme", "repo": "widgets", "hook_id": 512345678},
        "connections": {"token": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    }, idempotency_key=str(uuid.uuid4()))
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "github__delete-webhook",
        "arguments": {"owner": "acme", "repo": "widgets", "hook_id": 512345678}
      }
    }
    ```

    Expose the pack as tools with `--packs github` on the local server or `?packs=github` on the remote URL, and the agent calls `github__delete-webhook` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: Delete repository webhook (documented response shape, example values). From the bundle's `fixtures/delete-webhook.json`, not a live call.

```json
{"deleted": true, "hook_id": 512345678}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | No such webhook in this repository. |
| `connection_unauthorized` | 401 | no | GitHub rejected the token. |
| `rate_limited` | 403, 429 | yes | GitHub rate limit reached; it resets at the time in the x-ratelimit-reset header. |
| `forbidden` | 403 | no | built from the service's response |
| `gone` | 410 | no | Issues are disabled for this repository. |
| `invalid_request` | 422 | no | built from the service's response |

## Triggers

repo-event (trigger)

**Repository event.** Fires on GitHub repository events as they happen (issues opened or closed, pull requests, pushes, releases, comments, ...): B3OS creates a webhook on the repository with a generated secret, verifies every delivery's X-Hub-Signature-256, and removes the webhook when the subscription is deleted. The event carries the event type, action, repository, sender and GitHub's full payload. Needs a token that can manage webhooks (admin:repo_hook).

Ref `io.b3os/github/repo-event@1.0.0` · webhook, signature scheme `github` with a secret B3OS generates · connection `token`

| `with` key | Required | Description |
|---|---|---|
| `owner` | yes | Repository owner. |
| `repo` | yes | Repository name. |
| `events` | no | GitHub event names to receive, or * for every event. Default `["issues","pull_request","push"]`. |

In a routine:

```yaml
triggers:
  - type: action
    id: repo_event
    uses: io.b3os/github/repo-event@1
    with:
      owner: acme
      repo: widgets
      events:
        - issues
        - pull_request
    connections:
      token: conn_01m5t6cbgdjs1ketgcqwa2sne8
```

Event, labeled `fixture`: the recorded delivery in fixtures/repo-event-issue-opened.json (Repository event (delivery signed with a throwaway test secret)). A routine reads it as `trigger.data`; its fields are `type`, `action`, `repository`, `sender`, `delivery`, `payload`.

```json
{
  "type": "issues",
  "action": "opened",
  "repository": "acme/widgets",
  "sender": "mona",
  "delivery": "72d3162e-cc78-11e3-81ab-4c9367dc0958",
  "payload": {
    "action": "opened",
    "issue": {
      "id": 3051234542,
      "node_id": "I_kwDOAAAAAM6AAAAA",
      "url": "https://api.github.com/repos/acme/widgets/issues/42",
      "html_url": "https://github.com/acme/widgets/issues/42",
      "number": 42,
      "state": "open",
      "state_reason": null,
      "title": "Cart overflows on narrow screens",
      "body": "Steps to reproduce:\n\n1. Open the cart\n2. Resize to 375px",
      "user": {
        "login": "mona-example",
        "id": 1000002,
        "node_id": "MDQ6VXNlcjE=",
        "avatar_url": "https://avatars.githubusercontent.com/u/1000002?v=4",
        "html_url": "https://github.com/mona-example",
        "type": "User",
        "site_admin": false
      },
      "labels": [
        {
          "id": 208045946,
          "node_id": "MDU6TGFiZWwyMDgwNDU5NDY=",
          "name": "bug",
          "color": "d73a4a",
          "default": false,
          "description": null
        }
      ],
      "assignees": [
        {
          "login": "hubot-example",
          "id": 1000003,
          "node_id": "MDQ6VXNlcjE=",
          "avatar_url": "https://avatars.githubusercontent.com/u/1000003?v=4",
          "html_url": "https://github.com/hubot-example",
          "type": "User",
          "site_admin": false
        }
      ],
      "comments": 2,
      "created_at": "2026-10-24T17:11:20Z",
      "updated_at": "2026-10-25T09:02:41Z",
      "closed_at": null,
      "author_association": "CONTRIBUTOR"
    },
    "repository": {
      "id": 1296269,
      "name": "widgets",
      "full_name": "acme/widgets",
      "private": false
    },
    "sender": {"login": "mona", "id": 583231, "type": "User"}
  }
}
```

new-issue (trigger)

**New issue.** Fires once for each new issue opened in a repository (pull requests are skipped), optionally only with given labels, checked every 2 minutes by default. The first check records existing issues and emits nothing.

Ref `io.b3os/github/new-issue@1.0.0` · poll of `list-issues`, every 120 s by default (at least 60 s) · connection `token`

| `with` key | Required | Description |
|---|---|---|
| `owner` | yes | Repository owner. |
| `repo` | yes | Repository name. |
| `labels` | no | Only issues with every one of these labels. |

In a routine:

```yaml
triggers:
  - type: action
    id: new_issue
    uses: io.b3os/github/new-issue@1
    with:
      owner: acme
      repo: widgets
    connections:
      token: conn_01m5t6cbgdjs1ketgcqwa2sne8
```

Event, labeled `fixture`: the first item of `list-issues`'s fixture fixtures/list-issues.json (List issues (documented response shape, example values)) through the trigger's event mapping. A routine reads it as `trigger.data`; its fields are `number`, `title`, `state`, `state_reason`, `html_url`, `author`, `labels`, `assignees`, `comments`, `created_at`, `updated_at`, `closed_at`, `is_pull_request`.

```json
{
  "number": 42,
  "title": "Cart overflows on narrow screens",
  "state": "open",
  "state_reason": null,
  "html_url": "https://github.com/acme/widgets/issues/42",
  "author": "mona-example",
  "labels": ["bug"],
  "assignees": ["hubot-example"],
  "comments": 2,
  "created_at": "2026-10-24T17:11:20Z",
  "updated_at": "2026-10-25T09:02:41Z",
  "closed_at": null,
  "is_pull_request": false
}
```