Public: anyone can read this; the crew can change it.
Stop mirroring goes through the confirm page, which names the mirror and says nothing more is pushed #8005
Check · Review · Land · Live done — Landed and live in production.
"Stop mirroring" on a repository's settings page now goes through the confirm page first, like every other removal on that page. Before, it was a bare POST form to `<base>/settings/mirror/stop`.
shipped production is serving it deploy bbd5a0b
"Stop mirroring" on a repository's settings page now goes through the confirm page first, like every other removal on that page. Before, it was a bare POST form to <base>/settings/mirror/stop.
src/web/pages/settings.ts: the Mirror card's form is replaced byconfirmAction({ action:${base}/settings/mirror/stop, verb: "Stop mirroring" }). The mirror line and the "Stopping leaves … as it is; nothing more is pushed." sentence stay as they were.src/web/pages/search.ts:DESTRUCTIVEgets one entry for^/([^/]+)/([^/]+)/settings/mirror/stop$, directly after thesettings/proposal-tokensentry.
- Title: "Stop mirroring <owner>/<name>?" - Detail: "The copy on the other side stays as it is; nothing more is pushed to it. This cannot be turned back on from the settings page." - Phrase: none.
mirrorStopand the POST route are unchanged, so the MCP and the API stop a mirror exactly as before.test/confirm-mirror-stop.test.ts(new) checks two things for an admin:
- The settings page links to /-/confirm?to=…/settings/mirror/stop and no longer has a bare form posting there.
- The confirm page answers 200 with the title and the "nothing more is pushed" detail.
Both tests fail without the source change and pass with it.
Checks run: bun scripts/gate.ts, bun x tsc --noEmit and bun scripts/design-lint.ts are clean. bun scripts/fast.ts named 988 test files, too many to run here, so I picked the ones that cover this change, and all 66 tests pass:
test/confirm-mirror-stop.test.ts,test/mirror-target.test.ts,test/danger-buttons.test.tstest/settings.test.ts,test/settings-backups-mirror.test.ts,test/settings-repo-timing.test.tstest/migrate-after.test.ts,test/mcp-mirror-set.test.ts,test/search.test.tstest/cross-origin-mutations.test.ts,test/security-floor-routes.test.ts,test/design-audit.test.ts
During that run, one snapshot test logged a connection error because the sandbox blocks a local port; the test still passed.
To try it on the preview: open the settings of a repository that has an outbound mirror and press "Stop mirroring". The confirm page should name <owner>/<name> and say the copy on the other side stays as it is; confirming stops the mirror.
🤖 Generated with Claude Code
The preview works: /-/confirm for settings/mirror/stop answers with the title and the detail, the POST route and the terminal are unchanged (the terminal already confirms), and the page is plain links and post forms, so it works with JavaScript off. One change needed, and one to tidy.
- (fix first) The title does not name the mirror. The issue says the confirm page names the mirror, but "Stop mirroring alice/gizmo?" names the repository you are on, not the copy that stops receiving pushes (octo/gizmo). Someone who has forgotten where it mirrors to cannot tell what they are cutting off. Look up getMirrorTarget for that owner/name in the confirm handler and put held.fullName in the title or the detail; fall back to the current wording when nothing is mirrored. Make the test assert octo/gizmo on the confirm page.
- The detail says "cannot be turned back on from the settings page". Nothing in the diff or the settings page shows that; it is true only because setMirrorTarget is called from migrate. Say where it is set up again (migrate) or drop the sentence, so the page does not promise something unchecked.
Tests: add one that confirming (the POST) still stops the mirror and one for a repo that is not mirrored, since the new confirm entry now matches any owner/name.
"Stop mirroring" on a repository's settings page now goes through the confirm page first, like every other removal there. That page now names the mirror itself, not just the repository you are on.
- Settings page (
src/web/pages/settings.ts): the Mirror card's bare POST form is replaced byconfirmAction({ action:${base}/settings/mirror/stop, verb: "Stop mirroring" }). The mirror line and the "Stopping leaves … as it is" sentence stay.mirrorStopand its POST route are unchanged. - Confirm entry (
src/web/pages/search.ts):DESTRUCTIVEgets an entry for/<owner>/<name>/settings/mirror/stop, directly aftersettings/proposal-tokens. It looks the target up withgetMirrorTarget.
- Admin of a mirrored repo: the title is "Stop mirroring alice/gizmo to octo/gizmo?" and the detail starts "octo/gizmo stays as it is; nothing more is pushed to it."
- Repo with no mirror: it falls back to "Stop mirroring alice/gizmo?" and "The copy on the other side stays as it is".
- Someone who cannot administer the repo: they get the fallback wording too. This is a choice beyond the review: without it, the confirm page would tell anyone where a private repository mirrors to. It follows the existing take-back entry, which only shows a pull's title to someone who can write.
- Re-enabling: the detail now ends "Only a new migration sets up a mirror again." The only place that creates a mirror is
setMirrorTargetinsrc/actions/migrate.ts:672, so this replaces the unchecked "cannot be turned back on from the settings page". - Tests (
test/confirm-mirror-stop.test.ts, 5 tests):
- The settings page links to /-/confirm?to=…/settings/mirror/stop and no longer has a bare form posting there.
- The confirm page names octo/gizmo and says a migration sets a mirror up again.
- A non-admin gets a 200 with the plain title and never sees octo/gizmo.
- A repo with no mirror gets the plain wording.
- Confirming, which is a same-origin POST, still answers 303 to settings and removes the mirror target.
bun scripts/gate.ts, bun x tsc --noEmit and bun scripts/design-lint.ts are clean. fast.ts named too many files to run here, so I picked these and all 36 tests pass:
test/confirm-mirror-stop.test.ts,test/mirror-target.test.ts,test/danger-buttons.test.tstest/search.test.ts,test/settings.test.ts,test/security-floor-routes.test.ts
To try it on the preview: open settings on a mirrored repository, press "Stop mirroring", and check that the confirm page names where it mirrors to.
🤖 Generated with Claude Code
All three earlier asks are met. (1) The confirm title now names the mirror target via getMirrorTarget, shown only to someone who canAdmin, so a private repo's destination is not leaked. (2) The unverified claim is replaced with "Only a new migration sets up a mirror again". (3) The tests cover the mirror name, the non-admin case, the not-mirrored case, and the POST still stopping the mirror (303, target removed). It uses the confirmAction component, is an <a> to navigate and a POST form to mutate, works without JavaScript, and the preview answers. Next, non-blocking: 1) the Mirror card on a repo you cannot administer should not render at all; 2) add the confirm path to the terminal door if tty has a stop-mirror command.
"Stop mirroring" on a repository's settings page now goes through the confirm page first, like every other removal on that page. Before, it was a bare POST form to
<base>/settings/mirror/stop.src/web/pages/settings.ts: the Mirror card's form is replaced byconfirmAction({ action:${base}/settings/mirror/stop, verb: "Stop mirroring" }). The mirror line and the "Stopping leaves … as it is; nothing more is pushed." sentence stay as they were.src/web/pages/search.ts:DESTRUCTIVEgets one entry for^/([^/]+)/([^/]+)/settings/mirror/stop$, directly after thesettings/proposal-tokensentry.- Title: "Stop mirroring <owner>/<name>?" - Detail: "The copy on the other side stays as it is; nothing more is pushed to it. This cannot be turned back on from the settings page." - Phrase: none.
mirrorStopand the POST route are unchanged, so the MCP and the API stop a mirror exactly as before.test/confirm-mirror-stop.test.ts(new) checks two things for an admin:- The settings page links to
/-/confirm?to=…/settings/mirror/stopand no longer has a bare form posting there. - The confirm page answers 200 with the title and the "nothing more is pushed" detail.Both tests fail without the source change and pass with it.
Checks run:
bun scripts/gate.ts,bun x tsc --noEmitandbun scripts/design-lint.tsare clean.bun scripts/fast.tsnamed 988 test files, too many to run here, so I picked the ones that cover this change, and all 66 tests pass:test/confirm-mirror-stop.test.ts,test/mirror-target.test.ts,test/danger-buttons.test.tstest/settings.test.ts,test/settings-backups-mirror.test.ts,test/settings-repo-timing.test.tstest/migrate-after.test.ts,test/mcp-mirror-set.test.ts,test/search.test.tstest/cross-origin-mutations.test.ts,test/security-floor-routes.test.ts,test/design-audit.test.tsDuring that run, one snapshot test logged a connection error because the sandbox blocks a local port; the test still passed.
To try it on the preview: open the settings of a repository that has an outbound mirror and press "Stop mirroring". The confirm page should name
<owner>/<name>and say the copy on the other side stays as it is; confirming stops the mirror.🤖 Generated with Claude Code
Closes #7979