From ebfc2a03065028e1cbdb75e517595d5aeac669db Mon Sep 17 00:00:00 2001 From: Steve S Date: Wed, 10 Jun 2026 15:05:52 -0400 Subject: [PATCH 1/5] Migrate PR-triggered workflows from PAT to GitHub App token (#61673) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/link-check-on-pr.yml | 14 ++++++++++++-- .github/workflows/test-changed-content.yml | 12 +++++++++++- .github/workflows/test.yml | 14 ++++++++++++-- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/.github/workflows/link-check-on-pr.yml b/.github/workflows/link-check-on-pr.yml index a52eca933d46..c22e8986f046 100644 --- a/.github/workflows/link-check-on-pr.yml +++ b/.github/workflows/link-check-on-pr.yml @@ -36,10 +36,20 @@ jobs: - uses: ./.github/actions/node-npm-setup + - name: Generate GitHub App token + if: ${{ github.repository == 'github/docs-internal' }} + id: app-token + uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2 + with: + app-id: ${{ secrets.DOCS_BOT_APP_ID }} + private-key: ${{ secrets.DOCS_BOT_APP_PRIVATE_KEY }} + owner: github + repositories: docs-early-access + - uses: ./.github/actions/get-docs-early-access if: ${{ github.repository == 'github/docs-internal' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + token: ${{ steps.app-token.outputs.token }} - name: Get changed files id: changed-files @@ -55,7 +65,7 @@ jobs: FILES_CHANGED: ${{ steps.changed-files.outputs.all_changed_files }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - SHOULD_COMMENT: ${{ secrets.DOCS_BOT_PAT_BASE != '' }} + SHOULD_COMMENT: ${{ secrets.DOCS_BOT_APP_ID != '' }} FAIL_ON_FLAW: true ENABLED_LANGUAGES: en run: npm run check-links-pr diff --git a/.github/workflows/test-changed-content.yml b/.github/workflows/test-changed-content.yml index c1584b0138c1..e04d93cf6228 100644 --- a/.github/workflows/test-changed-content.yml +++ b/.github/workflows/test-changed-content.yml @@ -34,10 +34,20 @@ jobs: - uses: ./.github/actions/node-npm-setup + - name: Generate GitHub App token + if: ${{ github.repository == 'github/docs-internal' }} + id: app-token + uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2 + with: + app-id: ${{ secrets.DOCS_BOT_APP_ID }} + private-key: ${{ secrets.DOCS_BOT_APP_PRIVATE_KEY }} + owner: github + repositories: docs-early-access + - uses: ./.github/actions/get-docs-early-access if: ${{ github.repository == 'github/docs-internal' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + token: ${{ steps.app-token.outputs.token }} - uses: ./.github/actions/cache-nextjs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 289061169169..f9a1c25dbb12 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -95,10 +95,20 @@ jobs: - uses: ./.github/actions/node-npm-setup + - name: Generate GitHub App token + if: ${{ github.repository == 'github/docs-internal' }} + id: app-token + uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2 + with: + app-id: ${{ secrets.DOCS_BOT_APP_ID }} + private-key: ${{ secrets.DOCS_BOT_APP_PRIVATE_KEY }} + owner: github + repositories: docs-early-access,docs-internal.es-es,docs-internal.ja-jp,docs-internal.pt-br,docs-internal.zh-cn,docs-internal.ru-ru,docs-internal.fr-fr,docs-internal.ko-kr,docs-internal.de-de + - uses: ./.github/actions/get-docs-early-access if: ${{ github.repository == 'github/docs-internal' }} with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + token: ${{ steps.app-token.outputs.token }} - name: Check the test fixture data (if applicable) if: ${{ matrix.name == 'fixtures' }} @@ -123,7 +133,7 @@ jobs: if: ${{ matrix.name == 'languages' }} uses: ./.github/actions/clone-translations with: - token: ${{ secrets.DOCS_BOT_PAT_BASE }} + token: ${{ steps.app-token.outputs.token }} - name: Gather files changed if: ${{ matrix.name == 'content-linter' }} From fefb18fb466a92c586b81e5ff52dfd49fd028f35 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Wed, 10 Jun 2026 12:06:03 -0700 Subject: [PATCH 2/5] Add --versions arg to count-translation-corruptions (default: all versions) (#61549) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../scripts/count-translation-corruptions.ts | 230 ++++++++++++------ .../tests/count-translation-corruptions.ts | 37 +++ 2 files changed, 198 insertions(+), 69 deletions(-) create mode 100644 src/languages/tests/count-translation-corruptions.ts diff --git a/src/languages/scripts/count-translation-corruptions.ts b/src/languages/scripts/count-translation-corruptions.ts index 9b50d443c986..d629fa886cab 100644 --- a/src/languages/scripts/count-translation-corruptions.ts +++ b/src/languages/scripts/count-translation-corruptions.ts @@ -11,11 +11,17 @@ import warmServer from '@/frame/lib/warm-server' import type { Site } from '@/types' import { correctTranslatedContentStrings } from '@/languages/lib/correct-translation-content' -program - .description('Tally the number of liquid corruptions in a translation. Outputs JSON to stdout.') - .argument('[language...]', 'language(s) to compare against') - .action(main) -program.parse(process.argv) +if (import.meta.url === `file://${process.argv[1]}`) { + program + .description('Tally the number of liquid corruptions in a translation. Outputs JSON to stdout.') + .argument('[language...]', 'language(s) to compare against') + .option( + '-v, --versions ', + 'comma-separated version(s) to render under (default: all versions)', + ) + .action((languageCodes, options) => main(languageCodes, options)) + program.parse(process.argv) +} type Reusables = Map @@ -24,6 +30,10 @@ interface CorruptionEntry { location: string error: string illegalTag?: string + // The versions this exact corruption (file + location + error) was + // observed rendering under. Structural errors surface under every + // applicable version; render-time errors may surface under only some. + versions: string[] } interface LanguageResult { @@ -43,11 +53,38 @@ interface FrontmatterError { interface CorruptionReport { hasFailures: boolean totalCount: number + versions: string[] frontmatterErrors: FrontmatterError[] languages: LanguageResult[] } -async function main(languageCodes: string[]) { +export function resolveRequestedVersions(optionValue?: string): string[] { + const allVersionKeys = Object.keys(allVersions) + if (!optionValue) { + return allVersionKeys + } + const versions = Array.from( + new Set( + optionValue + .split(',') + .map((v) => v.trim()) + .filter(Boolean), + ), + ) + const invalid = versions.filter((v) => !allVersionKeys.includes(v)) + if (invalid.length) { + throw new Error( + `Invalid version(s): ${invalid.join(', ')}. Valid versions: ${allVersionKeys.join(', ')}`, + ) + } + return versions +} + +async function main(languageCodes: string[], options: { versions?: string } = {}) { + // Resolve and validate the versions to render under before suppressing + // console output, so validation errors are visible. + const versions = resolveRequestedVersions(options.versions) + // Suppress warmServer noise (frontmatter errors from translations) // and capture them as structured data instead const originalError = console.error @@ -92,7 +129,7 @@ async function main(languageCodes: string[]) { const languageResults: LanguageResult[] = [] for (const languageCode of langCodes) { - languageResults.push(await run(languageCode, site, reusables)) + languageResults.push(await run(languageCode, site, reusables, versions)) } const totalCount = languageResults.reduce((sum, r) => sum + r.total, 0) @@ -100,6 +137,7 @@ async function main(languageCodes: string[]) { const report: CorruptionReport = { hasFailures: totalCount > 0 || frontmatterErrors.length > 0, totalCount, + versions, frontmatterErrors, languages: languageResults, } @@ -136,12 +174,19 @@ function getReusables(): Reusables { // Build a minimal Liquid render context for validating translated content. // ifversion needs currentVersionObj; data tags call getDataByLanguage() internally. -const defaultVersion = 'free-pro-team@latest' -function buildRenderContext(languageCode: string) { +function buildRenderContext(languageCode: string, version: string) { + // Mirror what the shortVersions middleware adds to the request context so + // bare conditionals like {% ifversion ghes %} resolve correctly. Without the + // short-name flag, the native Liquid `if` sees an undefined variable and the + // branch silently evaluates false, hiding real corruptions. + const currentVersionObj = allVersions[version] return { currentLanguage: languageCode, - currentVersion: defaultVersion, - currentVersionObj: allVersions[defaultVersion], + currentVersion: version, + currentVersionObj, + [currentVersionObj.shortName]: true, + currentRelease: version.split('@')[1], + currentVersionShortName: currentVersionObj.shortName, } } @@ -149,89 +194,136 @@ async function run( languageCode: string, site: Site, englishReusables: Reusables, + versions: string[], ): Promise { const language = languages[languageCode as keyof typeof languages] - const corruptions: CorruptionEntry[] = [] const wheres = new Map() const illegalTags = new Map() - const context = buildRenderContext(languageCode) + + // Dedupe corruptions by file + location + error so the same broken Liquid + // rendered under multiple versions is a single entry annotated with every + // version it surfaced under (rather than N near-duplicate entries). + const byKey = new Map }>() // Suppress console.warn during rendering — the {% data %} tag warns // when it can't find translated data, which is expected noise. const originalWarn = console.warn console.warn = () => {} - function countError(error: Error, where: string, file: string) { + function countError(error: Error, where: string, file: string, version: string) { const errorString = error.message + const key = `${file}\t${where}\t${errorString}` + + let entry = byKey.get(key) + if (!entry) { + let illegalTag: string | undefined + const errorWithExtras = error as Error & { token?: { content?: string } } + if (errorString.includes('illegal tag syntax') && errorWithExtras.token?.content) { + illegalTag = errorWithExtras.token.content + illegalTags.set(illegalTag, (illegalTags.get(illegalTag) || 0) + 1) + } - let illegalTag: string | undefined - const errorWithExtras = error as Error & { token?: { content?: string } } - if (errorString.includes('illegal tag syntax') && errorWithExtras.token?.content) { - illegalTag = errorWithExtras.token.content - illegalTags.set(illegalTag, (illegalTags.get(illegalTag) || 0) + 1) + entry = { + file, + location: where, + error: errorString, + illegalTag, + versions: [], + versionSet: new Set(), + } + byKey.set(key, entry) + wheres.set(where, (wheres.get(where) || 0) + 1) } - - corruptions.push({ file, location: where, error: errorString, illegalTag }) - wheres.set(where, (wheres.get(where) || 0) + 1) + entry.versionSet.add(version) } - for (const page of site.pageList) { - if (page.languageCode !== languageCode) continue - - const strings: string[][] = [ - ['title', page.title], - ['shortTitle', page.shortTitle || ''], - ['intro', page.intro || ''], - ['markdown', page.markdown], - ].filter(([, string]) => Boolean(string)) + try { + for (const page of site.pageList) { + if (page.languageCode !== languageCode) continue + + // Only render under versions the page is actually served in (intersected + // with the requested set) — a page is never scraped under a version it + // doesn't apply to, so corruptions there can't break indexing. + const pageVersions = versions.filter((v) => page.applicableVersions.includes(v)) + if (!pageVersions.length) continue + + const strings: string[][] = [ + ['title', page.title], + ['shortTitle', page.shortTitle || ''], + ['intro', page.intro || ''], + ['markdown', page.markdown], + ].filter(([, string]) => Boolean(string)) + + for (const [where, string] of strings) { + for (const version of pageVersions) { + try { + await engine.parseAndRender(string, buildRenderContext(languageCode, version)) + } catch (error) { + if (error instanceof Error) { + countError(error, where, page.relativePath, version) + } else { + throw error + } + } + } + } + } - for (const [where, string] of strings) { + for (const [relativePath, englishContent] of Array.from(englishReusables.entries())) { + let correctedContent: string try { - await engine.parseAndRender(string, context) + const filePath = path.join(language.dir, relativePath) + const rawContent = fs.readFileSync(filePath, 'utf8') + correctedContent = correctTranslatedContentStrings(rawContent, englishContent, { + code: languageCode, + relativePath, + }) } catch (error) { + // A missing translated file (ENOENT) just means this language hasn't + // translated this reusable yet — skip it silently. Any other error (e.g. + // a malformed reusable that breaks correctTranslatedContentStrings) is a + // real corruption: record it and keep going rather than crashing the run. if (error instanceof Error) { - countError(error, where, page.relativePath) - } else { - throw error + if (!error.message.startsWith('ENOENT')) { + countError(error, 'reusable', relativePath, versions[0]) + } + continue } + throw error } - } - } - for (const [relativePath, englishContent] of Array.from(englishReusables.entries())) { - try { - const filePath = path.join(language.dir, relativePath) - const rawContent = fs.readFileSync(filePath, 'utf8') - const correctedContent = correctTranslatedContentStrings(rawContent, englishContent, { - code: languageCode, - relativePath, - }) - await engine.parseAndRender(correctedContent, context) - } catch (error) { - if (error instanceof Error && error.message.startsWith('ENOENT')) { - continue - } else if (error instanceof Error) { - countError(error, 'reusable', relativePath) - } else { - throw error + for (const version of versions) { + try { + await engine.parseAndRender(correctedContent, buildRenderContext(languageCode, version)) + } catch (error) { + if (error instanceof Error) { + countError(error, 'reusable', relativePath, version) + } else { + throw error + } + } } } - } - const topIllegalTags = Array.from(illegalTags.entries()) - .sort((a, b) => b[1] - a[1]) - .slice(0, 10) - .map(([tag, count]) => ({ tag, count })) - - console.warn = originalWarn - - return { - language: languageCode, - languageName: language.name, - total: corruptions.length, - corruptions, - byLocation: Object.fromEntries(wheres), - topIllegalTags, + const corruptions: CorruptionEntry[] = Array.from(byKey.values()).map( + ({ versionSet, ...entry }) => ({ ...entry, versions: Array.from(versionSet).sort() }), + ) + + const topIllegalTags = Array.from(illegalTags.entries()) + .sort((a, b) => b[1] - a[1]) + .slice(0, 10) + .map(([tag, count]) => ({ tag, count })) + + return { + language: languageCode, + languageName: language.name, + total: corruptions.length, + corruptions, + byLocation: Object.fromEntries(wheres), + topIllegalTags, + } + } finally { + console.warn = originalWarn } } diff --git a/src/languages/tests/count-translation-corruptions.ts b/src/languages/tests/count-translation-corruptions.ts new file mode 100644 index 000000000000..b4117e82764e --- /dev/null +++ b/src/languages/tests/count-translation-corruptions.ts @@ -0,0 +1,37 @@ +import { describe, expect, test } from 'vitest' + +import { allVersions } from '@/versions/lib/all-versions' +import { resolveRequestedVersions } from '@/languages/scripts/count-translation-corruptions' + +// Pick a real GHES key at runtime rather than hard-coding one — allVersions +// only contains currently-supported releases, so a literal like +// `enterprise-server@3.16` would start failing once it rolls off support. +const someGhesVersion = Object.keys(allVersions).find((v) => v.startsWith('enterprise-server@'))! + +describe('resolveRequestedVersions', () => { + test('defaults to every version when no option is passed', () => { + expect(resolveRequestedVersions()).toEqual(Object.keys(allVersions)) + expect(resolveRequestedVersions('')).toEqual(Object.keys(allVersions)) + }) + + test('parses a comma-separated list and trims whitespace', () => { + expect(resolveRequestedVersions(`free-pro-team@latest, ${someGhesVersion}`)).toEqual([ + 'free-pro-team@latest', + someGhesVersion, + ]) + }) + + test('dedupes repeated versions, preserving first-seen order', () => { + expect( + resolveRequestedVersions(`free-pro-team@latest, ${someGhesVersion}, free-pro-team@latest`), + ).toEqual(['free-pro-team@latest', someGhesVersion]) + }) + + test('rejects versions that are not real allVersions keys', () => { + // `enterprise-server@latest` is a common mistake — it is not a real key. + expect(() => resolveRequestedVersions('enterprise-server@latest')).toThrow( + /Invalid version\(s\): enterprise-server@latest/, + ) + expect(() => resolveRequestedVersions('free-pro-team@latest,nope')).toThrow(/nope/) + }) +}) From 625b22bc5107e4ea9f1fd6dc765b6e6599a352bb Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Wed, 10 Jun 2026 12:06:07 -0700 Subject: [PATCH 3/5] Bump checkout and paths-filter off Node20 to Node24 (#61535) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: heiskr <1221423+heiskr@users.noreply.github.com> --- .github/actions/clone-translations/action.yml | 16 ++++++++-------- .github/actions/get-docs-early-access/action.yml | 2 +- .../workflows/triage-unallowed-contributions.yml | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/actions/clone-translations/action.yml b/.github/actions/clone-translations/action.yml index 1aa2e862f0ca..c139d16474af 100644 --- a/.github/actions/clone-translations/action.yml +++ b/.github/actions/clone-translations/action.yml @@ -11,56 +11,56 @@ runs: using: 'composite' steps: - name: Clone Spanish - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: repository: github/docs-internal.es-es token: ${{ inputs.token }} path: translations/es-es - name: Clone Japanese - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: repository: github/docs-internal.ja-jp token: ${{ inputs.token }} path: translations/ja-jp - name: Clone Portuguese - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: repository: github/docs-internal.pt-br token: ${{ inputs.token }} path: translations/pt-br - name: Clone Simplified Chinese - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: repository: github/docs-internal.zh-cn token: ${{ inputs.token }} path: translations/zh-cn - name: Clone Russian - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: repository: github/docs-internal.ru-ru token: ${{ inputs.token }} path: translations/ru-ru - name: Clone French - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: repository: github/docs-internal.fr-fr token: ${{ inputs.token }} path: translations/fr-fr - name: Clone Korean - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: repository: github/docs-internal.ko-kr token: ${{ inputs.token }} path: translations/ko-kr - name: Clone German - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: repository: github/docs-internal.de-de token: ${{ inputs.token }} diff --git a/.github/actions/get-docs-early-access/action.yml b/.github/actions/get-docs-early-access/action.yml index 65ef8c727564..671694fa9eb1 100644 --- a/.github/actions/get-docs-early-access/action.yml +++ b/.github/actions/get-docs-early-access/action.yml @@ -19,7 +19,7 @@ runs: run: npm run what-docs-early-access-branch - name: Clone - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: repository: github/docs-early-access token: ${{ inputs.token }} diff --git a/.github/workflows/triage-unallowed-contributions.yml b/.github/workflows/triage-unallowed-contributions.yml index 6cb0e0faa39a..f15768af2d65 100644 --- a/.github/workflows/triage-unallowed-contributions.yml +++ b/.github/workflows/triage-unallowed-contributions.yml @@ -26,7 +26,7 @@ jobs: uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Get files changed - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d + uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 id: filter with: # Base branch used to get changed files From 506f7133f53298649932c135919e6bf2d781cb15 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Wed, 10 Jun 2026 12:50:51 -0700 Subject: [PATCH 4/5] Replace deprecated-runtime PR automation actions (#61537) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/enterprise-dates.yml | 2 +- .github/workflows/move-ready-to-merge-pr.yaml | 19 ++++++++++++++----- .github/workflows/sync-graphql.yml | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/.github/workflows/enterprise-dates.yml b/.github/workflows/enterprise-dates.yml index b0fb9fe1f1ea..39b84b4bdea1 100644 --- a/.github/workflows/enterprise-dates.yml +++ b/.github/workflows/enterprise-dates.yml @@ -64,7 +64,7 @@ jobs: - if: ${{ steps.create-pull-request.outputs.pull-request-number }} name: Approve - uses: juliangruber/approve-pull-request-action@dcc4effb325c0b503408619918d56e40653dcc91 + uses: juliangruber/approve-pull-request-action@68fcc9a5a73b5641cadf757cf99d73720dcb05d0 # v2.1.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} number: ${{ steps.create-pull-request.outputs.pull-request-number }} diff --git a/.github/workflows/move-ready-to-merge-pr.yaml b/.github/workflows/move-ready-to-merge-pr.yaml index 590e1580766d..ffb43d09ffeb 100644 --- a/.github/workflows/move-ready-to-merge-pr.yaml +++ b/.github/workflows/move-ready-to-merge-pr.yaml @@ -23,12 +23,21 @@ jobs: }} runs-on: ubuntu-latest steps: - - name: move PR - uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 + - name: Add PR to the open source board + uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0 with: - project: Docs open source board - column: Triage - repo-token: ${{ secrets.DOCS_BOT_PAT_BASE }} + project-url: https://github.com/orgs/github/projects/2936 + github-token: ${{ secrets.DOCS_BOT_PAT_BASE }} + + - name: Move PR to Triage on the open source board + uses: github/update-project-action@af4f6083118f5080c89828b421ef598d1906fb60 # v4 + with: + github_token: ${{ secrets.DOCS_BOT_PAT_BASE }} + organization: github + project_number: 2936 + content_id: ${{ github.event.pull_request.node_id }} + field: Status + value: Triage - name: Check out repo uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 diff --git a/.github/workflows/sync-graphql.yml b/.github/workflows/sync-graphql.yml index 095b971dc205..02c4cb437794 100644 --- a/.github/workflows/sync-graphql.yml +++ b/.github/workflows/sync-graphql.yml @@ -67,7 +67,7 @@ jobs: - if: ${{ steps.create-pull-request.outputs.pull-request-number }} name: Approve - uses: juliangruber/approve-pull-request-action@dcc4effb325c0b503408619918d56e40653dcc91 + uses: juliangruber/approve-pull-request-action@68fcc9a5a73b5641cadf757cf99d73720dcb05d0 # v2.1.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} number: ${{ steps.create-pull-request.outputs.pull-request-number }} From 9b7f6004b7ae0085cd755d673572246ae4b96736 Mon Sep 17 00:00:00 2001 From: Driele Neves Ribeiro Date: Wed, 10 Jun 2026 15:52:44 -0400 Subject: [PATCH 5/5] Restore snapshot conditionals section to use-custom-images.md (#61507) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../larger-runners/use-custom-images.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/content/actions/how-tos/manage-runners/larger-runners/use-custom-images.md b/content/actions/how-tos/manage-runners/larger-runners/use-custom-images.md index f271150d6031..d7b11c9fe705 100644 --- a/content/actions/how-tos/manage-runners/larger-runners/use-custom-images.md +++ b/content/actions/how-tos/manage-runners/larger-runners/use-custom-images.md @@ -94,6 +94,24 @@ jobs: # Add any steps to download and setup any dependencies here ``` +### Conditionals + +The `snapshot` keyword supports conditional execution using the `if` keyword around the snapshot mapping. You can use conditions to control when an image snapshot is created. For example, the following job skips image creation for tag builds. + +```yaml +jobs: + build: + runs-on: my-image-generation-runner + snapshot: + if: {% raw %}${{ ! startsWith(github.ref, 'refs/tags/') }}{% endraw %} + image-name: my-custom-image + version: 2.* + steps: + # Add any steps to download and setup any dependencies here +``` + +For more information about the `if` keyword, see [AUTOTITLE](/actions/how-tos/write-workflows/choose-when-workflows-run/control-jobs-with-conditions). + ## Versioning When you generate custom images, {% data variables.product.github %} automatically assigns version numbers to help you manage updates and track image history.