Dashboard › publish › Session 1ANTt3tr95qH
1ANTt3tr95qHp2KdSPerform an independent, read-only correctness and security review of the current working tree changes in /home/byk/Code/getsentry/publish. Focus on the new request-digest attestation binding, workflow handoffs in publish.yml and ci-poller.yml, and cleanup behavior when PUBLISH_ARGS is missing. Do not edit files. Inspect exact current diff and relevant call sites/tests. Report only substantiated findings, ordered by severity, with file:line evidence. End exactly MERGE or DO-NOT-MERGE. If you find no issues, say so and identify residual testing gaps. This is a correctness-critical release workflow change; reason adversarially about races and GitHub data shapes.
File not found: /home/byk/Code/getsentry/publish/.jj
M .github/workflows/ci-poller.yml M .github/workflows/publish.yml M AGENTS.md M src/libs/__tests__/github.js M src/libs/github.js M src/modules/__tests__/details-from-context.js M src/modules/__tests__/update-issue.js M src/modules/details-from-context.js M src/modules/post-workflow-details.js M src/modules/process-end-state.js M src/modules/update-issue.js M src/publish/inputs.js M src/publish/post-result.js M src/publish/post-workflow-details.js M src/publish/update-issue.js ?? .lore.md ?? src/modules/__tests__/approval-attestation.js ?? src/modules/__tests__/approval-authorizer.js ?? src/modules/approval-attestation.js ?? src/modules/approval-authorizer.js ?? src/publish/__tests__/ ?? src/publish/authorize-approval.js ?? src/publish/current-accepted-event.js ?? src/publish/record-ci-ready-attestation.js ?? src/publish/validate-approval-attestation.js .github/workflows/ci-poller.yml | 140 ++++++++++++++++++++++++-- .github/workflows/publish.yml ...
<path>/home/byk/Code/getsentry/publish/.github/workflows/publish.yml</path>
<type>file</type>
<content>
1: name: Publish
2: on:
3: issues:
4: types: [labeled]
5:
6: concurrency:
7: # Use the issue title (e.g. "publish: getsentry/foo@1.2.3") so duplicate
8: # issues for the same repo@version share a concurrency group.
9: group: ${{ github.event.issue.title }}
10: cancel-in-progress: false
11:
12: permissions:
13: contents: read
14: issues: write
15: packages: write
16:
17: jobs:
18: # When accepted is added to a publish issue:
19: # - Add ci-pending (and remove ci-failed if retrying)
20: # - Enable the poller via CI_POLLER_HAS_PENDING=true
21: # - Comment on the issue
22: # - Trigger the poller immediately so we don't wait for the next cron tick
23: # The publish job below requires ci-ready, so it will not fire until the
24: # poller flips ci-pending β ci-ready (which also prevents publishing without
25: # CI verification in the ...
<path>/home/byk/Code/getsentry/publish/.github/workflows/publish.yml</path>
<type>file</type>
<content>
381: VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
382:
383: - name: Update completed targets and remove label
384: if: ${{ cancelled() || failure() }}
385: env:
386: PUBLISH_ARGS: ${{ steps.inputs.outputs.result }}
387: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
388: run: node .__publish__/src/publish/update-issue.js
389:
390: - name: Inform about cancellation
391: if: ${{ cancelled() }}
392: env:
393: PUBLISH_ARGS: ${{ steps.inputs.outputs.result }}
394: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
395: run: node .__publish__/src/publish/post-result.js cancelled
396:
397: - name: Inform about failure
398: if: ${{ failure() }}
399: env:
400: PUBLISH_ARGS: ${{ steps.inputs.outputs.result }}
401: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN ...
<path>/home/byk/Code/getsentry/publish/.github/workflows/ci-poller.yml</path> <type>file</type> <content> 1: name: CI Status Poller 2: 3: on: 4: schedule: 5: - cron: "*/5 * * * *" 6: workflow_dispatch: 7: inputs: 8: attempt: 9: description: Self-dispatch attempt counter (internal use) 10: required: false 11: default: "0" 12: 13: permissions: 14: contents: read 15: issues: write 16: 17: jobs: 18: check-ci: 19: runs-on: ubuntu-latest 20: environment: production 21: # Skip entirely (no runner provisioned) when there's nothing to check. 22: # Set to "true" by ci-pending.yml, reset to "false" here when done. 23: # Always allow workflow_dispatch for manual recovery. 24: if: vars.CI_POLLER_HAS_PENDING == 'true' || github.event_name == 'workflow_dispatch' 25: concurrency: 26: group: ci-status-poller 27: cancel-in-progress: false 28: steps: 29: - name: Get publish code 30: uses: ...
<path>/home/byk/Code/getsentry/publish/.github/workflows/ci-poller.yml</path>
<type>file</type>
<content>
400: with:
401: client-id: ${{ vars.CI_POLLER_APP_CLIENT_ID }}
402: private-key: ${{ secrets.CI_POLLER_APP_PRIVATE_KEY }}
403:
404: - name: Sync poller variable with pending issue state
405: if: always() && steps.poller-token.outcome == 'success'
406: env:
407: GH_TOKEN: ${{ steps.poller-token.outputs.token }}
408: run: |
409: if [[ "${{ steps.remaining.outputs.count }}" == "0" ]]; then
410: echo "All ci-pending issues resolved. Disabling poller."
411: gh variable set CI_POLLER_HAS_PENDING -R "$GITHUB_REPOSITORY" -b "false"
412: else
413: echo "Still pending issues. Ensuring poller stays enabled."
414: gh variable set CI_POLLER_HAS_PENDING -R "$GITHUB_REPOSITORY" -b "true"
415: fi
416:
417: # Self-dispatch for fast re-checking ...
<path>/home/byk/Code/getsentry/publish/src/modules/approval-attestation.js</path>
<type>file</type>
<content>
1: const crypto = require("crypto");
2:
3: const APPROVAL_ATTESTATION_PREFIX = "<!-- publish-approval ";
4: const CI_READY_ATTESTATION_PREFIX = "<!-- publish-ci-ready ";
5: const ATTESTATION_SUFFIX = " -->";
6:
7: function createAttestation(prefix, value) {
8: return `${prefix}${Buffer.from(JSON.stringify(value)).toString(
9: "base64url"
10: )}${ATTESTATION_SUFFIX}`;
11: }
12:
13: function requestDigest({ body, labels, title }) {
14: if (typeof body !== "string" || !Array.isArray(labels)) {
15: throw new Error("Invalid publish request");
16: }
17:
18: return crypto
19: .createHash("sha256")
20: .update(
21: JSON.stringify({
22: body,
23: dryRun: labels.some((label) => label.name === "dry-run"),
24: title,
25: })
26: )
27: .digest("hex");
28: }
29:
30: function createApprovalAttestation({ actor, eventId, ...
<path>/home/byk/Code/getsentry/publish/src/publish/authorize-approval.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const {
3: createApprovalAttestation,
4: currentAcceptedEvent,
5: } = require("../modules/approval-attestation.js");
6: const { authorizeApproval } = require("../modules/approval-authorizer.js");
7:
8: function getAutoApprovedRepositories() {
9: return new Set(
10: fs
11: .readFileSync("auto-approve-repos.txt", "utf8")
12: .split(/\r?\n/)
13: .filter(Boolean)
14: );
15: }
16:
17: async function getPermission({ owner, repository, username }) {
18: const response = await getGitHubResponse(
19: `repos/${encodeURIComponent(owner)}/${encodeURIComponent(
20: repository
21: )}/collaborators/${encodeURIComponent(username)}/permission`
22: );
23:
24: if (!response.ok) {
25: throw new Error(
26: `Could not retrieve ${username}'s permission for ${owner}/${repository}: GitHub returned ...
<path>/home/byk/Code/getsentry/publish/src/publish/current-accepted-event.js</path>
<type>file</type>
<content>
1: const { currentAcceptedEvent } = require("../modules/approval-attestation.js");
2: const { getAllPages } = require("./validate-approval-attestation.js");
3:
4: async function getCurrentAcceptedEvent({
5: getIssueEvents,
6: issueNumber,
7: repository,
8: }) {
9: const event = currentAcceptedEvent(
10: await getIssueEvents({ repository, issueNumber })
11: );
12:
13: if (!event) {
14: throw new Error("The issue has no current accepted label event");
15: }
16:
17: return event;
18: }
19:
20: async function main() {
21: for (const name of [
22: "APPROVAL_TOKEN",
23: "APPROVAL_ISSUE_NUMBER",
24: "APPROVAL_ISSUE_REPOSITORY",
25: ]) {
26: if (!process.env[name]) {
27: throw new Error(`No "${name}" environment variable found`);
28: }
29: }
30:
31: const event = await getCurrentAcceptedEvent({
32: getIssueEvents: ({ ...
<path>/home/byk/Code/getsentry/publish/src/publish/record-ci-ready-attestation.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const {
3: createCiReadyAttestation,
4: currentAcceptedEvent,
5: } = require("../modules/approval-attestation.js");
6: const {
7: getAllPages,
8: getGitHubResponse,
9: getIssue,
10: } = require("./validate-approval-attestation.js");
11:
12: async function getAuthenticatedLogin() {
13: const response = await getGitHubResponse("user");
14:
15: if (!response.ok) {
16: throw new Error(
17: `Could not retrieve the authenticated user: GitHub returned ${response.status}`
18: );
19: }
20:
21: const { login } = await response.json();
22:
23: if (typeof login !== "string") {
24: throw new Error("GitHub returned no authenticated user login");
25: }
26:
27: return login;
28: }
29:
30: async function recordCiReadyAttestation({
31: expectedAcceptedEvent,
32: getAuthenticatedLogin,
33: getIssue,
34: ...
<path>/home/byk/Code/getsentry/publish/src/publish/validate-approval-attestation.js</path>
<type>file</type>
<content>
1: const {
2: currentAcceptedEvent,
3: currentCiReadyEvent,
4: hasApprovalAttestation,
5: hasCiReadyAttestation,
6: } = require("../modules/approval-attestation.js");
7:
8: async function getGitHubResponse(path) {
9: return fetch(`https://api.github.com/${path}`, {
10: headers: {
11: Accept: "application/vnd.github+json",
12: Authorization: `Bearer ${process.env.APPROVAL_TOKEN}`,
13: "X-GitHub-Api-Version": "2026-03-10",
14: },
15: });
16: }
17:
18: async function getIssue({ repository, issueNumber }) {
19: const response = await getGitHubResponse(
20: `repos/${repository}/issues/${issueNumber}`
21: );
22:
23: if (!response.ok) {
24: throw new Error(
25: `Could not retrieve issue #${issueNumber}: GitHub returned ${response.status}`
26: );
27: }
28:
29: return response.json();
30: }
31:
32: async ...
Found 64 matches
/home/byk/Code/getsentry/publish/src/publish/record-ci-ready-attestation.js:
Line 3: createCiReadyAttestation,
Line 30: async function recordCiReadyAttestation({
Line 57: return createCiReadyAttestation({ acceptedEvent, ciReadyActor, issue });
Line 73: const attestation = await recordCiReadyAttestation({
Line 107: recordCiReadyAttestation,
/home/byk/Code/getsentry/publish/src/publish/validate-approval-attestation.js:
Line 55: async function validateApprovalAttestation({
Line 115: const valid = await validateApprovalAttestation({
Line 151: validateApprovalAttestation,
/home/byk/Code/getsentry/publish/src/publish/__tests__/record-ci-ready-attestation.js:
Line 7: recordCiReadyAttestation,
Line 10: describe("recordCiReadyAttestation", () => {
Line 15: recordCiReadyAttestation({
Line 41: requestDigest: expect.any(String),
Line 49: recordCiReadyAttestation({
Line 69: ...
<path>/home/byk/Code/getsentry/publish/src/modules/__tests__/approval-attestation.js</path>
<type>file</type>
<content>
1: import { describe, expect, test } from "vitest";
2:
3: const {
4: createApprovalAttestation,
5: createCiReadyAttestation,
6: currentAcceptedEvent,
7: hasApprovalAttestation,
8: hasCiReadyAttestation,
9: parseApprovalAttestation,
10: requestDigest,
11: } = require("../approval-attestation.js");
12:
13: function issue(title, { body = "", dryRun = false } = {}) {
14: return {
15: body,
16: labels: dryRun ? [{ name: "dry-run" }] : [],
17: title,
18: };
19: }
20:
21: describe("approval attestations", () => {
22: test("matches the latest accepted event to a trusted attestation", () => {
23: const title = "publish: getsentry/relay/py@1.2.3";
24: const publishIssue = issue(title);
25: const attestation = createApprovalAttestation({
26: actor: "contractor",
27: eventId: "200",
28: issue: publishIssue,
29: ...
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/validate-approval-attestation.js</path>
<type>file</type>
<content>
1: import { afterEach, describe, expect, test, vi } from "vitest";
2:
3: const {
4: validateApprovalAttestation,
5: } = require("../validate-approval-attestation.js");
6: const {
7: createApprovalAttestation,
8: createCiReadyAttestation,
9: } = require("../../modules/approval-attestation.js");
10:
11: afterEach(() => {
12: vi.restoreAllMocks();
13: });
14:
15: function jsonResponse(json) {
16: return { ok: true, json: vi.fn().mockResolvedValue(json) };
17: }
18:
19: function issue(title, { body = "", labels = [{ name: "accepted" }] } = {}) {
20: return { body, labels, title };
21: }
22:
23: describe("validateApprovalAttestation", () => {
24: test("accepts the current accepted event and its trusted attestation", async () => {
25: const title = "publish: getsentry/sentry-javascript@10.0.0";
26: const publishIssue = issue(title);
27: ...
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/record-ci-ready-attestation.js</path>
<type>file</type>
<content>
1: import { describe, expect, test, vi } from "vitest";
2:
3: const {
4: parseCiReadyAttestation,
5: } = require("../../modules/approval-attestation.js");
6: const {
7: recordCiReadyAttestation,
8: } = require("../record-ci-ready-attestation.js");
9:
10: describe("recordCiReadyAttestation", () => {
11: test("binds the current approval to the app that will add ci-ready", async () => {
12: const title = "publish: getsentry/sentry-javascript@10.0.0";
13:
14: await expect(
15: recordCiReadyAttestation({
16: getAuthenticatedLogin: vi
17: .fn()
18: .mockResolvedValue("sentry-internal-app[bot]"),
19: getIssue: vi.fn().mockResolvedValue({
20: body: "Merge target: main",
21: labels: [{ name: "accepted" }],
22: title,
23: }),
24: getIssueEvents: ...
<path>/home/byk/Code/getsentry/publish/src/modules/update-issue.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const {
3: TARGETS_SECTION_PARSER_REGEX,
4: TARGETS_PARSER_REGEX,
5: } = require("./details-from-context");
6:
7: async function updateTargets({ octokit, version, publishRepo, issue_number }) {
8: const CRAFT_STATE_FILE_PATH = `${process.env.GITHUB_WORKSPACE}/__repo__/.craft-publish-${version}.json`;
9:
10: if (!fs.existsSync(CRAFT_STATE_FILE_PATH)) {
11: return;
12: }
13:
14: const issueRequest = octokit.rest.issues.get({
15: ...publishRepo,
16: issue_number,
17: });
18:
19: const craftStateRequest = fs.promises
20: .readFile(CRAFT_STATE_FILE_PATH, { encoding: "utf-8" })
21: .then((data) => JSON.parse(data));
22:
23: const [{ data: issue }, craftState] = await Promise.all([
24: issueRequest,
25: craftStateRequest,
26: ]);
27:
28: const newIssueBody = transformIssueBody(craftState, issue.body);
29: ...
<path>/home/byk/Code/getsentry/publish/src/modules/__tests__/update-issue.js</path>
<type>file</type>
<content>
1: import { vi, describe, test, expect, beforeAll, beforeEach, it } from "vitest";
2: import fs from "fs";
3:
4: const { updateIssue, transformIssueBody } = require("../update-issue.js");
5:
6: let mockExistsSync;
7:
8: const updateTargetsArgs = {
9: inputs: { repo: "sentry", version: "21.3.1" },
10: context: {
11: runId: "1234",
12: repo: { owner: "getsentry", repo: "publish" },
13: payload: { issue: { number: "211" } },
14: },
15: octokit: {
16: rest: {
17: actions: {
18: getWorkflowRun: async () => ({
19: data: {
20: html_url: "https://github.com/getsentry/sentry/actions/runs/1234",
21: },
22: }),
23: },
24: issues: {
25: get: vi.fn(),
26: update: vi.fn(),
27: removeLabel: vi.fn(),
28: },
29: },
30: },
31: Sentry: {
32: Scope: class Scope ...
<path>/home/byk/Code/getsentry/publish/src/publish/inputs.js</path>
<type>file</type>
<content>
1: const github = require("@actions/github");
2: const core = require("@actions/core");
3: const { detailsFromContext } = require("../modules/details-from-context");
4:
5: async function inputs() {
6: const result = await detailsFromContext({
7: context: github.context,
8: });
9: core.setOutput("result", result);
10: }
11:
12: inputs();
(End of file - total 12 lines)
</content>
<path>/home/byk/Code/getsentry/publish/src/modules/details-from-context.js</path> <type>file</type> <content> 1: /** 2: * Matches the entire "Targets" section of a github publish issue body. 3: */ 4: const TARGETS_SECTION_PARSER_REGEX = 5: /^(?!### Targets$\s)(?: *- \[[ xX]\] \S+\s*$(?:\r?\n)?)+/m; 6: 7: /** 8: * Matches all targets of a github publish issue body in a section that was already matched and extracted with `TARGETS_PARSER_REGEX`. 9: * The "id" of the targets is captured within a capture group. 10: */ 11: const TARGETS_PARSER_REGEX = /^\s*- \[[ x]\] (\S+)/gim; 12: 13: /** 14: * Matches checked targets of a github publish issue body in a section that was already matched and extracted with `TARGETS_PARSER_REGEX`. 15: * The "id" of the targets is captured within a capture group. 16: */ 17: const CHECKED_TARGETS_PARSER_REGEX = /^\s*- \[x\] (\S+)/gim; 18: const PUBLISH_TITLE_REGEX = 19: /^publish: ...
<path>/home/byk/Code/getsentry/publish/src/modules/approval-authorizer.js</path>
<type>file</type>
<content>
1: const ALLOWED_ROLE_NAMES = new Set(["write", "maintain", "admin"]);
2: const AUTO_APPROVER = "getsantry[bot]";
3: const { parsePublishTitle } = require("./details-from-context.js");
4:
5: async function authorizeApproval({
6: actor,
7: issueTitle,
8: getPermission,
9: autoApprovedRepositories = new Set(),
10: }) {
11: const title = parsePublishTitle(issueTitle);
12:
13: if (!title) {
14: return { authorized: false, repository: null };
15: }
16:
17: const repository = `getsentry/${title.repo}`;
18: const releasePath = `${repository}${title.path || ""}`;
19:
20: if (actor === AUTO_APPROVER && autoApprovedRepositories.has(releasePath)) {
21: return { authorized: true, repository };
22: }
23:
24: if (actor === AUTO_APPROVER) {
25: return { authorized: false, repository };
26: }
27:
28: const { role_name } = await getPermission({
29: ...
/home/byk/Code/getsentry/publish/src/publish/__tests__/current-accepted-event.js /home/byk/Code/getsentry/publish/src/publish/__tests__/record-ci-ready-attestation.js /home/byk/Code/getsentry/publish/src/publish/__tests__/validate-approval-attestation.js /home/byk/Code/getsentry/publish/src/publish/__tests__/authorize-approval.js
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/current-accepted-event.js</path>
<type>file</type>
<content>
1: import { describe, expect, test, vi } from "vitest";
2:
3: const { getCurrentAcceptedEvent } = require("../current-accepted-event.js");
4:
5: describe("getCurrentAcceptedEvent", () => {
6: test("returns the latest accepted-label event", async () => {
7: await expect(
8: getCurrentAcceptedEvent({
9: getIssueEvents: vi.fn().mockResolvedValue([
10: {
11: actor: { login: "contractor" },
12: event: "labeled",
13: id: "100",
14: label: { name: "accepted" },
15: },
16: {
17: actor: { login: "contractor" },
18: event: "labeled",
19: id: "200",
20: label: { name: "accepted" },
21: },
22: ]),
23: issueNumber: "123",
24: repository: "getsentry/publish",
25: })
26: ).resolves.toEqual({ ...
<path>/home/byk/Code/getsentry/publish/src/publish/post-result.js</path>
<type>file</type>
<content>
1: const processEndState = require("../modules/process-end-state");
2: const { getGitHubToken } = require("../libs/github");
3: const github = require("@actions/github");
4:
5: const context = github.context;
6: const octokit = github.getOctokit(getGitHubToken());
7: const inputs = JSON.parse(process.env.PUBLISH_ARGS);
8: const args = process.argv.slice(2);
9: const status = args[0];
10:
11: processEndState({ context, octokit, inputs, status });
(End of file - total 11 lines)
</content>
diff --git a/.github/workflows/ci-poller.yml b/.github/workflows/ci-poller.yml
index a3db430..a557c2d 100644
--- a/.github/workflows/ci-poller.yml
+++ b/.github/workflows/ci-poller.yml
@@ -12,7 +12,7 @@ on:
permissions:
contents: read
- issues: read
+ issues: write
jobs:
check-ci:
@@ -26,6 +26,11 @@ jobs:
group: ci-status-poller
cancel-in-progress: false
steps:
+ - name: Get publish code
+ uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
# sentry-internal-app token for label changes on this repo.
# This token is what triggers publish.yml downstream β see
# https://github.com/getsentry/publish for why GITHUB_TOKEN can't.
@@ -92,12 +97,49 @@ jobs:
title=$(echo "$issue" | jq -r '.title')
body=$(echo "$issue" | jq -r '.body')
- # Parse repo and version from title: "publish: owner/repo[/path]@version"
- # Only take owner/repo (first two ...
No files found
<path>/home/byk/Code/getsentry/publish/.github/workflows</path> <type>directory</type> <entries> auto-approve.yml ci-poller.yml cocoapods-keepalive.yml publish.yml test.yml (5 entries) </entries>
<path>/home/byk/Code/getsentry/publish/.github/workflows/auto-approve.yml</path>
<type>file</type>
<content>
1: name: auto-approve non-sdks
2: on:
3: issues:
4: types: [opened]
5:
6: permissions:
7: contents: read
8:
9: jobs:
10: auto-approve:
11: runs-on: ubuntu-latest
12: if: |
13: (github.actor == 'sentry-release-bot[bot]' || github.actor == 'getsantry[bot]') &&
14: startsWith(github.event.issue.title, 'publish: ')
15: steps:
16: - uses: actions/checkout@v4
17: with:
18: sparse-checkout: auto-approve-repos.txt
19: sparse-checkout-cone-mode: false
20: - name: Get auth token
21: id: token
22: uses: actions/create-github-app-token@v3
23: with:
24: client-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
25: private-key: ${{ secrets.SENTRY_INTERNAL_APP_PRIVATE_KEY }}
26: - name: Auto-approve if repo is in the list
27: env:
28: GH_TOKEN: ${{ ...
yarn run v1.22.22 $ vitest run src/modules/__tests__/approval-attestation.js src/modules/__tests__/update-issue.js src/publish/__tests__/current-accepted-event.js src/publish/__tests__/record-ci-ready-attestation.js src/publish/__tests__/validate-approval-attestation.js --printConsoleTrace [1m[46m RUN [49m[22m [36mv4.0.17 [39m[90m/home/byk/Code/getsentry/publish[39m [32mβ[39m src/modules/__tests__/update-issue.js [2m([22m[2m6 tests[22m[2m)[22m[32m 12[2mms[22m[39m [32mβ[39m src/modules/__tests__/approval-attestation.js [2m([22m[2m5 tests[22m[2m)[22m[32m 22[2mms[22m[39m [32mβ[39m src/publish/__tests__/validate-approval-attestation.js [2m([22m[2m8 tests[22m[2m)[22m[32m 25[2mms[22m[39m [32mβ[39m src/publish/__tests__/record-ci-ready-attestation.js [2m([22m[2m3 tests[22m[2m)[22m[32m 9[2mms[22m[39m [32mβ[39m src/publish/__tests__/current-accepted-event.js [2m([22m[2m2 tests[22m[2m)[22m[32m 18[2mms[22m[39m [2m Test ...
GitHub - actions/checkout: Action for checking out a repo Β· GitHub
[Skip to content](#start-of-content)
## Navigation Menu
[](/)
[Sign in](/login?return_to=https%3A%2F%2Fgithub.com%2Factions%2Fcheckout)
Appearance settings
- Platform
- AI CODE CREATION
- [GitHub CopilotWrite better code with AI](https://github.com/features/copilot)
- [GitHub Copilot appDirect agents from issue to merge](https://github.com/features/ai/github-app)
- [MCP RegistryIntegrate external tools](https://github.com/mcp)
- DEVELOPER WORKFLOWS
- [ActionsAutomate any workflow](https://github.com/features/actions)
- [CodespacesInstant dev environments](https://github.com/features/codespaces)
- [IssuesPlan and track ...
# Events that trigger workflows You can configure your workflows to run when specific activity on GitHub happens, at a scheduled time, or when an event outside of GitHub occurs. ## About events that trigger workflows Workflow triggers are events that cause a workflow to run. For more information about how to use workflow triggers, see [Triggering a workflow](/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow). Some events have multiple activity types. For these events, you can specify which activity types will trigger a workflow run. For more information about what each activity type means, see [Webhook events and payloads](/en/webhooks/webhook-events-and-payloads). > \[!NOTE] > Not all webhook events trigger workflows. Like GitHub Actions workflows, agentic workflows can be triggered by repository events and schedules. ...
Found 6 matches /home/byk/.local/share/opencode/tool-output/tool_072f5bc0c001aTaZLeL0xajaKG: Line 452: > * When a pull request is created or updated by a workflow using `GITHUB_TOKEN`, `pull_request` events with the `opened`, `synchronize`, or `reopened` activity types create workflow runs that require approval. A user with write access to the repository can approve these runs from the pull request page. With the exception of `workflow_dispatch` and `repository_dispatch`, other `GITHUB_TOKEN`-triggered events do not create workflow runs at all. Line 1157: ## `workflow_dispatch` Line 1161: | [workflow\_dispatch](/en/webhooks/webhook-events-and-payloads#workflow_dispatch) | Not applicable | Last commit on the `GITHUB_REF` branch or tag | Branch or tag that received dispatch | Line 1166: To enable a workflow to be triggered manually, you need to configure the `workflow_dispatch` event. ...
yarn run v1.22.22 $ vitest run --printConsoleTrace [1m[46m RUN [49m[22m [36mv4.0.17 [39m[90m/home/byk/Code/getsentry/publish[39m [32mβ[39m src/modules/__tests__/post-workflow-details.js [2m([22m[2m1 test[22m[2m)[22m[32m 13[2mms[22m[39m [32mβ[39m src/publish/__tests__/validate-approval-attestation.js [2m([22m[2m8 tests[22m[2m)[22m[32m 24[2mms[22m[39m [32mβ[39m src/modules/__tests__/approval-attestation.js [2m([22m[2m5 tests[22m[2m)[22m[32m 21[2mms[22m[39m [32mβ[39m src/publish/__tests__/current-accepted-event.js [2m([22m[2m2 tests[22m[2m)[22m[32m 25[2mms[22m[39m [32mβ[39m src/modules/__tests__/process-end-state.js [2m([22m[2m4 tests[22m[2m)[22m[32m 60[2mms[22m[39m [32mβ[39m src/modules/__tests__/approval-authorizer.js [2m([22m[2m14 tests[22m[2m)[22m[32m 16[2mms[22m[39m [32mβ[39m src/publish/__tests__/authorize-approval.js [2m([22m[2m3 tests[22m[2m)[22m[32m 14[2mms[22m[39m [32mβ[39m ...
HIGH β workflow_dispatch can execute a non-default ref with app tokens.
.github/workflows/ci-poller.yml:6,29-32,61-62,102-105
workflow_dispatch permits dispatching a branch/tag ref, and actions/checkout defaults to that event ref. A user who can dispatch a branch containing modified src/publish/* code can run it after the job exposes both GitHub App tokens through GH_TOKEN and RELEASE_TOKEN. persist-credentials: false does not protect environment variables. Pin this checkout to the trusted default branch/SHA while retaining manual dispatch.
All 53 tests pass. The new unit test covers missing parsed inputs at the module level, but no workflow-level test covers trusted-ref checkout for manual dispatch.
DO-NOT-MERGE
Date: Sep 5, 2026 * π‘ (18:59) `.github/workflows/ci-poller.yml` lines 404β415 include a `Sync poller variable with pending issue state` step running `always()` after successful `poller-token`; it uses `GH_TOKEN: ${{ steps.poller-token.outputs.token }}` and sets repository variable `CI_POLLER_HAS_PENDING` to `"false"` when `steps.remaining.outputs.count == "0"` or `"true"` otherwise via `gh variaβ¦
Date: Sep 5, 2026 * π΄ (18:59) User stated the ci-poller must bind each polling cycle to the current accepted-label event before inspecting CI and must never move a release to `ci-ready` after that approval event changes. * π΄ (18:59) User stated that after the final CI check, approval must be revalidated so a renamed or re-approved issue never reaches `ci-ready`. * π΄ (18:59) User stated the ci-β¦
Date: Sep 5, 2026 * π‘ [requested-security-review] (18:58) User requested an independent, read-only correctness and security review of current working-tree changes in `/home/byk/Code/getsentry/publish`, focusing on new request-digest attestation binding, workflow handoffs in `publish.yml` and `ci-poller.yml`, and cleanup behavior when `PUBLISH_ARGS` is missing. * π΄ [enforced-read-only-review] (1β¦