Cross-domain PR reviewer assignment for LiskHQ product repos. When an engineer
opens a PR in lisk-backend, lisk-web, lisk-infra, lisk-mobile, or
lisk-contracts, this workflow picks a reviewer based on the engineer's squad
and the repo's domain.
CODEOWNERS routes by file path; team review distributes within a chosen team. Neither can express "given the author's squad, find the right domain specialist." This workflow does that with one shared map.
review-map.yml— squad / specialist / fallback configuration. Single source of truth..github/workflows/auto-assign-reviewer.yml— reusable workflow with the cascade logic..github/workflows/validate-review-map.yml— CI lint that runs on PRs touching the map.examples/workflows/auto-assign-reviewer-caller.yml— caller stub each product repo copies, varying onlydomain:.
Bot / external authors short-circuit before the cascade. If the PR author
is in bot_authors, the workflow requests the repo's bot_pr_owners[<repo>]
(if mapped) and exits. Bots have no squad and don't need round-robin
distribution; a single per-repo owner is enough. If no owner is mapped for
the repo, the workflow exits 0 silently.
Given a human PR author A, repo domain D, author squad S:
- Own-squad specialists. If
S.specialists[D]minusAis non-empty, pickeligible[PR# % len]. - Own-squad equivalent domain. For each
Eindomain_equivalence[D], ifS.specialists[E]minusAis non-empty, pickeligible[PR# % len]. Keeps the review inside the squad when web/mobile are interchangeable. - Own-squad team round-robin. If prior steps produced nothing, pick from
S.membersminusA. In-team review is the priority — only after every teammate is exhausted do we cross squads. Useful when the author is the sole specialist for the domain (e.g., matjazv on a backend or contracts PR inorg) so the PR still gets reviewed by their own team. - Sibling cascade. Walk
sibling_fallback_order. For each sibling squadT(T ≠ S), ifT.specialists[D]minusAis non-empty, pickeligible[PR# % len]. Equivalence is not applied cross-squad. - Soft-fail. Post a PR comment asking for manual assignment. Workflow succeeds (never blocks merge).
PR# % N gives stateless deterministic round-robin. Author exclusion happens
before modulo, so distribution stays balanced.
domain_equivalence:
web: [mobile]
mobile: [web]Web and mobile are treated as interchangeable skills within a squad — when an org engineer opens a mobile PR, org's web specialists handle it (instead of cascading to money). Equivalence is intra-squad only; cross-squad cascade always uses the actual domain.
| Squad | backend | web | infra | mobile | contracts |
|---|---|---|---|---|---|
| platform | sameersubudhi, ishantiw | — | Nazgolze | — | — |
| money | vardan10 | — | — | oskarleonard, Balanced02, eniolam1000752, 5heri | — |
| org | matjazv | mmarinovic, mvuco00, ikem-legend, mislavtomic | — | — | matjazv |
| global | — | — | — | — | ricott1 |
— = no own-squad specialist; falls back to any other team member, then
cascades to siblings if the team has no one else available.
sibling_fallback_order: [money, org, platform, global].
- Open a PR editing
review-map.yml. - The
validate-review-map.ymlCI checks structure and that every GitHub login is anLiskHQorg member. - Squad TL approves their own squad's section.
- Merge.
- All assignment off: set
enabled: falseinreview-map.yml. - One repo off: remove it from
enabled_repos. - Bypass squad cascade for an author: add login to
bot_authors. They get routed tobot_pr_owners[<repo>]if mapped, otherwise skipped. - Stop bot routing in a repo: remove the repo from
bot_pr_owners(bot PRs in that repo will then be skipped).
All take effect on the next PR open with no other action.
bot_pr_owners maps repo name → single GitHub login. Designed for PRs where
squad-based routing doesn't apply: dependabot bumps, renovate bumps, claude
PR commits, copilot review bot, plus listed external contributors.
Current mapping:
| Repo | Owner |
|---|---|
lisk-backend |
ishantiw |
lisk-web |
mmarinovic |
lisk-mobile |
5heri |
lisk-infra |
Nazgolze |
lisk-contracts |
matjazv |
The owner is requested verbatim — no round-robin, no domain check, no fallback. If they're unavailable, the comment trail surfaces who the API rejected so a human can re-assign.
The workflow is best-effort. It always exits 0 (so it never blocks merges) even when:
- The author isn't in any squad.
- No eligible reviewer is found (posts a comment instead).
- The reviewer-request API rejects the call (e.g., user isn't a collaborator on the target repo).
The workflow exits non-zero only when the domain: input is invalid (bad
caller config) — that's a configuration error worth surfacing.
pull_request_target on [opened, ready_for_review]. We don't fire on
synchronize (every push) — that's noise; once a reviewer is requested they
stay requested. Drafts are skipped via if: github.event.pull_request.draft == false in the caller.
pull_request_target is used so PRs from forks work — the workflow only
calls the GitHub API and never checks out PR code, so the security
considerations of pull_request_target don't apply here.
- Add the repo to
enabled_reposinreview-map.yml. - If it introduces a new domain (not one of the existing 5), update:
- The
domaininput enum inauto-assign-reviewer.yml. - The allowed-domains list in
validate-review-map.yml. - Each squad's
specialistsmap (add the new domain key where applicable).
- The
- Copy
examples/workflows/auto-assign-reviewer-caller.ymlinto the new repo's.github/workflows/, setdomain:accordingly.
- Add the squad block under
squads:inreview-map.yml. - Add the squad name to
sibling_fallback_order(position determines priority). - List members and (optionally) per-domain specialists.