Skip to content

[bug] 2-4 "PAGS is live" notifications per push — the deploy watcher polls every workflow and calls a green CI run a deploy #359

Description

@serge-ivo

Reported symptom

"why do I get so many notifications that PAGS is live?"

That string is lib/deploy-watch.ts:90`${repoName} is live.` — the body of the ✅ deploy
notification. The user is getting several per push, and some ❌ failure notifications for builds that
did not fail.

Cause: the watcher asks GitHub for "the newest run" and this repo has seven workflows

runDeployWatch polls one endpoint per repo (lib/deploy-watch.ts:133):

const res = await fetchWorkflowRuns(repo.github_repo, token, { perPage: 1 });
const raw  = "runs" in res ? res.runs[0] : undefined;

and fetchWorkflowRuns (lib/github-actions.ts:28-44) builds its query from per_page and page
only
:

const qs = `per_page=${perPage}${page > 1 ? `&page=${page}` : ""}`;
fetch(`https://github.com/ghapi/repos/${repo}/actions/runs?${qs}`)

No workflow_id, no branch, no event, no status. So runs[0] is the newest run across every
workflow in the repo
. ProAgentStore/platform has seven:

ci.yml  deploy-api.yml  deploy-host.yml  deploy-mcp.yml
deploy-testflight.yml  publish-mcp-registry.yml  publish-npm.yml

One push to main starts two to four of them. They finish at different times, so the per-minute cron
sees a different newest run id on successive sweeps, and decideDeployNotification fires on each
one — its only dedup is lastNotifiedRunId === run.id (:79), a single watermark column
(coding_repos.last_deploy_run_id) against an interleaved stream of runs from unrelated workflows.
It can never settle.

Real runs from this repo, same push, two workflows each:

b1266cf  CI              success
b1266cf  Deploy API Wor…  success
31e41cc  CI              success
31e41cc  Deploy Host Wo…  success

≈2–4 notifications per push. The session before this filed ~20 pushes in three hours.

Three separate wrongnesses, not one

1. CI is not a deploy. A green ci.yml run produces "✅ Deployed #412 — PAGS is live." Nothing
was deployed and nothing went live; typecheck passed. This is the notification claiming an outcome it
has no evidence for — the same honesty rule the platform applies to agents.

2. A cancelled run reports as a build failure. ok = run.conclusion === "success" (:84), so
anything else takes the ❌ branch: "❌ Build failed #N — PAGS — cancelled." Superseded runs are
cancelled routinely by concurrency groups (there are two in the recent list), so pushing twice in
quick succession sends a failure alarm for a build that was merely replaced.

3. The run number is per-workflow. #412 from CI and #88 from Deploy API arrive minutes apart
for the same commit, so the number carries no usable meaning across notifications.

No dedup anywhere downstream

notifyUser (routes/push.ts:196-206) does two things with no coalescing, no rate limit and no
recent-duplicate check:

  • createNotification(...) — the in-app list keeps every copy;
  • sendPushToUser(..., { tag: type })tag is the literal string "deploy", so the OS tray
    visually collapses them to one, but each still fires its alert. That is why it feels like far more
    notifications than the tray shows: the buzzes are real, the tray is lying by collapsing them.

Fix

The root error is treating any completed workflow run as a deploy that put something live. The
watcher needs to know which workflow is the deploy, and only speak for that one.

Options, in rough order of preference:

  1. Filter the query. fetchWorkflowRuns should accept branch, event and status, and the
    watcher should pass the repo's default branch, event=push, status=completed. That alone kills
    the PR/branch noise and the in-progress churn, but not the CI-vs-deploy confusion, because CI
    also runs on push to main.
  2. Name the deploy workflow. Either let a coding_repos row declare it, or default to matching
    the workflow file/name against a deploy pattern, and query .../actions/workflows/<id>/runs. This
    is what actually fixes it — one workflow, one watermark, one notification.
  3. If more than one workflow legitimately deploys (this repo deploys api, host and mcp
    separately), the watermark has to become per (repo, workflow) rather than the single
    last_deploy_run_id column, and the body should name which one — "api Worker is live", not
    "PAGS is live".

Whatever is chosen, conclusion === "cancelled" should be silent, not a failure alarm.

Worth considering alongside

notifyUser has no duplicate suppression at all, and this is the second notification bug in the same
area (#338, #344, #349 were all deploy/handoff notification defects). A cheap floor — drop an
identical (user, type, title, body) inside some short window — would have bounded this one to a single
buzz instead of eighty, regardless of the watcher's logic.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendBackend / Worker / API workbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions