Skip to content

check_run signals are fingerprinted per repo, so a green PR resolves a red default branch #69

Description

@javiertoledo

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:

  1. Break a CI check on the default branch. Confirm a platform issue opens with fingerprint check:<repo_id>:<check name>.
  2. Open any pull request whose branch passes that same named check.
  3. 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-808processOperationalSignal, 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions