diff --git a/.github/workflows/devkit-sync.yml b/.github/workflows/devkit-sync.yml index b6871b7..0fc91b8 100644 --- a/.github/workflows/devkit-sync.yml +++ b/.github/workflows/devkit-sync.yml @@ -117,10 +117,21 @@ jobs: set -euo pipefail git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git push origin --delete "$BRANCH_NAME" 2>/dev/null || true + # NO `git push origin --delete "$BRANCH_NAME"` here (DEC-079). + # + # Deleting the head ref DELETES THE PR WITH IT: GitHub auto-closes any + # open PR whose head branch disappears. That is what closed 18 sync PRs + # across the fleet, including four that were already fully green — the + # e2e had passed, and the work was discarded and re-queued for nothing. + # It also made the `gh pr edit` reuse path below dead code: the delete + # closed the very PR the next line goes looking for, so `existing` was + # always empty and every run created a brand-new PR. + # + # Force-pushing the branch is sufficient and non-destructive: an open PR + # follows its head ref and simply picks up the new commit. git checkout -B "$BRANCH_NAME" git commit -m "chore: sync devkit submodule to ${{ steps.bump.outputs.target }}" - git push --force-with-lease origin "$BRANCH_NAME" + git push --force origin "$BRANCH_NAME" body="Automated devkit sync — bumped \`$DEVKIT_PATH\` to the devkit's latest release (\`${{ steps.bump.outputs.target }}\`)." existing=$(gh pr list --head "$BRANCH_NAME" --state open --json number --jq '.[0].number // empty' 2>/dev/null || true) diff --git a/SPEC.md b/SPEC.md index d594bfe..77d59ea 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1128,6 +1128,25 @@ while half the fleet was still broken. Collector → `e2e/harness/_artifacts.sh` harnesses; prune → `e2e/ci/prune-artifacts.sh`, invoked by both workflows. A reusable workflow can call the script from the consumer's vendored devkit path, so there is still exactly one definition. +**DEC-079 — devkit-sync is on-demand, and never deletes its own branch.** +Two corrections to the sync mechanism, one of policy and one of implementation. + +*Policy.* The nightly `schedule:` is removed; the caller is `workflow_dispatch:` only. A submodule +pin is a deliberate dependency upgrade, not something that should land while nobody is watching, and +the schedule measurably cost more than it delivered: **189 runs produced 29 merged bumps** (51 PRs +opened, 18 closed without merging). Worse, from 2026-07-10 it silently no-op'd for three weeks — +every night it opened a PR whose checks sat in `action_required` awaiting approval, so nothing merged +and nothing complained, because a no-op is indistinguishable from "nothing to sync". Deliberate bumps +go through `make update-devkit` or a manual dispatch. + +*Implementation.* The push step began with `git push origin --delete "$BRANCH_NAME"`. Deleting a head +ref **deletes the PR with it** — GitHub auto-closes any open PR whose head branch disappears. That is +the actual cause of the 18 closed-unmerged PRs, including four that were **already fully green**: the +e2e had passed and the result was discarded and re-queued for nothing. The same delete also made the +`gh pr edit` reuse path below it dead code — it closed the very PR the next line went looking for, so +`existing` was always empty and every run created a brand-new PR instead of updating one. A force-push +alone is sufficient and non-destructive: an open PR follows its head ref and picks up the new commit. + > **Numbering note:** DEC-070–072 were introduced as code comments only and were never written up > here. This section documents 073–076; backfilling 070–072 is tracked separately. diff --git a/templates/devkit-sync.caller.yml b/templates/devkit-sync.caller.yml index 32993e5..d2a8497 100644 --- a/templates/devkit-sync.caller.yml +++ b/templates/devkit-sync.caller.yml @@ -1,8 +1,20 @@ # devkit-sync — caller template (SPEC DEC-044). # # Copied into a consumer plugin repo at .github/workflows/devkit-sync.yml by -# `make link-workflows`. Keeps the repo's vendored devkit submodule -# (tests/_testkit) current by bumping it to the devkit's latest main nightly. +# `make link-workflows`. Bumps the repo's vendored devkit submodule +# (tests/_testkit) to the devkit's latest release within its major channel. +# +# ON DEMAND ONLY (DEC-079). This used to run nightly at 04:00 UTC. A submodule +# pin is a deliberate dependency upgrade, not something that should happen while +# you sleep, and the schedule cost more than it delivered: +# * 189 runs across the fleet produced 29 merged bumps — 51 PRs opened, 18 of +# them closed without ever merging. +# * From 2026-07-10 it silently no-op'd for three weeks: every night it opened +# a PR whose checks sat in `action_required` awaiting approval, so nothing +# merged and nothing complained. A no-op looks exactly like "nothing to sync". +# * It clobbered in-flight work — see the branch-delete note in the reusable +# workflow. +# Bump deliberately instead: `make update-devkit`, or run this workflow manually. # # Required REPO-level secrets (the shared sync GitHub App; already present on # repos that previously used skills-sync — same App): @@ -15,8 +27,6 @@ name: devkit-sync on: - schedule: - - cron: "0 4 * * *" # nightly 04:00 UTC (offset from skills-sync's 03:00) workflow_dispatch: permissions: