---
title: "Label bot"
description: "A webhook receiver that labels new threads and closes them from a comment command."
canonical_url: "https://forges.link/examples/label-bot"
---
# Label bot

> A webhook receiver that labels new threads and closes them from a comment command.

`label-bot` is a webhook receiver that works with any forge that you configure. You can [view the source](https://github.com/danielroe/forges/tree/main/examples/label-bot) on GitHub. The bot does two things:

- When an issue or pull request opens with a title that matches a pattern, the bot adds a label and posts a comment.
- When a comment contains `/close`, the bot closes the thread, as long as the commenter has write access to the repository.

Not every forge can do both. The bot calls `provider.can()` before each write, so on a forge that can't add labels, such as Bitbucket or Tangled, it posts the comment and reports the label as `unsupported` in its response.

## Run it

Give the bot the credentials and the webhook secret of your forge, along with the pattern to match and the label to add:

```sh
FORGES_GITHUB_TOKEN=ghp_... \
FORGES_GITHUB_WEBHOOK_SECRET=... \
LABEL_BOT_PATTERN='^(feat|fix)[(:]' \
LABEL_BOT_LABEL=triage \
pnpm --filter @forges-examples/label-bot start
```

`LABEL_BOT_COMMENT` sets the text of the comment, and `PORT` sets the port, which defaults to `3000`.

Then point the webhook of your forge at `http://<host>:3000/webhooks/<forge>`, for example `/webhooks/github`. If you configure several instances of the same forge, add the host of the instance to the path, as in `/webhooks/gitlab/gitlab.example.com`.

If the signature of a delivery doesn't verify, the bot responds with `401`. For any other delivery, it responds with a JSON object that has the `forge` and an `actions` list of the actions that it took.

The handler in `src/handler.ts` is a `(request: Request) => Promise<Response>` function, so it runs on any runtime that supports the Fetch API. `src/server.ts` serves it with `node:http`.

## Collaborator checks

Before the bot closes a thread, it calls `repos.permissionFor()` to read the role of the commenter. If the forge has no collaborator API, the bot reports `unsupported` and leaves the thread open.

## What it shows

The example demonstrates three things:

- [Receiving webhooks](https://forges.link/guides/webhooks) for several forges with one handler.
- [Checking capabilities](https://forges.link/guides/capabilities) before each write.
- Signing deliveries in tests with `signDelivery()`.

## Source

<example-files example="label-bot">

- `src/`
  - [`bot.ts`](https://github.com/danielroe/forges/blob/main/examples/label-bot/src/bot.ts)
  - [`handler.ts`](https://github.com/danielroe/forges/blob/main/examples/label-bot/src/handler.ts)
  - [`server.ts`](https://github.com/danielroe/forges/blob/main/examples/label-bot/src/server.ts)
- `test/`
  - [`bot.test.ts`](https://github.com/danielroe/forges/blob/main/examples/label-bot/test/bot.test.ts)
- [`package.json`](https://github.com/danielroe/forges/blob/main/examples/label-bot/package.json)
- [`README.md`](https://github.com/danielroe/forges/blob/main/examples/label-bot/README.md)
- [`tsconfig.json`](https://github.com/danielroe/forges/blob/main/examples/label-bot/tsconfig.json)

</example-files>

## Run the tests

```sh
pnpm --filter @forges-examples/label-bot test
```

The tests sign deliveries with `signDelivery()` from `forges/testing` and serve responses from the fixtures in `test/fixtures/github/`, so they work without a network connection.


## Sitemap

See the full [sitemap](https://forges.link/sitemap.md) for all pages.
