What happened
check_run webhooks are turned into facility.signal.v1 operational signals fingerprinted per repository and check name, with no branch in the key — services/api/src/github/processor.ts:791-807:
const check = payload.check_run;
const checkName = check?.name ?? "unnamed";
await applyFacilitySignal(db, {
…
fingerprint: `check:${repo.id}:${checkName}`,
…
});
A check run on a pull request branch and the same check on the default branch therefore share one fingerprint. Three things follow.
A green PR resolves a red main. applyFacilitySignal (services/api/src/integrations/signals.ts:43-52) resolves the platform issue on any succeeded or recovered status. So: CI fails on main, a platform issue opens; someone pushes an unrelated PR branch whose build check passes; the same fingerprint resolves, and the main failure disappears from the watchtower surface while main is still broken.
A PR branch failure reads as a production problem. The inverse: a red check on any contributor's branch raises a repo-level platform issue whose title (Check build failure) carries no branch, so it is indistinguishable from the default branch breaking.
Two PRs failing the same check collide. Both map to one fingerprint, so the second failure updates the first's issue and one recovery clears both.
This also contradicts the contract the docs state for these signals (apps/docs/docs/reference/webhooks.md):
Keep fingerprint stable across every status update for the same lifecycle condition. In particular, a recovery must use the fingerprint from the failed signal so Facility can resolve the existing issue instead of addressing a different one.
A PR branch check and a default branch check are not the same lifecycle condition.
How to reproduce
On a repo connected to a Facility instance:
- Break a CI check on the default branch. Confirm a platform issue opens with fingerprint
check:<repo_id>:<check name>.
- Open any pull request whose branch passes that same named check.
- When the PR's check completes successfully, the platform issue from step 1 is resolved — while the default branch is still failing.
deployment_status has the same shape of key but is scoped by environment (processor.ts:782), which is the right granularity for deployments; checks have no equivalent scoping today.
What I propose
Split the two populations at the source:
- Default-branch checks stay operational signals for watchtower, with the branch (or ref) in the fingerprint so distinct refs never share an issue.
- PR-branch checks feed the pull request's own CI state and do not raise a platform issue at all.
This is a prerequisite for surfacing CI on the board rather than an optional cleanup: without it, a red CI would be reported in two places — as a story flag and as a repo-level incident — telling contradictory stories about whether production is healthy.
Evidence
services/api/src/github/processor.ts:752-808 — processOperationalSignal, the fingerprint construction
services/api/src/integrations/signals.ts:43-52 — resolution on any success
services/api/src/github/processor.ts:75-81 — the check_run payload type does not currently carry head_sha or check_suite, so the branch is not even available to the handler yet
apps/docs/docs/reference/webhooks.md — the documented fingerprint contract
Version: 0f646ac. Found while mapping the GitHub integration; not user-reported.
What happened
check_runwebhooks are turned intofacility.signal.v1operational signals fingerprinted per repository and check name, with no branch in the key —services/api/src/github/processor.ts:791-807:A check run on a pull request branch and the same check on the default branch therefore share one fingerprint. Three things follow.
A green PR resolves a red
main.applyFacilitySignal(services/api/src/integrations/signals.ts:43-52) resolves the platform issue on anysucceededorrecoveredstatus. So: CI fails onmain, a platform issue opens; someone pushes an unrelated PR branch whosebuildcheck passes; the same fingerprint resolves, and themainfailure disappears from the watchtower surface whilemainis still broken.A PR branch failure reads as a production problem. The inverse: a red check on any contributor's branch raises a repo-level platform issue whose title (
Check build failure) carries no branch, so it is indistinguishable from the default branch breaking.Two PRs failing the same check collide. Both map to one fingerprint, so the second failure updates the first's issue and one recovery clears both.
This also contradicts the contract the docs state for these signals (
apps/docs/docs/reference/webhooks.md):A PR branch check and a default branch check are not the same lifecycle condition.
How to reproduce
On a repo connected to a Facility instance:
check:<repo_id>:<check name>.deployment_statushas the same shape of key but is scoped byenvironment(processor.ts:782), which is the right granularity for deployments; checks have no equivalent scoping today.What I propose
Split the two populations at the source:
This is a prerequisite for surfacing CI on the board rather than an optional cleanup: without it, a red CI would be reported in two places — as a story flag and as a repo-level incident — telling contradictory stories about whether production is healthy.
Evidence
services/api/src/github/processor.ts:752-808—processOperationalSignal, the fingerprint constructionservices/api/src/integrations/signals.ts:43-52— resolution on any successservices/api/src/github/processor.ts:75-81— thecheck_runpayload type does not currently carryhead_shaorcheck_suite, so the branch is not even available to the handler yetapps/docs/docs/reference/webhooks.md— the documented fingerprint contractVersion:
0f646ac. Found while mapping the GitHub integration; not user-reported.