Skip to content

[bug] Deploy notification doesn't deep-link — the target is cross-origin, so client.navigate silently fails and only focuses the tab #338

Closed

Description

@serge-ivo

The report

I got this notification, but when I click on it it doesn't deep link:
"fws/platform — failure. Open the run to see why."

Cause: the URL is cross-origin, and client.navigate cannot go there

lib/deploy-watch.ts:64 sets the notification's click target to the GitHub Actions run URL:

body: ok ? `${repoName} is live.` : `${repoName} — ${run.conclusion || "failed"}. Open the run to see why.`,
url: run.url,      // https://github.com/<owner>/<repo>/actions/runs/<id>

store/sw.js:51:

const target = (event.notification.data && event.notification.data.url) || "/console/";
for (const client of clientList) {
    if (client.url.includes("/console") && "focus" in client) {
        try { await client.navigate(target); }
        catch (_e) { /* navigation may be blocked cross-origin; focus anyway */ }
        return client.focus();          // ← happens either way
    }
}
if (self.clients.openWindow) return self.clients.openWindow(target);

WindowClient.navigate() is same-origin only by spec. Navigating a proagentstore.online
tab to github.com rejects, the catch swallows it, and focus() runs regardless. With a
console tab open, clicking the notification just brings that tab forward and nothing else
happens.

The comment shows the failure was anticipated — but "focus anyway" is not a fallback, it is a
no-op that looks identical to a broken notification.

It also makes the bug intermittent: with no console tab open, openWindow(target) is reached
and cross-origin is allowed there, so the GitHub page opens. Same click, different outcome,
depending on whether the app is already open — which is precisely when a user is most likely to
click it.

And it links to the wrong place anyway

"Open the run" should open the run in the product. RunDetail exists —
App.tsx:52, instances/:id/tasks/:taskId — and is where the objective, steps, outcome and
timings live. The notification instead sends the user to GitHub Actions, which shows the CI job but
not the agent run that caused it.

Fix

  1. Send a same-origin deep link — the console run route — so client.navigate succeeds and an
    open tab actually moves. Keep the GitHub URL as a link inside that page, where it belongs.
  2. Make the fallback real. If navigate throws, fall through to openWindow(target) instead of
    focusing a tab that did not move. As written, the only branch that can handle a cross-origin
    target is the one taken when the app is closed.
  3. While there: notificationclick matches on client.url.includes("/console"), so a user sitting
    on /usage or /preferences is matched and silently navigated elsewhere; and any non-console
    tab is ignored entirely. Worth confirming that is intended.

Verification

  • Clicking the notification with the console open lands on the run page — not just a focused tab.
  • Clicking with the console closed opens the same page.
  • The GitHub Actions link is reachable from the run page in one click.
  • A notification for an instance the user no longer owns degrades to /console/ rather than a 404.

Activity

  1. serge-ivo commented on Aug 7, 2026

    @serge-ivo
    ContributorAuthor

    Fixed in 1e4c7f1.

    Ordering: there is no per-deploy page in the product, and deferring the notification until one existed would not help — the only per-run artifact IS the GitHub Actions run, which is exactly the thing that cannot be navigated to. So it links the stable parent that already exists when the sweep runs: the repo. deployDeepLink(instanceId, repoId) → /console/instances/:id/coding?builds=<repoId>, which opens the Coding surface on Builds showing that repo's run history — with "View run" out to GitHub one click away, where the CI log belongs. The repo id is already in hand in runDeployWatch (the row it is sweeping), so nothing about the first-sight watermark rule changes.

    • decideDeployNotification takes the link as a parameter and a test asserts the URL is never off-origin.
    • store/sw.js: an off-origin target now skips the open-tab loop entirely (WindowClient.navigate is same-origin by spec), and a navigate that rejects falls THROUGH to openWindow instead of focusing a tab that did not move. The "focus anyway" comment described a no-op indistinguishable from a broken notification, and it is what made the bug intermittent.
    • The in-app notification list finally reads the row's url (stored since migration 0026, returned by SELECT *, never read). A deploy row has no instanceId, so clicking one previously did nothing at all. notificationRoute strips the /console base so react-router can add its own basename back; a pre-[bug] Deploy notification doesn't deep-link — the target is cross-origin, so client.navigate silently fails and only focuses the tab #338 row pointing at github.com opens in a new tab instead of routing to a 404.
    • A stale repo id degrades to the Builds fleet list (resolveBuildsView already handled it); a stale instance id degrades through the existing surface gate.

    On the third point: client.url.includes("/console") matching /usage and /preferences is correct — those ARE the console SPA, and moving whichever tab holds it is the intent of a deep link. Left as is, with a comment saying so.

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

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions