Repository navigation
🌱 test(crux/agent): pin LastLaunchAt wiring from manager to watchdog observation #57911
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # changelog-fragment-guard (#5675): PR-level guard for the changelog.d/ | |
| # fragment convention, modelled on testutil-guard.yml (#5603). | |
| # | |
| # Changelog entries used to be appended directly under CHANGELOG.md's | |
| # `## Unreleased` heading, so any two PRs landing near each other edited the | |
| # same lines under the same heading and the second one to merge always hit a | |
| # structural conflict — six rebases of unrelated PRs on 2026-09-02 alone. | |
| # Entries now land as one file per PR under changelog.d/ (see its README.md), | |
| # compiled into Unreleased at release time by src/scripts/compile-changelog.sh | |
| # inside tagged-release.yml. This guard is what keeps new PRs on the fragment | |
| # path. | |
| # | |
| # WHAT IT ASKS: a PR changing code under src/** must either | |
| # - add a changelog.d/<category>-<pr-or-slug>.md fragment, or | |
| # - carry the `no-changelog` label. | |
| # Docs-only and test-only changes are exempt without the label — the same | |
| # exemption the changelog discipline has always had (CHANGELOG.md's "How we | |
| # maintain this file": routine refactors, test-only changes, and dependency | |
| # churn are out of scope; changelog-reminder.yml applies the same carve-out). | |
| # It also validates any fragments the PR does add (name and shape), so a | |
| # miscategorized fragment fails at PR time rather than at release time — | |
| # the category prefix drives the semver bump. | |
| # | |
| # TRANSITION (date-gated below, remove after it lapses): until 2026-09-09 a | |
| # direct CHANGELOG.md edit still satisfies the guard, so PRs already in flight | |
| # with entries under `## Unreleased` land without rework. The pass message | |
| # tells those authors to prefer fragments. After the window a direct-edit-only | |
| # PR goes red here — still non-blocking — because a direct edit is exactly the | |
| # conflict generator this migration removes. | |
| # | |
| # REQUIRED CONTEXT — this check is a required status check on the protected | |
| # release lines (#11250), so it MUST report on every PR. GitHub leaves a | |
| # path-filtered required check "expected" forever on PRs whose diff never | |
| # matches `on.paths`, and such a PR can never merge (hivecommons/hive#11234, a | |
| # workflow-only change, was refused with `405 Required status check | |
| # "changelog-fragment-guard" is expected`). The path gate therefore lives in | |
| # the job, not in the trigger: check-changelog-fragment.sh exits 0 with "no | |
| # non-exempt code changes under src/" on docs-, test-, CI- and bin-only PRs, | |
| # which is the same exemption `on.paths` used to express. Do NOT reintroduce | |
| # an `on.paths` filter here while the context is required. | |
| # | |
| # Registered in .github/release-lines.yml (`[-v2]`): changelog.d/ and the | |
| # compiler exist only on v4, and v2 is sunset — a PR to v2 would run the v2 | |
| # merge-ref's workflows anyway, where this file does not exist. | |
| name: changelog-fragment-guard | |
| on: | |
| pull_request: | |
| branches: [v4, v5, v6] | |
| # Default types plus labeled/unlabeled: adding or removing `no-changelog` | |
| # must re-evaluate the verdict without waiting for a push. | |
| types: [opened, reopened, synchronize, labeled, unlabeled] | |
| # No `paths:` filter on purpose — see REQUIRED CONTEXT above. The job | |
| # itself decides whether the diff is changelog-relevant. | |
| permissions: | |
| # contents: write lets the autofix step push a generated fragment to | |
| # same-repo PR branches; pull-requests: write posts the sticky guidance | |
| # comment. Both are read-only on fork PRs and those steps are skipped there. | |
| # No PR code is executed in this workflow — only git, grep and echo run. | |
| # actions: write lets the autofix dispatch pr-verifier.yml on the head it | |
| # pushed with github.token, the one event shape that token may fire | |
| # (#17036). | |
| contents: write | |
| pull-requests: write | |
| actions: write | |
| # Only a new push may cancel a running guard: the superseded run is on the | |
| # old head SHA, where nothing merges anyway. Label events must NOT cancel — | |
| # hive's bots apply three or four labels seconds after every push, each | |
| # label spawned a run that cancelled the push-triggered one, and the head SHA | |
| # was left with CANCELLED check-runs for this *required* context. GitHub | |
| # scores every run of a required name, so one cancelled sibling kept the PR | |
| # at mergeStateStatus=BLOCKED with a green survivor beside it (#11250). | |
| concurrency: | |
| group: changelog-fragment-guard-${{ github.event.pull_request.number }} | |
| cancel-in-progress: ${{ github.event.action == 'synchronize' }} | |
| jobs: | |
| changelog-fragment-guard: | |
| name: changelog-fragment-guard | |
| # No job-level `if:` on label events — on purpose (#14755). This job used | |
| # to skip itself for every label other than `no-changelog`, and a skipped | |
| # job still reports a check-run that GitHub scores as *passing* for a | |
| # required context. hive's labeler adds three or four labels seconds | |
| # after every push, so a genuine failure on the push-triggered run was | |
| # superseded by a "skipped" sibling on the same head SHA and tide merged | |
| # a malformed fragment (#14629), which then broke the release `decide` | |
| # job on v5 and held every open v5 PR behind a red base. The guard is | |
| # cheap (git, grep, echo); re-running it on each label event keeps the | |
| # reported verdict truthful. Label runs are not cancelled by concurrency | |
| # (see above), so they cannot leave CANCELLED check-runs either. | |
| # Forward-merge sync PRs (v5-topup.yml / v6-topup.yml, and the hand-rolled | |
| # ones maintainers open when those conflict) live on sync/* branches — | |
| # or on <lane>/sync-vN-to-vM when an agent opens the top-up, since the | |
| # push broker only accepts <lane>/... heads (#10498) — and carry the | |
| # source line's entire history. The three-dot diff below | |
| # re-attributes every src/ change and every release-time CHANGELOG.md | |
| # compilation edit to the sync PR, so it can never satisfy the guard — | |
| # each carried change already passed it on its own PR (#7783). | |
| # Required on v5: keep on the self-hosted pool so a GitHub-hosted runner | |
| # outage does not make otherwise-mergeable PRs impossible to merge. | |
| runs-on: ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && fromJSON(vars.HIVE_RUNNER_LABELS || '["ubuntu-latest"]') || 'ubuntu-latest' }} | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Detect forward-merge sync branch | |
| id: sync_branch | |
| run: | | |
| HEAD_REF=${GITHUB_HEAD_REF:-} | |
| case "$HEAD_REF" in | |
| # Human-opened top-ups live on sync/*; agent-opened ones are | |
| # forced onto <lane>/sync-vN-to-vM by the push broker. Keep in | |
| # step with src/scripts/check-changelog-fragment.sh. | |
| sync/*|*/sync-v[0-9]*-to-v[0-9]*) echo "skip=true" >> "$GITHUB_OUTPUT" ;; | |
| *) echo "skip=false" >> "$GITHUB_OUTPUT" ;; | |
| esac | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| if: ${{ steps.sync_branch.outputs.skip != 'true' }} | |
| with: | |
| # Full history so the merge base with the target branch exists | |
| # locally and the three-dot diff below is computable. | |
| fetch-depth: 0 | |
| # The autofix push uses TOPUP_PUSH_TOKEN when set: a push made with | |
| # that PAT fires `pull_request` AND `pull_request_target` runs, so | |
| # every check re-reports on the new head. With github.token the | |
| # push lands and fires the `pull_request` runs, but NO | |
| # `pull_request_target` run (observed on #16567's b9cd4bac: eleven | |
| # pull_request runs with actor=github-actions[bot], no PR Verifier). | |
| # Dependabot-triggered runs see no Actions secrets, so Dependabot PRs | |
| # (#16567) always take the github.token path; the dispatch step | |
| # after the push re-runs pr-verifier for them. Same optional-PAT | |
| # convention as cli-pin-bump.yml / v5-topup.yml / notice-autofix.yml. | |
| token: ${{ secrets.TOPUP_PUSH_TOKEN != '' && secrets.TOPUP_PUSH_TOKEN || github.token }} | |
| - name: Code changes must carry a changelog.d fragment (or no-changelog) | |
| id: guard | |
| if: ${{ steps.sync_branch.outputs.skip != 'true' }} | |
| env: | |
| BASE_REF: ${{ github.base_ref }} | |
| # Read from the event payload, not the API: needs no token scope and | |
| # therefore behaves identically on fork PRs (#4440's lesson). | |
| HAS_NO_CHANGELOG_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'no-changelog') }} | |
| # The guard copies any src/changelog.d/ fragments it rejected here so | |
| # the autofix and comment steps below can act on them (#11919). | |
| MISPLACED_OUT: ${{ runner.temp }}/changelog-misplaced.txt | |
| run: | | |
| set -euo pipefail | |
| src/scripts/check-changelog-fragment.sh "$BASE_REF" | |
| # api-reference.md and api-reference/*.md `pkg/<file>:<line>` citations go stale when a PR edits | |
| # a route-registering server.go. The `relative links and anchors resolve` | |
| # job reports that too, but it is not a required check, so the causing PR | |
| # merged red and every later PR inherited the failure (#11866). This | |
| # context is required, so the drift fails the PR that caused it. Pure | |
| # grep/sed, no toolchain. | |
| - name: Route-registering or static page files changed — api-reference citations must be resynced | |
| if: ${{ steps.sync_branch.outputs.skip != 'true' }} | |
| env: | |
| BASE_REF: ${{ github.base_ref }} | |
| run: | | |
| set -euo pipefail | |
| if ! git diff --name-only "origin/${BASE_REF}...HEAD" | grep -Eq '^src/pkg/(hub|dashboard)/server\.go$|^src/pkg/(hub|dashboard)/static/'; then | |
| echo "No server.go or hub/dashboard static page change in this PR — nothing to check." | |
| exit 0 | |
| fi | |
| if ! bash src/scripts/check-api-reference-citations.sh; then | |
| echo "::error::This PR edited a server.go or static pages and src/docs/api-reference.md / api-reference/*.md citations drifted. Run 'bash src/scripts/check-api-reference-citations.sh --fix' and commit the result. If static pages under src/pkg/dashboard/ also changed, lower the matching baseline in src/pkg/dashboard/webstatic/style_ratchet_test.go (styleRatchetBaselines) in the same PR." | |
| exit 1 | |
| fi | |
| # Most guard failures are humans who never heard of changelog.d. For | |
| # same-repo PRs (the author already has push rights to the branch, so | |
| # this adds no privilege), generate a fragment from the PR title and push | |
| # it signed-off; the author can reword it or swap in `no-changelog`. | |
| # Fork PRs cannot be pushed to and only get the comment below. | |
| - name: Autofix — add a fragment derived from the PR title | |
| id: autofix | |
| if: ${{ always() && steps.guard.outcome == 'failure' && steps.sync_branch.outputs.skip != 'true' && github.event.pull_request.head.repo.full_name == github.repository }} | |
| env: | |
| PR_TITLE: ${{ github.event.pull_request.title }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| PR_AUTHOR_LOGIN: ${{ github.event.pull_request.user.login }} | |
| PR_AUTHOR_ID: ${{ github.event.pull_request.user.id }} | |
| HEAD_REF: ${{ github.event.pull_request.head.ref }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| MISPLACED_FILE: ${{ runner.temp }}/changelog-misplaced.txt | |
| run: | | |
| set -euo pipefail | |
| echo "pushed=false" >> "$GITHUB_OUTPUT" | |
| # A fragment under src/changelog.d/ is never compiled. Generating a | |
| # title-only twin at the root would bury the hand-written entry | |
| # (#11919) — fail with the move instruction instead. | |
| if [ -s "$MISPLACED_FILE" ]; then | |
| while IFS= read -r m; do | |
| [ -z "$m" ] && continue | |
| echo "::error file=$m::misplaced fragment — not autofixing. Run: git mv $m changelog.d/$(basename "$m")" | |
| done < "$MISPLACED_FILE" | |
| exit 1 | |
| fi | |
| # Only the "no fragment" failure is auto-fixable; a malformed | |
| # fragment (wrong name / no bullet) is the author's to correct. | |
| if [ -s fragments.txt ]; then | |
| echo "PR already has a fragment that failed validation — not autofixing." | |
| exit 0 | |
| fi | |
| # Sanitise the title: single line, printable ASCII/UTF-8 letters, | |
| # digits and common punctuation; strip the leading emoji/symbols and | |
| # any HTML-comment opener so the entry cannot smuggle a release marker. | |
| entry="$(printf '%s' "$PR_TITLE" | tr -d '\r\n' | tr -cd '[:print:]' | sed -E 's/^[^[:alnum:]]+//; s/<!--//g; s/-->//g; s/[[:space:]]+$//' | cut -c1-200)" | |
| [ -n "$entry" ] || entry="Change from PR #${PR_NUMBER}" | |
| lower="$(printf '%s' "$PR_TITLE" | tr '[:upper:]' '[:lower:]')" | |
| category=changed | |
| case "$lower" in | |
| *🐛*|fix*|bug*|*"fix("*|*"fix:"*) category=fixed ;; | |
| *✨*|feat*|add*|*"feat("*|*"feat:"*) category=added ;; | |
| *🔒*|*security*|*"sec("*|*"sec:"*|*cve-*) category=security ;; | |
| *deprecat*) category=deprecated ;; | |
| esac | |
| f="changelog.d/${category}-${PR_NUMBER}.md" | |
| if [ -e "$f" ]; then | |
| echo "$f already exists on the branch — not overwriting." | |
| exit 0 | |
| fi | |
| printf -- '- %s (#%s)\n' "$entry" "$PR_NUMBER" > "$f" | |
| echo "Generated $f:"; cat "$f" | |
| # Author AND sign off as the PR author's GitHub noreply identity, not | |
| # as github-actions[bot]. The squash merge attributes the landed | |
| # commit to the PR author while keeping this message, so a bot | |
| # Signed-off-by here becomes a permanent mismatched-signoff on the | |
| # protected branch — exactly what dco-squash-attribution.yml | |
| # (check-squash-signoff-attribution.sh) rejects, which turned every | |
| # autofixed human PR red. The noreply form <id>+<login>@... is the | |
| # one that gate accepts for the PR author; the bot is credited with | |
| # Co-authored-by, the correct trailer for an automated contributor. | |
| author_email="${PR_AUTHOR_ID}+${PR_AUTHOR_LOGIN}@users.noreply.github.com" | |
| git config user.name "$PR_AUTHOR_LOGIN" | |
| git config user.email "$author_email" | |
| git add -- "$f" | |
| git commit -m "changelog: fragment for #${PR_NUMBER} | |
| Generated by changelog-fragment-guard from the PR title (#5675). | |
| Edit the wording freely, or replace this file with the no-changelog | |
| label if the change is not user-visible. | |
| Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> | |
| Signed-off-by: ${PR_AUTHOR_LOGIN} <${author_email}>" | |
| # --force-with-lease against the SHA this run evaluated: never | |
| # clobber a push the author made while this job ran. | |
| # When this push was made with github.token it fires no | |
| # `pull_request_target` run, so pr-verifier (a required context) | |
| # would stay "expected"; the step below re-triggers it (#17036). | |
| if git push --force-with-lease="refs/heads/${HEAD_REF}:${HEAD_SHA}" origin "HEAD:refs/heads/${HEAD_REF}"; then | |
| echo "pushed=true" >> "$GITHUB_OUTPUT" | |
| echo "fragment=$f" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "::warning::could not push the generated fragment (branch moved or token lacks push); leaving guidance comment instead" | |
| fi | |
| # Deterministically re-run pr-verifier on the head just pushed (#17036). | |
| # On #16567 the autofix push landed 47s after the last prow label and | |
| # nothing relabelled the PR for three days, so pr-verifier's new | |
| # labeled/unlabeled types alone had no producer. When the push used | |
| # TOPUP_PUSH_TOKEN it already fired `pull_request_target` and nothing | |
| # more is needed. When it used github.token, the only events that token | |
| # may trigger are workflow_dispatch and repository_dispatch, so dispatch | |
| # pr-verifier.yml on the PR branch: the run's check lands on the branch | |
| # tip, which is the head just pushed. The autofix only pushes to | |
| # same-repo branches, so this never dispatches a fork's workflow file. | |
| - name: Dispatch pr-verifier on the autofix head (github.token push) | |
| if: ${{ steps.autofix.outputs.pushed == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| HAS_PUSH_TOKEN: ${{ secrets.TOPUP_PUSH_TOKEN != '' }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| HEAD_REF: ${{ github.event.pull_request.head.ref }} | |
| run: | | |
| set -euo pipefail | |
| if [ "$HAS_PUSH_TOKEN" = "true" ]; then | |
| echo "Pushed with TOPUP_PUSH_TOKEN — the push itself fired pull_request_target; nothing to dispatch." | |
| exit 0 | |
| fi | |
| if gh workflow run pr-verifier.yml --repo "$GITHUB_REPOSITORY" --ref "$HEAD_REF" -f "pr_number=${PR_NUMBER}"; then | |
| echo "dispatched pr-verifier.yml on ${HEAD_REF} for #${PR_NUMBER}" | |
| else | |
| # Most likely the branch's pr-verifier.yml predates the | |
| # workflow_dispatch trigger (HTTP 422): a rebase onto the base | |
| # branch fixes it, and the rebase push fires everything anyway. | |
| echo "::warning::could not dispatch pr-verifier.yml on ${HEAD_REF}; the required 'verify PR contents' context stays expected on the new head until a push, a non-github.token label flip, or a close/reopen (#17036). Rebasing onto the base branch resolves it." | |
| fi | |
| # Whatever the autofix did, tell the author once (one sticky comment, | |
| # updated in place) and remove the comment when the check passes. | |
| - name: Post or update guidance comment | |
| if: ${{ always() && steps.sync_branch.outputs.skip != 'true' && github.event.pull_request.head.repo.full_name == github.repository }} | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GUARD_OUTCOME: ${{ steps.guard.outcome }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| AUTOFIX_PUSHED: ${{ steps.autofix.outputs.pushed }} | |
| AUTOFIX_FILE: ${{ steps.autofix.outputs.fragment }} | |
| MISPLACED_FILE: ${{ runner.temp }}/changelog-misplaced.txt | |
| with: | |
| script: | | |
| const marker = '<!-- changelog-fragment-guard -->'; | |
| const number = Number(process.env.PR_NUMBER); | |
| const { owner, repo } = context.repo; | |
| const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number: number, per_page: 100 }); | |
| const existing = comments.find(c => c.user?.type === 'Bot' && (c.body || '').includes(marker)); | |
| const failed = process.env.GUARD_OUTCOME === 'failure'; | |
| // Misplaced src/changelog.d/ fragments get their own comment with | |
| // the exact git mv commands (next step); the generic "fragment | |
| // needed" text would be wrong there (#11919). | |
| const fs = require('fs'); | |
| const misplaced = fs.existsSync(process.env.MISPLACED_FILE) && fs.readFileSync(process.env.MISPLACED_FILE, 'utf8').trim() !== ''; | |
| if (!failed || misplaced) { | |
| if (existing) { | |
| await github.rest.issues.deleteComment({ owner, repo, comment_id: existing.id }); | |
| } | |
| return; | |
| } | |
| if (process.env.AUTOFIX_PUSHED === 'true') { | |
| const body = [ | |
| marker, | |
| '### Changelog fragment added for you', | |
| '', | |
| 'This PR changes code under `src/` and had no `changelog.d/` fragment, so CI pushed `' + process.env.AUTOFIX_FILE + '` generated from the PR title (signed-off, by github-actions[bot]).', | |
| '', | |
| '- Reword the bullet if the title is not how the entry should read in CHANGELOG.md (category prefix drives the semver bump: `added` / `changed` / `deprecated` / `fixed` / `security`).', | |
| '- If the change is **not** user-visible, delete the file and ask a maintainer for the `no-changelog` label.', | |
| '- Pull before your next push: `git pull --rebase`.', | |
| '', | |
| 'See `changelog.d/README.md` (#5675). This comment is removed automatically once the check passes.', | |
| ].join('\n'); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body }); | |
| } else { | |
| await github.rest.issues.createComment({ owner, repo, issue_number: number, body }); | |
| } | |
| return; | |
| } | |
| const body = [ | |
| marker, | |
| '### Changelog fragment needed', | |
| '', | |
| 'This PR changes code under `src/` but has no `changelog.d/` fragment and no `no-changelog` label, so the **changelog-fragment-guard** check fails.', | |
| '', | |
| 'Pick one:', | |
| '', | |
| '**A. The change is user-visible** (feature, fix, new env var, behavior/security change) — add one file and push:', | |
| '', | |
| '```sh', | |
| '# category: added | changed | deprecated | fixed | security', | |
| "echo '- <one-line entry in the style of CHANGELOG.md>' > changelog.d/<category>-" + number + '.md', | |
| 'git add changelog.d && git commit -s -m "changelog: fragment for #' + number + '"', | |
| '```', | |
| '', | |
| '**B. Not user-visible** (refactor, internal test plumbing, CI) — ask a maintainer to add the `no-changelog` label.', | |
| '', | |
| 'Do not edit `CHANGELOG.md` directly; the release compiler assembles it from fragments (see `changelog.d/README.md`, #5675). This comment is removed automatically once the check passes.', | |
| ].join('\n'); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body }); | |
| } else { | |
| await github.rest.issues.createComment({ owner, repo, issue_number: number, body }); | |
| } | |
| # Fragments under src/changelog.d/ are never compiled (#11919). Fork PRs | |
| # cannot be pushed to and are skipped by the steps above, so every PR — | |
| # fork or not — gets ONE sticky comment with the exact git mv commands, | |
| # removed again once nothing is misplaced. Best-effort: if the token is | |
| # read-only (some fork configurations) the commands are still in the job | |
| # summary and the guard's ::error annotations. | |
| - name: Post or update misplaced-fragment comment | |
| if: ${{ always() && steps.sync_branch.outputs.skip != 'true' }} | |
| continue-on-error: true | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| MISPLACED_FILE: ${{ runner.temp }}/changelog-misplaced.txt | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const marker = '<!-- hive-changelog-misplaced -->'; | |
| const number = Number(process.env.PR_NUMBER); | |
| const { owner, repo } = context.repo; | |
| const files = fs.existsSync(process.env.MISPLACED_FILE) | |
| ? fs.readFileSync(process.env.MISPLACED_FILE, 'utf8').split('\n').map(s => s.trim()).filter(Boolean) | |
| : []; | |
| const cmds = files.map(f => 'git mv ' + f + ' changelog.d/' + f.split('/').pop()); | |
| if (cmds.length) { | |
| await core.summary | |
| .addHeading('Misplaced changelog fragments', 3) | |
| .addRaw('Fragments live in `changelog.d/` at the repository root, not under `src/`. Run:\n\n') | |
| .addCodeBlock(cmds.join('\n'), 'sh') | |
| .write(); | |
| } | |
| try { | |
| const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number: number, per_page: 100 }); | |
| const existing = comments.find(c => c.user?.type === 'Bot' && (c.body || '').includes(marker)); | |
| if (!cmds.length) { | |
| if (existing) { | |
| await github.rest.issues.deleteComment({ owner, repo, comment_id: existing.id }); | |
| } | |
| return; | |
| } | |
| const body = [ | |
| marker, | |
| '### Changelog fragment in the wrong directory', | |
| '', | |
| 'Fragments live in `changelog.d/` at the **repository root**, not under `src/`. Files under `src/changelog.d/` are never compiled into `CHANGELOG.md`, so the **changelog-fragment-guard** check fails. Move them and push:', | |
| '', | |
| '```sh', | |
| ...cmds, | |
| 'git commit -s -m "changelog: move fragment to repo-root changelog.d/"', | |
| '```', | |
| '', | |
| 'See `changelog.d/README.md` (#5675, #11919). This comment is removed automatically once nothing is misplaced.', | |
| ].join('\n'); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body }); | |
| } else { | |
| await github.rest.issues.createComment({ owner, repo, issue_number: number, body }); | |
| } | |
| } catch (e) { | |
| core.warning('could not post the misplaced-fragment comment (' + e.message + '); the git mv commands are in the job summary'); | |
| } |