GitHub connector
Turns pull-request and issue references into a link with a status. Two paths, deliberately separate:
| Input | How it resolves | Confidence |
|---|---|---|
| A link already in the message | direct lookup of that exact artifact | highest — a link is not a guess |
A prose reference ("the PR", PR #482) | search API, biased to the author's most recently updated open work | ≤ 0.95 |
GitHub's own preview shows you a title. This shows you whether the PR is merged, still a draft, or waiting on a review.
What it resolves
| Message | Resolves to |
|---|---|
can someone review the PR? | the author's open PRs, most recently updated first |
PR #482 is ready | that exact pull request |
EVER-123 is blocked | claimed by Jira when its key pattern matches; GitHub answers non-Jira issue refs |
https://github.com/org/repo/pull/482 | that PR, enriched with live status (needs unfurl_links) |
Reference extraction happens in the core, not here — see
the pipeline. A
vague reference arrives with key: None, which is the connector's instruction
to infer from author + recency rather than look something up.
Configuration
[connectors.github]
token_env = "GITHUB_TOKEN" # required — the token lives ONLY in the env var
org = "ever-co" # optional — scopes searches to one org
api_base = "https://api.github.com" # optional — the default
host = "github.com" # optional — the web host claimed for links
# chat user id -> GitHub login
[connectors.github.user_map]
U0123ABC = "octocat"
U0456DEF = "hubot"
| Key | Required | Default | Meaning |
|---|---|---|---|
token_env | ✅ | — | Env var holding the API token |
api_base | — | https://api.github.com | REST API root; trailing slash trimmed |
host | — | github.com | Web host claimed for link enrichment |
org | — | — | Org login; when set, searches carry org:{login} |
[…user_map] | — | empty | Chat user id → GitHub login |
The token
A read token is enough. Classic PAT or fine-grained, with read access to the
repositories you want resolved — Ever Async never writes to GitHub. The REST
API version is pinned (2022-11-28) on every request.
GitHub Enterprise
Set both, because they are different things:
[connectors.github]
token_env = "GITHUB_TOKEN"
api_base = "https://github.acme.internal/api/v3"
host = "github.acme.internal"
api_base is where requests go. host is which URLs this connector claims
during link enrichment. Set only the first and pasted enterprise links are
ignored; set only the second and every lookup goes to github.com.
User mapping
This is the part people forget.
An author with no
user_mapentry resolves to nothing.
That is a normal answer, not an error — the connector returns an empty list, the pipeline moves on, and the message may get a nudge instead of a card. Nothing is logged as a failure, so a half-filled map looks exactly like a quiet connector.
The map is chat-user-id → GitHub login:
[connectors.github.user_map]
U0123ABC = "octocat"
Slack user ids look like U0123ABC — find one by clicking a profile →
Copy member ID.
Post a message with an explicit reference (PR #482). If that resolves and
the PR does not, the map is the problem: keyed references need no author
mapping, inferred ones need it to know whose work to search.
How inference works
For a vague reference, the connector searches:
type:pr author:{login} is:open sort:updated
scoped by org: when configured, taking the 5 most recently updated
candidates. Then:
| Situation | Confidence |
|---|---|
| The reference carried an exact PR number | 0.95 |
| The author has exactly one open PR | 0.85 |
| Several candidates — best by title-token overlap with the message | 0.60 → 0.75 |
Those numbers meet [policy] min_confidence (default 0.7), which is what
decides whether a card is posted publicly. One open PR clears it; a
three-way guess with no keyword overlap does not. See
policy → min_confidence.
Link enrichment
With [policy] unfurl_links = true (the default), URLs already in the message
are handed to every connector. This one claims:
https://{host}/{owner}/{repo}/pull/{number}
https://{host}/{owner}/{repo}/issues/{number}
Host comparison is normalized — a leading www., a default port, and letter
case are all ignored, so https://GitHub.com/... and
https://github.com:443/... are the same host. A trailing slash, a query
string or a fragment is fine. A repo root, a non-numeric id, or any other host
is not claimed — no item, no error, and the URL is left for whichever
connector owns it.
Health check
everasync doctor
Verifies the token reaches the API. A failure here is almost always an expired token or a fine-grained PAT that was never granted the repositories it is being asked about.
Behaviour and limits
- 6-second timeout per resolve. A slow GitHub never delays a nudge; the
call is abandoned and counted as
ever_async_connector_calls_total{connector="github",outcome="timeout"}. - Read-only. No writes, ever.
- Top three items overall. Results from all connectors are merged, sorted by confidence, and truncated to three.
- An error is not a failure of the message. A broken connector contributes nothing and the pipeline continues.