DashboardpublishSession 1ANTt3tr95qH

Session 1ANTt3tr95qH

Full ID: 1ANTt3tr95qHp2KdS
Messages: 34
Distillations: 3
Time range: 2026-09-05 18:58:43 — 2026-09-05 19:08:01

Conversation (34 messages)

Perform 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.

gateway · 2026-09-05 18:58:43
result
File not found: /home/byk/Code/getsentry/publish/.jj
gateway · 2026-09-05 18:59:30
result
 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         ...
gateway · 2026-09-05 18:59:34
result
<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 ...
gateway · 2026-09-05 18:59:38
result
<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 ...
gateway · 2026-09-05 18:59:44
result
<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: ...
gateway · 2026-09-05 18:59:47
result
<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 ...
gateway · 2026-09-05 18:59:49
result
<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, ...
gateway · 2026-09-05 18:59:52
result
<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 ...
gateway · 2026-09-05 18:59:54
result
<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: ({ ...
gateway · 2026-09-05 18:59:57
result
<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:  ...
gateway · 2026-09-05 19:00:00
result
<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 ...
gateway · 2026-09-05 19:00:03
result
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:       ...
gateway · 2026-09-05 19:00:05
result
<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:     ...
gateway · 2026-09-05 19:00:08
result
<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:  ...
gateway · 2026-09-05 19:00:11
result
<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: ...
gateway · 2026-09-05 19:00:15
result
<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: ...
gateway · 2026-09-05 19:00:17
result
<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 ...
gateway · 2026-09-05 19:00:29
result
<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>
gateway · 2026-09-05 19:00:32
result
<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: ...
gateway · 2026-09-05 19:00:40
result
<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:  ...
gateway · 2026-09-05 19:00:44
result
/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
gateway · 2026-09-05 19:00:48
result
<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({ ...
gateway · 2026-09-05 19:01:05
result
<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>
gateway · 2026-09-05 19:01:17
result
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 ...
gateway · 2026-09-05 19:02:35
result
No files found
gateway · 2026-09-05 19:02:38
result
<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>
gateway · 2026-09-05 19:02:47
result
<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: ${{ ...
gateway · 2026-09-05 19:03:11
result
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

 RUN  v4.0.17 /home/byk/Code/getsentry/publish

 βœ“ src/modules/__tests__/update-issue.js (6 tests) 12ms
 βœ“ src/modules/__tests__/approval-attestation.js (5 tests) 22ms
 βœ“ src/publish/__tests__/validate-approval-attestation.js (8 tests) 25ms
 βœ“ src/publish/__tests__/record-ci-ready-attestation.js (3 tests) 9ms
 βœ“ src/publish/__tests__/current-accepted-event.js (2 tests) 18ms

 Test ...
gateway · 2026-09-05 19:04:36
result
                                                                                                          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 ...
gateway · 2026-09-05 19:05:01
result
# 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. ...
gateway · 2026-09-05 19:05:17
result
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. ...
gateway · 2026-09-05 19:05:24
result
yarn run v1.22.22
$ vitest run --printConsoleTrace

 RUN  v4.0.17 /home/byk/Code/getsentry/publish

 βœ“ src/modules/__tests__/post-workflow-details.js (1 test) 13ms
 βœ“ src/publish/__tests__/validate-approval-attestation.js (8 tests) 24ms
 βœ“ src/modules/__tests__/approval-attestation.js (5 tests) 21ms
 βœ“ src/publish/__tests__/current-accepted-event.js (2 tests) 25ms
 βœ“ src/modules/__tests__/process-end-state.js (4 tests) 60ms
 βœ“ src/modules/__tests__/approval-authorizer.js (14 tests) 16ms
 βœ“ src/publish/__tests__/authorize-approval.js (3 tests) 14ms
 βœ“ ...
gateway · 2026-09-05 19:08:01

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

gpt-5.6-terra · 2026-09-05 19:08:01 · ~169 tokens

Distillations (3)

Gen 0 2026-09-05 19:04:39 · 419 tokens

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…

Gen 0 2026-09-05 19:04:29 · 890 tokens

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-…

Gen 0 2026-09-05 18:59:56 · 252 tokens

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…