DashboardpublishSession 1BpBuVO0t33J

Session 1BpBuVO0t33J

Full ID: 1BpBuVO0t33JPd6VY
Messages: 304
Distillations: 50
Time range: 2026-09-09 09:29:36 — 2026-09-09 16:46:37

Conversation (304 messages)

Perform an independent read-only correctness/security review of the exact current worktree. Do not edit. Scope decision: organization-wide availability of SENTRY_INTERNAL_APP_PRIVATE_KEY and SENTRY_RELEASE_BOT_PRIVATE_KEY is an explicitly accepted pre-existing organization Security issue and is NOT a merge blocker for this repository change; still note only if this change worsens it. Review all current changed files and tests, especially approval provenance, direct-bot denial, human self-approval denial, live request/event/digest binding, CI-ready transition (must always add ci-ready after CI and never after mutation/reapproval), pre-Craft fence, cleanup, manual workflow_dispatch relay and workflow_run main/success gate, repository dispatch input handling, protected production environment usage, immutable action and Craft pins. Verify test/lint/diff evidence yourself. Return non-empty findings first, classified MUST-FIX/CONCERN/PASS with exact current file:line evidence, then tests. End exactly MERGE or DO-NOT-MERGE. If blocked, begin BLOCKED with exact tool/error and end DO-NOT-MERGE.

gateway · 2026-09-09 09:29:36
result
<path>/home/byk/Code/getsentry/publish</path>
<type>directory</type>
<entries>
.eslintrc.js
.git/
.github/
.gitignore
.lore.md
AGENTS.md
auto-approve-repos.txt
docs/
LICENSE
node_modules/
package.json
README.md
src/
vitest.config.js
yarn.lock

(15 entries)
</entries>
gateway · 2026-09-09 09:29:54
result
## main...origin/main [behind 1]
 M .github/workflows/auto-approve.yml
 M .github/workflows/ci-poller.yml
 M .github/workflows/cocoapods-keepalive.yml
 M .github/workflows/publish.yml
 M .github/workflows/test.yml
 M AGENTS.md
 M README.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
?? .github/workflows/ci-poller-dispatch.yml
?? .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
?? ...
gateway · 2026-09-09 09:30:11
result
 .github/workflows/auto-approve.yml            |  23 ++-
 .github/workflows/ci-poller.yml               | 195 +++++++++++++++++++++++---
 .github/workflows/cocoapods-keepalive.yml     |   3 +-
 .github/workflows/publish.yml                 | 139 ++++++++++++++++--
 .github/workflows/test.yml                    |   6 +-
 AGENTS.md                                     |   6 +
 README.md                                     |   4 +-
 src/libs/__tests__/github.js                  |   2 +-
 src/libs/github.js                            |   6 +-
 src/modules/__tests__/details-from-context.js |  22 ++-
 src/modules/__tests__/update-issue.js         |  28 ++++
 src/modules/details-from-context.js           |  17 ++-
 src/modules/post-workflow-details.js          |   4 +-
 src/modules/process-end-state.js              |   4 +-
 src/modules/update-issue.js                   |  42 +++---
 src/publish/inputs.js                         |  17 ++-
 src/publish/post-result.js                    |   8 ...
gateway · 2026-09-09 09:30:23
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:   issues: write
9: 
10: jobs:
11:   auto-approve:
12:     runs-on: ubuntu-latest
13:     environment: production
14:     if: |
15:       (github.actor == 'sentry-release-bot[bot]' || github.actor == 'getsantry[bot]') &&
16:       startsWith(github.event.issue.title, 'publish: ')
17:     steps:
18:       - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
19:         with:
20:           sparse-checkout: auto-approve-repos.txt
21:           sparse-checkout-cone-mode: false
22:       - name: Get auth token
23:         id: token
24:         uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
25:         with:
26:           client-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
27:           private-key: ${{ ...
gateway · 2026-09-09 09:30:35
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:   repository_dispatch:
7:     types: [ci-poller]
8:   workflow_run:
9:     workflows: [Run CI Status Poller]
10:     types: [completed]
11: 
12: permissions:
13:   contents: read
14:   issues: write
15: 
16: jobs:
17:   check-ci:
18:     runs-on: ubuntu-latest
19:     environment: production
20:     # Skip entirely (no runner provisioned) when there's nothing to check.
21:     # Set to "true" by ci-pending.yml, reset to "false" here when done.
22:     # repository_dispatch and a successful main-branch run of the protected
23:     # workflow_dispatch relay are the trusted manual-recovery paths.
24:     if: >-
25:       vars.CI_POLLER_HAS_PENDING == 'true'
26:       || github.event_name == 'repository_dispatch'
27:       || (
28:       github.event_name == 'workflow_run'
29:       && ...
gateway · 2026-09-09 09:30:48
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/ci-poller-dispatch.yml</path>
<type>file</type>
<content>
1: name: Run CI Status Poller
2: 
3: on:
4:   workflow_dispatch:
5: 
6: permissions: {}
7: 
8: jobs:
9:   dispatch:
10:     # production only permits deployments from main. Repository secrets must
11:     # never be used by this workflow; protected environment secrets enforce
12:     # that an arbitrary dispatched ref cannot add them.
13:     environment: production
14:     runs-on: ubuntu-latest
15:     steps:
16:       - run: ":"

(End of file - total 16 lines)
</content>
gateway · 2026-09-09 09:30:55
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-09 09:31:02
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/cocoapods-keepalive.yml</path>
<type>file</type>
<content>
1: name: CocoaPods Token Keep-Alive
2: 
3: on:
4:   schedule:
5:     # CocoaPods sessions currently expire after 3 days of inactivity (VALIDITY_LENGTH)
6:     # This runs every day at midnight UTC to keep the current token alive
7:     # See: https://github.com/CocoaPods/trunk.cocoapods.org/blob/a1869790e9ae2229b6985b6af4532da814202558/app/models/session.rb#L9
8:     - cron: "0 0 * * *" # Runs every day at midnight UTC
9: 
10: permissions:
11:   contents: read
12: 
13: jobs:
14:   keepalive:
15:     name: Refresh CocoaPods Session
16:     runs-on: macos-15
17:     environment: production
18:     env:
19:       COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
20: 
21:     steps:
22:       - name: Install CocoaPods
23:         run: gem install cocoapods
24:       - name: Print CocoaPods Version
25:         run: pod --version
26:       - name: Refresh CocoaPods ...
gateway · 2026-09-09 09:31:11
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/test.yml</path>
<type>file</type>
<content>
1: name: Test
2: on:
3:   push:
4:     branches:
5:       - main
6:   pull_request:
7: 
8: permissions:
9:   contents: read
10: 
11: defaults:
12:   run:
13:     shell: bash
14: jobs:
15:   unit-test:
16:     runs-on: ubuntu-latest
17:     name: unit tests
18:     steps:
19:       - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803
20:       - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38
21:         with:
22:           node-version: 24
23:       - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
24:         id: cache
25:         with:
26:           path: node_modules
27:           key: ${{ runner.os }}-node_modules-${{ hashFiles('package.json', 'yarn.lock') }}
28: 
29:       - name: Install Dependencies
30:         if: steps.cache.outputs.cache-hit != 'true'
31:         run: yarn install --frozen-lockfile
32: 
33:       - name: Test
34:    ...
gateway · 2026-09-09 09:31:17
result
/home/byk/Code/getsentry/publish/src/publish/__tests__/workflow-action-pinning.js
/home/byk/Code/getsentry/publish/src/publish/__tests__/record-auto-approval-attestation.js
/home/byk/Code/getsentry/publish/src/publish/__tests__/publish-workflow.js
/home/byk/Code/getsentry/publish/src/publish/__tests__/auto-approval-workflow.js
/home/byk/Code/getsentry/publish/src/publish/__tests__/ci-poller-workflow.js
/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-09 09:31:27
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 AUTO_APPROVAL_ATTESTATION_PREFIX = "<!-- publish-auto-approval ";
5: const CI_READY_ATTESTATION_PREFIX = "<!-- publish-ci-ready ";
6: const ATTESTATION_SUFFIX = " -->";
7: 
8: function createAttestation(prefix, value) {
9:   return `${prefix}${Buffer.from(JSON.stringify(value)).toString(
10:     "base64url"
11:   )}${ATTESTATION_SUFFIX}`;
12: }
13: 
14: function requestDigest({ body, labels, title }) {
15:   if (typeof body !== "string" || !Array.isArray(labels)) {
16:     throw new Error("Invalid publish request");
17:   }
18: 
19:   return crypto
20:     .createHash("sha256")
21:     .update(
22:       JSON.stringify({
23:         body,
24:         dryRun: labels.some((label) => label.name === "dry-run"),
25:         title,
26:       })
27:     )
28:     ...
gateway · 2026-09-09 09:31:41
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 { parsePublishTitle } = require("./details-from-context.js");
3: 
4: const AUTO_APPROVAL_LABELER = "sentry-internal-app[bot]";
5: const AUTO_APPROVERS = new Set(["getsantry[bot]", "sentry-release-bot[bot]"]);
6: 
7: function isAutoApprovedRepository({
8:   actor,
9:   autoApprovedRepositories,
10:   issueTitle,
11: }) {
12:   if (!AUTO_APPROVERS.has(actor)) {
13:     return false;
14:   }
15: 
16:   const title = parsePublishTitle(issueTitle);
17: 
18:   if (!title) {
19:     return false;
20:   }
21: 
22:   return autoApprovedRepositories.has(
23:     `getsentry/${title.repo}${title.path || ""}`
24:   );
25: }
26: 
27: async function authorizeApproval({ actor, issueTitle, getPermission }) {
28:   const title = parsePublishTitle(issueTitle);
29: 
30:   if (!title) {
31:     return { authorized: ...
gateway · 2026-09-09 09:31:54
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:   hasAutoApprovalAttestation,
6: } = require("../modules/approval-attestation.js");
7: const {
8:   AUTO_APPROVAL_LABELER,
9:   AUTO_APPROVERS,
10:   authorizeApproval,
11:   isAutoApprovedRepository,
12: } = require("../modules/approval-authorizer.js");
13: 
14: function getAutoApprovedRepositories() {
15:   return new Set(
16:     fs
17:       .readFileSync("auto-approve-repos.txt", "utf8")
18:       .split(/\r?\n/)
19:       .filter(Boolean)
20:   );
21: }
22: 
23: async function getPermission({ owner, repository, username }) {
24:   const response = await getGitHubResponse(
25:     `repos/${encodeURIComponent(owner)}/${encodeURIComponent(
26:       repository
27:     )}/collaborators/${encodeURIComponent(username)}/permission`
28:   );
29: 
30:   if (!response.ok) {
31:     ...
gateway · 2026-09-09 09:32:02
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-09 09:32:09
result
<path>/home/byk/Code/getsentry/publish/src/publish/record-auto-approval-attestation.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const {
3:   createAutoApprovalAttestation,
4: } = require("../modules/approval-attestation.js");
5: const { getIssue } = require("./validate-approval-attestation.js");
6: 
7: async function recordAutoApprovalAttestation({
8:   autoApprover,
9:   getIssue,
10:   issueNumber,
11:   issueTitle,
12:   repository,
13: }) {
14:   const issue = await getIssue({ repository, issueNumber });
15: 
16:   if (
17:     issue.state !== "open" ||
18:     issue.title !== issueTitle ||
19:     issue.user?.login?.toLowerCase() !== autoApprover.toLowerCase()
20:   ) {
21:     throw new Error("The automated approval request changed before approval");
22:   }
23: 
24:   return createAutoApprovalAttestation({ autoApprover, issue });
25: }
26: 
27: async function main() {
28:   for (const name of [
29:     "GITHUB_OUTPUT",
30:     "APPROVAL_TOKEN",
31:     ...
gateway · 2026-09-09 09:32:21
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-09 09:32:27
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-09 09:33:56
result
rg: regex parse error:
    (?:\buses:\s*[^\s]+@(?![0-9a-f]{40}(?:\s|$)|sha256:[0-9a-f]{64}(?:\s|$)))
                        ^^^
error: look-around, including look-ahead and look-behind, is not supported

Consider enabling PCRE2 with the --pcre2 flag, which can handle backreferences
and look-around.
gateway · 2026-09-09 09:34:09
result
Found 18 matches
/home/byk/Code/getsentry/publish/.github/workflows/publish.yml:
  Line 36:         uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803

  Line 42:         uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1

  Line 52:         uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1

  Line 149:         uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1

  Line 192:         uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803

  Line 223:         uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38

  Line 268:         uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1

  Line 274:       - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803

  Line 353:       - uses: ...
gateway · 2026-09-09 09:34:26
result
<path>/home/byk/Code/getsentry/publish/src/libs/github.js</path>
<type>file</type>
<content>
1: /**
2:  * This is a helper function that throws a useful error message if the
3:  * workflow environment is not configured correctly.
4:  *
5:  * @returns string
6:  */
7: function getGitHubToken() {
8:   if (!process.env.GITHUB_TOKEN) {
9:     throw new Error(
10:       'No "GITHUB_TOKEN" environment variable found. ' +
11:         "Please ensure the workflow is configured correctly"
12:     );
13:   }
14:   return process.env.GITHUB_TOKEN;
15: }
16: 
17: module.exports = {
18:   getGitHubToken,
19: };

(End of file - total 19 lines)
</content>
gateway · 2026-09-09 09:34:34
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-09 09:34:41
result
<path>/home/byk/Code/getsentry/publish/src/modules/post-workflow-details.js</path>
<type>file</type>
<content>
1: async function postWorkflowDetails({ context, octokit }) {
2:   const { repo: publishRepo, runId: run_id } = context;
3:   const { number: issue_number } = context.payload.issue;
4: 
5:   const workflowInfo = (
6:     await octokit.rest.actions.getWorkflowRun({
7:       ...publishRepo,
8:       run_id,
9:     })
10:   ).data;
11: 
12:   return octokit.rest.issues.createComment({
13:     ...publishRepo,
14:     issue_number,
15:     body: `Publishing: [run#${run_id}](${workflowInfo.html_url})`,
16:   });
17: }
18: 
19: module.exports = postWorkflowDetails;

(End of file - total 19 lines)
</content>
gateway · 2026-09-09 09:34:46
result
<path>/home/byk/Code/getsentry/publish/src/modules/process-end-state.js</path>
<type>file</type>
<content>
1: const Sentry = require("@sentry/node");
2: 
3: async function processEndState({ context, octokit, inputs, status }) {
4:   const { repo, version } = inputs;
5:   const { repo: publishRepo, runId: run_id } = context;
6:   const { number: issue_number } = context.payload.issue;
7:   const workflowInfo = (
8:     await octokit.rest.actions.getWorkflowRun({
9:       ...publishRepo,
10:       run_id,
11:     })
12:   ).data;
13: 
14:   const details = {
15:     repo,
16:     version,
17:     publishRepo,
18:     run_id,
19:     issue_number,
20:     workflowInfo,
21:     status,
22:   };
23: 
24:   await postIssueComment({
25:     octokit,
26:     details,
27:   });
28: 
29:   if (status === "success") {
30:     await octokit.rest.issues.update({
31:       ...publishRepo,
32:       issue_number,
33:       state: "closed",
34:     });
35:   }
36: 
37:   await reportSession({ ...
gateway · 2026-09-09 09:35:01
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-09 09:35:37
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-09 09:35:44
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-09 09:35:52
result
<path>/home/byk/Code/getsentry/publish/src/publish/post-workflow-details.js</path>
<type>file</type>
<content>
1: const postWorkflowDetails = require("../modules/post-workflow-details.js");
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: 
8: postWorkflowDetails({ context, octokit });

(End of file - total 8 lines)
</content>
gateway · 2026-09-09 09:35:58
result
<path>/home/byk/Code/getsentry/publish/src/publish/update-issue.js</path>
<type>file</type>
<content>
1: const { updateIssue } = require("../modules/update-issue.js");
2: const { getGitHubToken } = require("../libs/github");
3: const github = require("@actions/github");
4: 
5: async function main() {
6:   const context = github.context;
7:   const octokit = github.getOctokit(getGitHubToken());
8:   let inputs;
9: 
10:   if (process.env.PUBLISH_ARGS) {
11:     try {
12:       inputs = JSON.parse(process.env.PUBLISH_ARGS);
13:     } catch {
14:       console.warn("Could not parse publish inputs; skipping target update");
15:     }
16:   }
17: 
18:   await updateIssue({ context, octokit, inputs });
19: }
20: 
21: if (require.main === module) {
22:   main().catch((error) => {
23:     console.error(error);
24:     process.exitCode = 1;
25:   });
26: }
27: 
28: module.exports = { main };

(End of file - total 28 lines)
</content>
gateway · 2026-09-09 09:36:25
result
diff --git a/.github/workflows/auto-approve.yml b/.github/workflows/auto-approve.yml
index d9763c7..b2b489b 100644
--- a/.github/workflows/auto-approve.yml
+++ b/.github/workflows/auto-approve.yml
@@ -5,25 +5,44 @@ on:
 
 permissions:
   contents: read
+  issues: write
 
 jobs:
   auto-approve:
     runs-on: ubuntu-latest
+    environment: production
     if: |
       (github.actor == 'sentry-release-bot[bot]' || github.actor == 'getsantry[bot]') &&
       startsWith(github.event.issue.title, 'publish: ')
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
         with:
           sparse-checkout: auto-approve-repos.txt
           sparse-checkout-cone-mode: false
       - name: Get auth token
         id: token
-        uses: actions/create-github-app-token@v3
+        uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
         with:
           client-id: ${{ vars.SENTRY_INTERNAL_APP_ID ...
gateway · 2026-09-09 09:36:39
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-09 09:36:49
result
<path>/home/byk/Code/getsentry/publish/src/modules/__tests__/approval-authorizer.js</path>
<type>file</type>
<content>
1: import { describe, expect, test, vi } from "vitest";
2: 
3: const { authorizeApproval } = require("../approval-authorizer.js");
4: 
5: const issueTitle = "publish: getsentry/sentry-javascript@10.0.0";
6: 
7: describe("authorizeApproval", () => {
8:   test.each(["getsantry[bot]", "sentry-release-bot[bot]"])(
9:     "rejects direct approval by %s without a target repository lookup",
10:     async (actor) => {
11:       const getPermission = vi.fn();
12: 
13:       await expect(
14:         authorizeApproval({
15:           actor,
16:           issueTitle,
17:           getPermission,
18:           autoApprovedRepositories: new Set(["getsentry/sentry-javascript"]),
19:         })
20:       ).resolves.toEqual({
21:         authorized: false,
22:         repository: "getsentry/sentry-javascript",
23:       });
24: 
25:       ...
gateway · 2026-09-09 09:36:55
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/authorize-approval.js</path>
<type>file</type>
<content>
1: import { afterEach, describe, expect, test, vi } from "vitest";
2: import fs from "fs";
3: const {
4:   createAutoApprovalAttestation,
5: } = require("../../modules/approval-attestation.js");
6: const {
7:   getAutoApprovedRepositories,
8:   main,
9: } = require("../authorize-approval.js");
10: 
11: const originalEnvironment = { ...process.env };
12: 
13: afterEach(() => {
14:   process.env = { ...originalEnvironment };
15:   vi.restoreAllMocks();
16:   vi.resetModules();
17: });
18: 
19: function jsonResponse(json) {
20:   return { ok: true, json: vi.fn().mockResolvedValue(json) };
21: }
22: 
23: async function runAuthorization({ actor, issueTitle, responses }) {
24:   process.env.GITHUB_OUTPUT = "/tmp/github-output";
25:   process.env.APPROVAL_TOKEN = "release-bot-token";
26:   process.env.APPROVAL_ACTOR = actor;
27:   process.env.APPROVAL_ISSUE_NUMBER = ...
gateway · 2026-09-09 09:37:02
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(
20:   title,
21:   { body = "", labels = [{ name: "accepted" }], state = "open" } = {}
22: ) {
23:   return { body, labels, state, title };
24: }
25: 
26: describe("validateApprovalAttestation", () => {
27:   test("accepts the current accepted event and its trusted attestation", async () => {
28:     const title = "publish: getsentry/sentry-javascript@10.0.0";
29:    ...
gateway · 2026-09-09 09:37:06
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:           state: "open",
23:           title,
24:         }),
25:         ...
gateway · 2026-09-09 09:37:16
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-09 09:37:25
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/ci-poller-workflow.js</path>
<type>file</type>
<content>
1: import { readFileSync } from "fs";
2: import { describe, expect, test } from "vitest";
3: 
4: const workflow = readFileSync(".github/workflows/ci-poller.yml", "utf8");
5: const manualDispatchWorkflow = readFileSync(
6:   ".github/workflows/ci-poller-dispatch.yml",
7:   "utf8"
8: );
9: 
10: describe("CI poller workflow", () => {
11:   test("checks out trusted code before requesting app tokens", () => {
12:     expect(workflow).toMatch(
13:       /- name: Get publish code\n\s+uses: actions\/checkout@[a-f0-9]{40}\n\s+with:\n(?:\s+#.*\n)*\s+ref: \$\{\{ github\.event\.repository\.default_branch \}\}/
14:     );
15: 
16:     expect(workflow.indexOf("Get publish code")).toBeLessThan(
17:       workflow.indexOf("Get auth token")
18:     );
19:   });
20: 
21:   test("uses a protected workflow dispatch relay for manual recovery", () => {
22:     ...
gateway · 2026-09-09 09:37:41
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/publish-workflow.js</path>
<type>file</type>
<content>
1: import { readFileSync } from "fs";
2: import { describe, expect, test } from "vitest";
3: 
4: const workflow = readFileSync(".github/workflows/publish.yml", "utf8");
5: 
6: describe("publish workflow", () => {
7:   test("revalidates approval immediately before Craft", () => {
8:     const fence = workflow.indexOf("Revalidate approval attestation");
9: 
10:     expect(fence).toBeGreaterThan(workflow.indexOf("Set targets"));
11:     expect(fence).toBeLessThan(workflow.indexOf("Publish using Craft"));
12:     expect(workflow.slice(fence)).toContain(
13:       'REQUIRE_CI_READY_ATTESTATION: "true"'
14:     );
15:   });
16: 
17:   test("pins Craft to an immutable digest", () => {
18:     expect(workflow).toMatch(
19:       /uses: docker:\/\/getsentry\/craft@sha256:[a-f0-9]{64}/
20:     );
21:   });
22: });

(End of file - total 22 lines)
</content>
gateway · 2026-09-09 09:37:52
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/auto-approval-workflow.js</path>
<type>file</type>
<content>
1: import { readFileSync } from "fs";
2: import { describe, expect, test } from "vitest";
3: 
4: const workflow = readFileSync(".github/workflows/auto-approve.yml", "utf8");
5: 
6: describe("auto-approval workflow", () => {
7:   test("records a request-bound attestation before adding accepted", () => {
8:     expect(workflow).toContain(
9:       "node src/publish/record-auto-approval-attestation.js"
10:     );
11:     expect(
12:       workflow.indexOf("record-auto-approval-attestation.js")
13:     ).toBeLessThan(workflow.indexOf("--add-label accepted"));
14:   });
15: 
16:   test("posts the proof as github-actions before the internal app labels", () => {
17:     const proof = workflow.indexOf("Post automated approval attestation");
18:     const label = workflow.indexOf("--add-label accepted");
19: 
20:     expect(workflow.slice(proof, label)).toContain(
21:       ...
gateway · 2026-09-09 09:38:00
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/workflow-action-pinning.js</path>
<type>file</type>
<content>
1: import { readFileSync, readdirSync } from "fs";
2: import { describe, expect, test } from "vitest";
3: 
4: const workflowPaths = readdirSync(".github/workflows")
5:   .filter((path) => path.endsWith(".yml"))
6:   .map((path) => `.github/workflows/${path}`);
7: const workflows = workflowPaths.map((path) => ({
8:   path,
9:   contents: readFileSync(path, "utf8"),
10: }));
11: const SECRET_BEARING_JOBS = {
12:   ".github/workflows/auto-approve.yml": ["auto-approve"],
13:   ".github/workflows/ci-poller.yml": ["check-ci"],
14:   ".github/workflows/cocoapods-keepalive.yml": ["keepalive"],
15:   ".github/workflows/publish.yml": ["waiting-for-ci", "publish"],
16: };
17: 
18: function jobContents(workflow, jobName) {
19:   const header = `  ${jobName}:`;
20:   const jobStart = workflow.indexOf(header);
21:   const nextJobOffset = workflow
22:     .slice(jobStart + ...
gateway · 2026-09-09 09:38:14
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/record-auto-approval-attestation.js</path>
<type>file</type>
<content>
1: import { describe, expect, test, vi } from "vitest";
2: 
3: const {
4:   parseAutoApprovalAttestation,
5: } = require("../../modules/approval-attestation.js");
6: const {
7:   recordAutoApprovalAttestation,
8: } = require("../record-auto-approval-attestation.js");
9: 
10: describe("recordAutoApprovalAttestation", () => {
11:   test("binds an automated requester to the live publish request", async () => {
12:     const title = "publish: getsentry/sentry-javascript@10.0.0";
13: 
14:     await expect(
15:       recordAutoApprovalAttestation({
16:         autoApprover: "getsantry[bot]",
17:         getIssue: vi.fn().mockResolvedValue({
18:           body: "Merge target: main",
19:           labels: [],
20:           state: "open",
21:           title,
22:           user: { login: "getsantry[bot]" },
23:         }),
24:         issueNumber: "123",
25:       ...
gateway · 2026-09-09 09:38:20
result
<path>/home/byk/Code/getsentry/publish/src/libs/__tests__/github.js</path>
<type>file</type>
<content>
1: import { vi, describe, test, expect } from "vitest";
2: 
3: vi.mock("fs");
4: 
5: const { getGitHubToken } = require("../github.js");
6: 
7: describe("getGitHubToken", () => {
8:   test("throw if no token is defined", async () => {
9:     delete process.env.GITHUB_TOKEN;
10:     expect(() => getGitHubToken()).toThrow(
11:       'No "GITHUB_TOKEN" environment variable found.'
12:     );
13:   });
14: 
15:   test("return token if defined", async () => {
16:     process.env.GITHUB_TOKEN = "Example Token";
17:     expect(getGitHubToken()).toEqual("Example Token");
18:   });
19: });

(End of file - total 19 lines)
</content>
gateway · 2026-09-09 09:38:30
result
<path>/home/byk/Code/getsentry/publish/src/modules/__tests__/details-from-context.js</path>
<type>file</type>
<content>
1: import { test, expect } from "vitest";
2: 
3: const {
4:   detailsFromContext,
5:   parsePublishTitle,
6: } = require("../details-from-context.js");
7: 
8: const inputsArgs = {
9:   context: {
10:     repo: { owner: "getsentry", repo: "publish" },
11:     payload: {
12:       issue: {
13:         number: "223",
14:         title: "publish: getsentry/sentry@21.3.1",
15:         body: `
16: Requested by: @BYK
17: 
18: Merge target: custom-branch
19: 
20: Quick links:
21: - [View changes](https://github.com/getsentry/sentry/compare/21.3.0...refs/heads/releases/21.3.1)
22: - [View check runs](https://github.com/getsentry/sentry/commit/7e5ca7ed5581552de066e2a8bc295b8306be38ac/checks/)
23: 
24: Assign the **accepted** label to this issue to approve the release. ...
gateway · 2026-09-09 09:38:34
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-09 09:38:45
result
diff --git a/.github/workflows/cocoapods-keepalive.yml b/.github/workflows/cocoapods-keepalive.yml
index 21c0696..ffa5982 100644
--- a/.github/workflows/cocoapods-keepalive.yml
+++ b/.github/workflows/cocoapods-keepalive.yml
@@ -1,4 +1,4 @@
-name: "CocoaPods Token Keep-Alive"
+name: CocoaPods Token Keep-Alive
 
 on:
   schedule:
@@ -6,7 +6,6 @@ on:
     # This runs every day at midnight UTC to keep the current token alive
     # See: https://github.com/CocoaPods/trunk.cocoapods.org/blob/a1869790e9ae2229b6985b6af4532da814202558/app/models/session.rb#L9
     - cron: "0 0 * * *" # Runs every day at midnight UTC
-  workflow_dispatch:
 
 permissions:
   contents: read
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 44692d0..da7f78e 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -16,11 +16,11 @@ jobs:
     runs-on: ubuntu-latest
     name: unit tests
     steps:
-      - uses: actions/checkout@v6
-      - uses: actions/setup-node@v6
+  ...
gateway · 2026-09-09 09:38:53
result
<path>/home/byk/Code/getsentry/publish/.lore.md</path>
<type>file</type>
<content>
1: <!-- Managed by lore (https://github.com/BYK/loreai) — manual edits are imported on next session. -->
2: 
3: ## Long-term Knowledge
4: 
5: ### Architecture
6: 
7: <!-- lore:01a043d7-502d-7de9-9416-66f3050c12b7 -->
8: * **approval-authorizer GitHub roles**: Chose live GitHub collaborator permission checks over private \`security-as-code\` because Publish is public and GitHub reflects effective target-repository access without exposing RBAC. Humans authorize only with \`write\`, \`maintain\`, or \`admin\`; \`triage\` never authorizes. \`getsantry\[bot]\` must never fall through to collaborator lookup: it authorizes only when its exact parsed repository/release path is in \`auto-approve-repos.txt\`, because bot repository access does not prove the event followed an allowlisted auto-approval path.
9: 
10: <!-- lore:01a082db-d8b9-7438-927a-07bf7aa5e369 -->
11: * **production environment secret boundary**: ...
gateway · 2026-09-09 09:41:48
result
7c60ddb feat: accept workspace publish requests
 .github/workflows/ci-poller.yml                    |   62 +-
 .github/workflows/cocoapods-keepalive.yml          |    2 +-
 .github/workflows/publish.yml                      |   84 +-
 README.md                                          |    5 +
 docs/publish-issue-format.md                       |  107 ++
 package.json                                       |    5 +-
 scripts/generate-publish-issue-title-parser.js     |  104 ++
 src/modules/__tests__/ci-poller-input.js           |   97 ++
 src/modules/__tests__/ci-poller-workflow.js        |  197 +++
 src/modules/__tests__/details-from-context.js      |  166 ++-
 .../generate-publish-issue-title-parser.js         |   40 +
 src/modules/__tests__/publish-location.js          |   91 ++
 src/modules/__tests__/publish-workflow.js          |  156 +++
 src/modules/__tests__/release-revision.js          |  150 ++
 src/modules/__tests__/update-issue.js              |   15 +-
 ...
gateway · 2026-09-09 09:45:45
result
yarn run v1.22.22
$ vitest run --run --printConsoleTrace

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

 ✓ src/publish/__tests__/validate-approval-attestation.js (9 tests) 62ms
 ✓ src/modules/__tests__/approval-attestation.js (5 tests) 45ms
 ✓ src/modules/__tests__/update-issue.js (6 tests) 23ms
 ✓ src/modules/__tests__/post-workflow-details.js (1 test) 26ms
 ✓ src/modules/__tests__/process-end-state.js (4 tests) 60ms
 ✓ src/publish/__tests__/current-accepted-event.js (2 tests) 37ms
 ✓ src/publish/__tests__/authorize-approval.js (7 tests) 37ms
 ✓ ...
gateway · 2026-09-09 09:45:55
result
yarn run v1.22.22
$ eslint src .github --ignore-pattern '!.github'
Done in 1.24s.
gateway · 2026-09-09 09:47:22
result
Found 2 matches
/home/byk/Code/getsentry/publish/src/publish/validate-approval-attestation.js:
  Line 13:       "X-GitHub-Api-Version": "2026-03-10",


/home/byk/Code/getsentry/publish/src/publish/authorize-approval.js:
  Line 44:       "X-GitHub-Api-Version": "2026-03-10",
gateway · 2026-09-09 09:47:34
result
# API Versions

Learn how to specify which REST API version to use whenever you make a request to the REST API.

## About API versioning

The GitHub REST API is versioned. The API version name is based on the date when the API version was released. For example, the API version `2026-03-10` was released on Tue, 10 Mar 2026.

Breaking changes are changes that can potentially break an integration. Breaking changes will be released in a new API version. We will provide advance notice before releasing breaking changes. ...
gateway · 2026-09-09 09:48:24
result
<path>/home/byk/Code/getsentry/publish/auto-approve-repos.txt</path>
<type>file</type>
<content>
1: getsentry/arroyo
2: getsentry/auto-type-annotate
3: getsentry/devenv
4: getsentry/infra-event-notifier
5: getsentry/jest-sentry-environment
6: getsentry/json-schema-diff
7: getsentry/js-source-scopes
8: getsentry/objectstore/clients
9: getsentry/ophio
10: getsentry/pdb
11: getsentry/pyo3-python-tracing-subscriber
12: getsentry/pytest-sentry
13: getsentry/relay/py
14: getsentry/responses
15: getsentry/rust-proguard
16: getsentry/rust-sourcemap
17: getsentry/rust-usage-accountant
18: getsentry/script-runner
19: getsentry/sentry-api-schema
20: getsentry/sentry-forked-djangorestframework-stubs
21: getsentry/sentry-forked-django-stubs
22: getsentry/sentry-forked-jsonnet
23: getsentry/sentry-infra-tools
24: getsentry/sentry-kafka-management
25: getsentry/sentry-kafka-schemas
26: getsentry/sentry-protos
27: getsentry/sentry-redis-tools
28: getsentry/service-registry
29: getsentry/skrooge
30: ...
gateway · 2026-09-09 09:48:35
result
Found 25 matches
/home/byk/Code/getsentry/publish/.github/workflows/ci-poller-dispatch.yml:
  Line 3: on:

  Line 4:   workflow_dispatch:

  Line 14:     runs-on: ubuntu-latest


/home/byk/Code/getsentry/publish/.github/workflows/publish.yml:
  Line 2: on:

  Line 3:   issues:

  Line 14:   issues: write

  Line 27:     runs-on: ubuntu-latest

  Line 171:     runs-on: ubuntu-latest

  Line 225:           node-version: 24


/home/byk/Code/getsentry/publish/.github/workflows/ci-poller.yml:
  Line 3: on:

  Line 5:     - cron: "*/5 * * * *"

  Line 6:   repository_dispatch:

  Line 8:   workflow_run:

  Line 14:   issues: write

  Line 18:     runs-on: ubuntu-latest


/home/byk/Code/getsentry/publish/.github/workflows/auto-approve.yml:
  Line 2: on:

  Line 3:   issues:

  Line 8:   issues: write

  Line 12:     runs-on: ubuntu-latest


/home/byk/Code/getsentry/publish/.github/workflows/test.yml:
  Line 2: on:

  Line 16:     runs-on: ubuntu-latest

  Line 22:           node-version: ...
gateway · 2026-09-09 09:48:44
result
## main...origin/main [behind 1]
 M .github/workflows/auto-approve.yml
 M .github/workflows/ci-poller.yml
 M .github/workflows/cocoapods-keepalive.yml
 M .github/workflows/publish.yml
 M .github/workflows/test.yml
 M AGENTS.md
 M README.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
?? .github/workflows/ci-poller-dispatch.yml
?? .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
?? ...
gateway · 2026-09-09 09:51:23
result
bbff32997b690408c3d70e8da6ecbc182a48f3d5a0d95386ec308d20d0ec65e6  -
d4b4de3ef92b0802c3ecd99bf327f8e46a3e51e213cbd8620fbca112d709d2fb  -
publish: getsentry/toolkit/../.__publish__@1.2.3 {"repo":"toolkit","path":"/../.__publish__","version":"1.2.3"}
publish: getsentryreit/space name@1.2.3 null
publish: getsentry/toolkit@--dry-run {"repo":"toolkit","version":"--dry-run"}
gateway · 2026-09-09 09:51:34
result
diff --git a/.github/workflows/ci-poller-dispatch.yml b/.github/workflows/ci-poller-dispatch.yml
new file mode 100644
index 0000000..781bd08
--- /dev/null
+++ b/.github/workflows/ci-poller-dispatch.yml
@@ -0,0 +1,16 @@
+name: Run CI Status Poller
+
+on:
+  workflow_dispatch:
+
+permissions: {}
+
+jobs:
+  dispatch:
+    # production only permits deployments from main. Repository secrets must
+    # never be used by this workflow; protected environment secrets enforce
+    # that an arbitrary dispatched ref cannot add them.
+    environment: production
+    runs-on: ubuntu-latest
+    steps:
+      - run: ":"
diff --git a/src/modules/approval-attestation.js b/src/modules/approval-attestation.js
new file mode 100644
index 0000000..096024a
--- /dev/null
+++ b/src/modules/approval-attestation.js
@@ -0,0 +1,259 @@
+const crypto = require("crypto");
+
+const APPROVAL_ATTESTATION_PREFIX = "<!-- publish-approval ";
+const AUTO_APPROVAL_ATTESTATION_PREFIX = "<!-- publish-auto-approval ...
gateway · 2026-09-09 09:51:40
result
# Webhook events and payloads

Learn about when each webhook event occurs and what the payload contains.

## About webhook events and payloads

You can create webhooks that subscribe to the events listed on this page. To limit the number of HTTP requests to your server, you should only subscribe to the specific events that you plan on handling. For more information, see [Creating webhooks](/en/webhooks/using-webhooks/creating-webhooks).

Each webhook event on this page includes a description of the webhook properties for that event. If the event has multiple actions, the properties corresponding to each action are included.

Each event is only available to specific types of webhooks. For example, an organization webhook can subscribe to the `team` event, but a repository webhook cannot. The description of each webhook event lists the availability for that event. For more information, see [Types of webhooks](/en/webhooks/types-of-webhooks). ...
gateway · 2026-09-09 09:51:49
result
Found 3 matches
/home/byk/.local/share/opencode/tool-output/tool_0859490b7001INPpc8Fu06bmdP:
  Line 406: | `workflow_run` | `object or null` | **Required.**                 |

  Line 459: | `workflow_run`      | `object or null`   | **Required.** |

  Line 1859: | `workflow_run` | `object`         | **Required.** |
gateway · 2026-09-09 09:51:55
result
<path>/home/byk/.local/share/opencode/tool-output/tool_0859490b7001INPpc8Fu06bmdP</path>
<type>file</type>
<content>
1810: 
1811: This event occurs when there is activity relating to a job in a GitHub Actions workflow. For more information, see "Using jobs in a workflow." For information about the API to manage workflow jobs, see "Workflow jobs" in the REST API documentation.
1812: For activity relating to a workflow run instead of a job in a workflow run, use the workflow\_run event.
1813: To subscribe to this event, a GitHub App must have at least read-level access for the "Actions" repository permission.
1814: 
1815: ### Availability
1816: 
1817: * `business`
1818: * `repository`
1819: * `organization`
1820: * `app`
1821: 
1822: ### Webhook payload object
1823: 
1824: **Action type:** `completed`, `in_progress`, `queued`, `waiting`
1825: 
1826: A job in a workflow run finished. ...
gateway · 2026-09-09 09:52: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-09 09:52:06
result
Found 9 matches
/home/byk/.local/share/opencode/tool-output/tool_08594e0e7001zM8uzsnfG42Ys5:
  Line 455: Runs your workflow when activity on a pull request in the workflow's repository occurs. For example, if no activity types are specified, the workflow runs when a pull request is opened or reopened or when the head branch of the pull request is updated. For activity related to pull request reviews, pull request review comments, or pull request comments, use the [`pull_request_review`](#pull_request_review), [`pull_request_review_comment`](#pull_request_review_comment), or [`issue_comment`](#issue_comment) events instead. For information about the pull request APIs, see [Pull requests](/en/graphql/reference/pulls#object-pullrequest) in the GraphQL API documentation or [REST API endpoints for pull requests](/en/rest/pulls).

  Line 457: Note that `GITHUB_SHA` for this event is the last merge commit of the pull request merge branch. ...
gateway · 2026-09-09 09:52:11
result
<path>/home/byk/.local/share/opencode/tool-output/tool_08594e0e7001zM8uzsnfG42Ys5</path>
<type>file</type>
<content>
1225: 
1226: ```shell
1227: gh workflow run run-tests.yml -f logLevel=warning -f tags=false -f environment=staging
1228: ```
1229: 
1230: For more information, see the GitHub CLI information in [Manually running a workflow](/en/actions/how-tos/manage-workflow-runs/manually-run-a-workflow).
1231: 
1232: ## `workflow_run`
1233: 
1234: | Webhook event payload                                                   | Activity types                                      | `GITHUB_SHA`                  | `GITHUB_REF`   |
1235: | ----------------------------------------------------------------------- | --------------------------------------------------- | ----------------------------- | -------------- |
1236: | [`workflow_run`](/en/webhooks/webhook-events-and-payloads#workflow_run) | - `completed`<br/>- `requested`<br/>- `in_progress` | Last commit on default branch | Default branch ...
gateway · 2026-09-09 09:52:28
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-09 09:52:50
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:   repository_dispatch:
7:     types: [ci-poller]
8:   workflow_run:
9:     workflows: [Run CI Status Poller]
10:     types: [completed]
11: 
12: permissions:
13:   contents: read
14:   issues: write
15: 
16: jobs:
17:   check-ci:
18:     runs-on: ubuntu-latest
19:     environment: production
20:     # Skip entirely (no runner provisioned) when there's nothing to check.
21:     # Set to "true" by ci-pending.yml, reset to "false" here when done.
22:     # repository_dispatch and a successful main-branch run of the protected
23:     # workflow_dispatch relay are the trusted manual-recovery paths.
24:     if: >-
25:       vars.CI_POLLER_HAS_PENDING == 'true'
26:       || github.event_name == 'repository_dispatch'
27:       || (
28:       github.event_name == 'workflow_run'
29:       && ...
gateway · 2026-09-09 09:53:08
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:   issues: write
9: 
10: jobs:
11:   auto-approve:
12:     runs-on: ubuntu-latest
13:     environment: production
14:     if: |
15:       (github.actor == 'sentry-release-bot[bot]' || github.actor == 'getsantry[bot]') &&
16:       startsWith(github.event.issue.title, 'publish: ')
17:     steps:
18:       - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
19:         with:
20:           sparse-checkout: auto-approve-repos.txt
21:           sparse-checkout-cone-mode: false
22:       - name: Get auth token
23:         id: token
24:         uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
25:         with:
26:           client-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
27:           private-key: ${{ ...
gateway · 2026-09-09 09:54:58
result
name: CI Status Poller

on:
  schedule:
    - cron: "*/5 * * * *"
  workflow_dispatch:
    inputs:
      attempt:
        description: Self-dispatch attempt counter (internal use)
        required: false
        default: "0"

permissions:
  contents: read
  issues: read

jobs:
  check-ci:
    runs-on: ubuntu-latest
    environment: production
    # Skip entirely (no runner provisioned) when there's nothing to check.
    # Set to "true" by ci-pending.yml, reset to "false" here when done.
    # Always allow workflow_dispatch for manual recovery.
    if: vars.CI_POLLER_HAS_PENDING == 'true' || github.event_name == 'workflow_dispatch'
    concurrency:
      group: ci-status-poller
      cancel-in-progress: false
    steps:
      # 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. ...
gateway · 2026-09-09 09:57:02
result
# REST API endpoints for users

Use the REST API to get public and private information about authenticated users.

> [!NOTE]
> Most endpoints use `Authorization: Bearer <YOUR-TOKEN>` and `Accept: application/vnd.github+json` headers, plus `X-GitHub-Api-Version: 2026-03-10`. Curl examples below omit these standard headers for brevity.

## Get the authenticated user

```
GET /user
```

OAuth app tokens and personal access tokens (classic) need the read:user scope, or the broader user scope, for this endpoint to return the private user response. The private user response includes additional fields such as private_gists, total_private_repos, owned_private_repos, disk_usage, collaborators, and two_factor_authentication. Tokens without these scopes receive the public user response.
The private and public user response types are unrelated to the private profile setting. ...
gateway · 2026-09-09 09:57:07
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/publish-workflow.js</path>
<type>file</type>
<content>
1: import { readFileSync } from "fs";
2: import { describe, expect, test } from "vitest";
3: 
4: const workflow = readFileSync(".github/workflows/publish.yml", "utf8");
5: 
6: describe("publish workflow", () => {
7:   test("revalidates approval immediately before Craft", () => {
8:     const fence = workflow.indexOf("Revalidate approval attestation");
9: 
10:     expect(fence).toBeGreaterThan(workflow.indexOf("Set targets"));
11:     expect(fence).toBeLessThan(workflow.indexOf("Publish using Craft"));
12:     expect(workflow.slice(fence)).toContain(
13:       'REQUIRE_CI_READY_ATTESTATION: "true"'
14:     );
15:   });
16: 
17:   test("pins Craft to an immutable digest", () => {
18:     expect(workflow).toMatch(
19:       /uses: docker:\/\/getsentry\/craft@sha256:[a-f0-9]{64}/
20:     );
21:   });
22: });

(End of file - total 22 lines)
</content>
gateway · 2026-09-09 09:57:21
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/ci-poller-workflow.js</path>
<type>file</type>
<content>
1: import { readFileSync } from "fs";
2: import { describe, expect, test } from "vitest";
3: 
4: const workflow = readFileSync(".github/workflows/ci-poller.yml", "utf8");
5: const manualDispatchWorkflow = readFileSync(
6:   ".github/workflows/ci-poller-dispatch.yml",
7:   "utf8"
8: );
9: 
10: describe("CI poller workflow", () => {
11:   test("checks out trusted code before requesting app tokens", () => {
12:     expect(workflow).toMatch(
13:       /- name: Get publish code\n\s+uses: actions\/checkout@[a-f0-9]{40}\n\s+with:\n(?:\s+#.*\n)*\s+ref: \$\{\{ github\.event\.repository\.default_branch \}\}/
14:     );
15: 
16:     expect(workflow.indexOf("Get publish code")).toBeLessThan(
17:       workflow.indexOf("Get auth token")
18:     );
19:   });
20: 
21:   test("uses a protected workflow dispatch relay for manual recovery", () => {
22:     ...
gateway · 2026-09-09 09:57:37
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/authorize-approval.js</path>
<type>file</type>
<content>
1: import { afterEach, describe, expect, test, vi } from "vitest";
2: import fs from "fs";
3: const {
4:   createAutoApprovalAttestation,
5: } = require("../../modules/approval-attestation.js");
6: const {
7:   getAutoApprovedRepositories,
8:   main,
9: } = require("../authorize-approval.js");
10: 
11: const originalEnvironment = { ...process.env };
12: 
13: afterEach(() => {
14:   process.env = { ...originalEnvironment };
15:   vi.restoreAllMocks();
16:   vi.resetModules();
17: });
18: 
19: function jsonResponse(json) {
20:   return { ok: true, json: vi.fn().mockResolvedValue(json) };
21: }
22: 
23: async function runAuthorization({ actor, issueTitle, responses }) {
24:   process.env.GITHUB_OUTPUT = "/tmp/github-output";
25:   process.env.APPROVAL_TOKEN = "release-bot-token";
26:   process.env.APPROVAL_ACTOR = actor;
27:   process.env.APPROVAL_ISSUE_NUMBER = ...
gateway · 2026-09-09 09:57:46
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(
20:   title,
21:   { body = "", labels = [{ name: "accepted" }], state = "open" } = {}
22: ) {
23:   return { body, labels, state, title };
24: }
25: 
26: describe("validateApprovalAttestation", () => {
27:   test("accepts the current accepted event and its trusted attestation", async () => {
28:     const title = "publish: getsentry/sentry-javascript@10.0.0";
29:    ...
gateway · 2026-09-09 09:57:54
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-09 09:58:10
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-09 09:58:21
result
<path>/home/byk/Code/getsentry/publish/src/publish/update-issue.js</path>
<type>file</type>
<content>
1: const { updateIssue } = require("../modules/update-issue.js");
2: const { getGitHubToken } = require("../libs/github");
3: const github = require("@actions/github");
4: 
5: async function main() {
6:   const context = github.context;
7:   const octokit = github.getOctokit(getGitHubToken());
8:   let inputs;
9: 
10:   if (process.env.PUBLISH_ARGS) {
11:     try {
12:       inputs = JSON.parse(process.env.PUBLISH_ARGS);
13:     } catch {
14:       console.warn("Could not parse publish inputs; skipping target update");
15:     }
16:   }
17: 
18:   await updateIssue({ context, octokit, inputs });
19: }
20: 
21: if (require.main === module) {
22:   main().catch((error) => {
23:     console.error(error);
24:     process.exitCode = 1;
25:   });
26: }
27: 
28: module.exports = { main };

(End of file - total 28 lines)
</content>
gateway · 2026-09-09 09:58:37
result
diff --git a/src/modules/update-issue.js b/src/modules/update-issue.js
index 9b600cc..c5dfba3 100644
--- a/src/modules/update-issue.js
+++ b/src/modules/update-issue.js
@@ -4,7 +4,7 @@ const {
   TARGETS_PARSER_REGEX,
 } = require("./details-from-context");
 
-async function updateTargets({octokit, version, publishRepo, issue_number}) {
+async function updateTargets({ octokit, version, publishRepo, issue_number }) {
   const CRAFT_STATE_FILE_PATH = `${process.env.GITHUB_WORKSPACE}/__repo__/.craft-publish-${version}.json`;
 
   if (!fs.existsSync(CRAFT_STATE_FILE_PATH)) {
@@ -36,37 +36,41 @@ async function updateTargets({octokit, version, publishRepo, issue_number}) {
 
 function transformIssueBody(craftState, issueBody) {
   const declaredTargets = new Set();
-  return issueBody.replace(
-    TARGETS_SECTION_PARSER_REGEX,
-    (targetsSection) => {
-      let targetsText = targetsSection.trimEnd();
-      targetsText = targetsText.replace(
-        TARGETS_PARSER_REGEX,
-        ...
gateway · 2026-09-09 09:59:31
result
name: auto-approve non-sdks
on:
  issues:
    types: [opened]

permissions:
  contents: read

jobs:
  auto-approve:
    runs-on: ubuntu-latest
    if: |
      (github.actor == 'sentry-release-bot[bot]' || github.actor == 'getsantry[bot]') &&
      startsWith(github.event.issue.title, 'publish: ')
    steps:
      - uses: actions/checkout@v4
        with:
          sparse-checkout: auto-approve-repos.txt
          sparse-checkout-cone-mode: false
      - name: Get auth token
        id: token
        uses: actions/create-github-app-token@v3
        with:
          client-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
          private-key: ${{ secrets.SENTRY_INTERNAL_APP_PRIVATE_KEY }}
      - name: Auto-approve if repo is in the list
        env:
          GH_TOKEN: ${{ steps.token.outputs.token }}
          ISSUE_TITLE: ${{ github.event.issue.title }}
          ISSUE_URL: ${{ github.event.issue.html_url }}
        run: |
          REPO=$(echo "$ISSUE_TITLE" | sed -n 's/^publish: ...
gateway · 2026-09-09 09:59:36
result
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index cff6bc3..9ff9a9d 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -32,13 +32,84 @@ jobs:
       && github.event.issue.state == 'open'
       && startsWith(github.event.issue.title, 'publish: ')
     steps:
+      - name: Get publish code
+        uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803
+        with:
+          persist-credentials: false
+
       - name: Get auth token
         id: token
-        uses: actions/create-github-app-token@v3
+        uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
         with:
           client-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
           private-key: ${{ secrets.SENTRY_INTERNAL_APP_PRIVATE_KEY }}
 
+      # The release bot sees all target repositories. This checks the actor's
+      # current effective target-repository role without exposing RBAC data. ...
gateway · 2026-09-09 09:59:45
result
    # after checking CI (even if ci-ready was already present, waiting-for-ci
    # removes it first so a fresh labeled event fires), so this gate is
    # guaranteed to trigger on the happy path.
    if: >-
      github.event.issue.state == 'open'
      && github.event.label.name == 'ci-ready'
      && contains(github.event.issue.labels.*.name, 'accepted')
      && contains(github.event.issue.labels.*.name, 'ci-ready')
      && !contains(github.event.issue.labels.*.name, 'ci-pending')
      && !contains(github.event.issue.labels.*.name, 'ci-failed')
    timeout-minutes: 90
    env:
      SENTRY_DSN: "https://303a687befb64dc2b40ce4c96de507c5@o1.ingest.sentry.io/6183838"
    steps:
      - name: Get repo contents
        uses: actions/checkout@v6
        with:
          path: .__publish__

      - name: Setup Node
        uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: yarn
          cache-dependency-path: .__publish__/yarn.lock

      - name: ...
gateway · 2026-09-09 09:59:53
result
7c60ddb feat: accept workspace publish requests
 .github/workflows/ci-poller.yml                    |   62 +-
 .github/workflows/cocoapods-keepalive.yml          |    2 +-
 .github/workflows/publish.yml                      |   84 +-
 README.md                                          |    5 +
 docs/publish-issue-format.md                       |  107 ++
 package.json                                       |    5 +-
 scripts/generate-publish-issue-title-parser.js     |  104 ++
 src/modules/__tests__/ci-poller-input.js           |   97 ++
 src/modules/__tests__/ci-poller-workflow.js        |  197 +++
 src/modules/__tests__/details-from-context.js      |  166 ++-
 .../generate-publish-issue-title-parser.js         |   40 +
 src/modules/__tests__/publish-location.js          |   91 ++
 src/modules/__tests__/publish-workflow.js          |  156 +++
 src/modules/__tests__/release-revision.js          |  150 ++
 src/modules/__tests__/update-issue.js              |   15 +-
 ...
gateway · 2026-09-09 10:00:48
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-09 10:00:56
result
ok auto-approve.yml
ok ci-poller-dispatch.yml
ok ci-poller.yml
ok cocoapods-keepalive.yml
ok publish.yml
ok test.yml
gateway · 2026-09-09 10:01:01
result
yarn run v1.22.22
$ vitest run --run --printConsoleTrace

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

 ✓ src/publish/__tests__/validate-approval-attestation.js (9 tests) 28ms
 ✓ src/modules/__tests__/approval-attestation.js (5 tests) 47ms
 ✓ src/publish/__tests__/authorize-approval.js (7 tests) 19ms
 ✓ src/publish/__tests__/current-accepted-event.js (2 tests) 20ms
 ✓ src/modules/__tests__/approval-authorizer.js (15 tests) 31ms
 ✓ src/modules/__tests__/process-end-state.js (4 tests) 58ms
 ✓ src/modules/__tests__/post-workflow-details.js (1 test) 24ms
 ...
gateway · 2026-09-09 10:01:09
result
yarn run v1.22.22
$ eslint src .github --ignore-pattern '!.github'
Done in 1.49s.
gateway · 2026-09-09 10:01:18
result
yarn run v1.22.22
$ prettier --write src --check .
Checking formatting...
[warn] .lore.md
[warn] AGENTS.md
[warn] docs/key_rotation_vault_changes.md
[warn] docs/rfc.md
[warn] Code style issues found in 4 files.
Done in 2.48s.
gateway · 2026-09-09 10:02:12
result
<path>/home/byk/Code/getsentry/publish/.lore.md</path>
<type>file</type>
<content>
1: <!-- Managed by lore (https://github.com/BYK/loreai) — manual edits are imported on next session. -->
2: 
3: ## Long-term Knowledge
4: 
5: ### Architecture
6: 
7: <!-- lore:01a043d7-502d-7de9-9416-66f3050c12b7 -->
8: 
9: - **approval-authorizer GitHub roles**: Chose live GitHub collaborator permission checks over private \`security-as-code\` because Publish is public and GitHub reflects effective target-repository access without exposing RBAC. Humans authorize only with \`write\`, \`maintain\`, or \`admin\`; \`triage\` never authorizes. \`getsantry\[bot]\` must never fall through to collaborator lookup: it authorizes only when its exact parsed repository/release path is in \`auto-approve-repos.txt\`, because bot repository access does not prove the event followed an allowlisted auto-approval path.
10: 
11: <!-- lore:01a082db-d8b9-7438-927a-07bf7aa5e369 -->
12: 
13: - **production environment secret ...
gateway · 2026-09-09 10:03:51
result
# Authenticating to the REST API

You can authenticate to the REST API to access more endpoints and have a higher rate limit.

## About authentication

Many REST API endpoints require authentication or return additional information if you are authenticated. Additionally, you can make more requests per hour when you are authenticated.

To authenticate your request, you will need to provide an authentication token with the required scopes or permissions. There a few different ways to get a token: You can create a personal access token, generate a token with a GitHub App, or use the built-in `GITHUB_TOKEN` in a GitHub Actions workflow.

After creating a token, you can authenticate your request by sending the token in the `Authorization` header of your request. For example, in the following request, replace `YOUR-TOKEN` with a reference to your token:

```shell
curl --request GET \
--url "https://api.github.com/octocat" \
--header "Authorization: Bearer YOUR-TOKEN" \
--header ...
gateway · 2026-09-09 10:04:31
result
<path>/home/byk/Code/getsentry/publish/package.json</path>
<type>file</type>
<content>
1: {
2:   "name": "publish",
3:   "private": true,
4:   "version": "0.0.1",
5:   "description": "Approval-based publishing system for Sentry",
6:   "main": "index.js",
7:   "repository": "git@github.com:getsentry/publish.git",
8:   "author": "Sentry Open Source <oss@sentry.io>",
9:   "license": "Apache-2.0",
10:   "volta": {
11:     "node": "24.0.0",
12:     "yarn": "1.22.22"
13:   },
14:   "resolutions": {
15:     "undici": "^6.23.0"
16:   },
17:   "scripts": {
18:     "test": "vitest run",
19:     "test:watch": "vitest",
20:     "lint": "eslint src .github --ignore-pattern '!.github'",
21:     "prettier": "prettier --write src"
22:   },
23:   "devDependencies": {
24:     "eslint": "^8.9.0",
25:     "eslint-config-prettier": "^8.3.0",
26:     "eslint-plugin-yml": "^0.13.0",
27:     "prettier": "^2.2.1",
28:     "vitest": "^4.1.0"
29:   },
30:   "dependencies": {
31:     "@actions/core": ...
gateway · 2026-09-09 10:07:38
result
{"id":9871907870,"node_id":"EN_kwDOEgGvjc8AAAACTGlcHg","name":"production","url":"https://api.github.com/repos/getsentry/publish/environments/production","html_url":"https://github.com/getsentry/publish/deployments/activity_log?environments_filter=production","created_at":"2025-11-10T21:33:16Z","updated_at":"2026-09-08T20:55:15Z","can_admins_bypass":false,"protection_rules":[{"id":42720759,"node_id":"GA_kwDOEgGvjc4Ci933","type":"branch_policy"}],"deployment_branch_policy":{"protected_branches":false,"custom_branch_policies":true}}{"default_workflow_permissions":"write","can_approve_pull_request_reviews":true}
gateway · 2026-09-09 10:07:48
result
{"total_count":1,"branch_policies":[{"id":38207111,"node_id":"MDE2OkdhdGVCcmFuY2hQb2xpY3kzODIwNzExMQ==","name":"main","type":"branch"}]}
gateway · 2026-09-09 10:08:14
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:   issues: write
9: 
10: jobs:
11:   auto-approve:
12:     runs-on: ubuntu-latest
13:     environment: production
14:     if: |
15:       (github.actor == 'sentry-release-bot[bot]' || github.actor == 'getsantry[bot]') &&
16:       startsWith(github.event.issue.title, 'publish: ')
17:     steps:
18:       - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
19:         with:
20:           sparse-checkout: auto-approve-repos.txt
21:           sparse-checkout-cone-mode: false
22:       - name: Get auth token
23:         id: token
24:         uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
25:         with:
26:           client-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
27:           private-key: ${{ ...
gateway · 2026-09-09 10:08:25
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 AUTO_APPROVAL_ATTESTATION_PREFIX = "<!-- publish-auto-approval ";
5: const CI_READY_ATTESTATION_PREFIX = "<!-- publish-ci-ready ";
6: const ATTESTATION_SUFFIX = " -->";
7: 
8: function createAttestation(prefix, value) {
9:   return `${prefix}${Buffer.from(JSON.stringify(value)).toString(
10:     "base64url"
11:   )}${ATTESTATION_SUFFIX}`;
12: }
13: 
14: function requestDigest({ body, labels, title }) {
15:   if (typeof body !== "string" || !Array.isArray(labels)) {
16:     throw new Error("Invalid publish request");
17:   }
18: 
19:   return crypto
20:     .createHash("sha256")
21:     .update(
22:       JSON.stringify({
23:         body,
24:         dryRun: labels.some((label) => label.name === "dry-run"),
25:         title,
26:       })
27:     )
28:     ...
gateway · 2026-09-09 10:08:35
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-09 10:08:43
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:   repository_dispatch:
7:     types: [ci-poller]
8:   workflow_run:
9:     workflows: [Run CI Status Poller]
10:     types: [completed]
11: 
12: permissions:
13:   contents: read
14:   issues: write
15: 
16: jobs:
17:   check-ci:
18:     runs-on: ubuntu-latest
19:     environment: production
20:     # Skip entirely (no runner provisioned) when there's nothing to check.
21:     # Set to "true" by ci-pending.yml, reset to "false" here when done.
22:     # repository_dispatch and a successful main-branch run of the protected
23:     # workflow_dispatch relay are the trusted manual-recovery paths.
24:     if: >-
25:       vars.CI_POLLER_HAS_PENDING == 'true'
26:       || github.event_name == 'repository_dispatch'
27:       || (
28:       github.event_name == 'workflow_run'
29:       && ...
gateway · 2026-09-09 10:08:47
result
# Contexts reference

Find information about contexts available in GitHub Actions workflows, including available properties, access methods, and usage examples.

## Available contexts

| Context name | Type     | Description                                                                                                                                          |
| ------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `github`     | `object` | Information about the workflow run. For more information, see [`github` context](#github-context).                                                   |
| `env`        | `object` | Contains variables set in a workflow, job, or step. For more information, see [`env` context](#env-context). ...
gateway · 2026-09-09 10:09:17
result
Response too large (exceeds 5MB limit)
gateway · 2026-09-09 10:09:28
result
2c4c77d1cafa8d792ab4a9d449799221baf95176a47692ad9a0b350b0a2618ed  -
bcd2ba49218906704ab6c1aa796996da409d3eb1
d23441a48e516b6c34aea4fa41551a30e30af803
11d5960a326750d5838078e36cf38b85af677262
gateway · 2026-09-09 10:09:40
result
gh: Not Found (HTTP 404)
base64: invalid input
gateway · 2026-09-09 10:09:48
result
.github
.github/CODEOWNERS
.github/dependabot.yml
.github/workflows
.github/workflows/release.yml
.github/workflows/stale.yml
.github/workflows/test.yml
.github/workflows/update-permission-inputs.yml
.gitignore
.release-please-manifest.json
CHANGELOG.md
CONTRIBUTING.md
LICENSE
README.md
action.yml
dist
dist/main.cjs
dist/post.cjs
lib
lib/get-permissions-from-inputs.js
lib/main.js
lib/post.js
lib/request.js
main.js
package-lock.json
package.json
post.js
release-please-config.beta.json
release-please-config.json
scripts
scripts/generated
scripts/generated/app-permissions.json
scripts/update-permission-inputs.js
tests
tests/README.md
tests/action-deprecated-inputs.test.js
tests/index.js
tests/index.js.snapshot
tests/main-app-id-fallback.test.js
tests/main-client-id-precedence.test.js
tests/main-custom-github-api-url.test.js
tests/main-enterprise-fail-response.test.js
tests/main-enterprise-installation-not-found.test.js
tests/main-enterprise-mutual-exclusivity-owner.test.js
tests/main-ente...
gateway · 2026-09-09 10:09:57
result
import pRetry from "p-retry";
import isNetworkError from "is-network-error";
// @ts-check

/**
 * @param {string} clientId
 * @param {string} privateKey
 * @param {string} enterprise
 * @param {string} owner
 * @param {string[]} repositories
 * @param {undefined | Record<string, string>} permissions
 * @param {import("@actions/core")} core
 * @param {import("@octokit/auth-app").createAppAuth} createAppAuth
 * @param {import("@octokit/request").request} request
 * @param {boolean} skipTokenRevoke
 */
export async function main(
  clientId,
  privateKey,
  enterprise,
  owner,
  repositories,
  permissions,
  core,
  createAppAuth,
  request,
  skipTokenRevoke,
) {
  // Validate mutual exclusivity of enterprise with owner/repositories
  if (enterprise && (owner || repositories.length > 0)) {
    throw new Error("Cannot use 'enterprise' input with 'owner' or 'repositories' inputs");
  }

  const target = resolveInstallationTarget(enterprise, owner, repositories, core);

  const auth = ...
gateway · 2026-09-09 10:10:48
result
## main...origin/main [behind 1]
 M .github/workflows/auto-approve.yml
 M .github/workflows/ci-poller.yml
 M .github/workflows/cocoapods-keepalive.yml
 M .github/workflows/publish.yml
 M .github/workflows/test.yml
 M AGENTS.md
 M README.md
 M docs/key_rotation_vault_changes.md
 M docs/rfc.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
?? .github/workflows/ci-poller-dispatch.yml
?? .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__/
?? ...
gateway · 2026-09-09 10:11:47
result
No files found
gateway · 2026-09-09 10:11:56
result
<path>/home/byk/Code/getsentry/publish/.github/workflows</path>
<type>directory</type>
<entries>
auto-approve.yml
ci-poller-dispatch.yml
ci-poller.yml
cocoapods-keepalive.yml
publish.yml
test.yml

(6 entries)
</entries>
gateway · 2026-09-09 10:12:04
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-09 10:12:12
result
<path>/home/byk/Code/getsentry/publish/src/publish/record-auto-approval-attestation.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const {
3:   createAutoApprovalAttestation,
4: } = require("../modules/approval-attestation.js");
5: const { getIssue } = require("./validate-approval-attestation.js");
6: 
7: async function recordAutoApprovalAttestation({
8:   autoApprover,
9:   getIssue,
10:   issueNumber,
11:   issueTitle,
12:   repository,
13: }) {
14:   const issue = await getIssue({ repository, issueNumber });
15: 
16:   if (
17:     issue.state !== "open" ||
18:     issue.title !== issueTitle ||
19:     issue.user?.login?.toLowerCase() !== autoApprover.toLowerCase()
20:   ) {
21:     throw new Error("The automated approval request changed before approval");
22:   }
23: 
24:   return createAutoApprovalAttestation({ autoApprover, issue });
25: }
26: 
27: async function main() {
28:   for (const name of [
29:     "GITHUB_OUTPUT",
30:     "APPROVAL_TOKEN",
31:     ...
gateway · 2026-09-09 10:12:17
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:   hasAutoApprovalAttestation,
6: } = require("../modules/approval-attestation.js");
7: const {
8:   AUTO_APPROVAL_LABELER,
9:   AUTO_APPROVERS,
10:   authorizeApproval,
11:   isAutoApprovedRepository,
12: } = require("../modules/approval-authorizer.js");
13: 
14: function getAutoApprovedRepositories() {
15:   return new Set(
16:     fs
17:       .readFileSync("auto-approve-repos.txt", "utf8")
18:       .split(/\r?\n/)
19:       .filter(Boolean)
20:   );
21: }
22: 
23: async function getPermission({ owner, repository, username }) {
24:   const response = await getGitHubResponse(
25:     `repos/${encodeURIComponent(owner)}/${encodeURIComponent(
26:       repository
27:     )}/collaborators/${encodeURIComponent(username)}/permission`
28:   );
29: 
30:   if (!response.ok) {
31:     ...
gateway · 2026-09-09 10:12:23
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 { parsePublishTitle } = require("./details-from-context.js");
3: 
4: const AUTO_APPROVAL_LABELER = "sentry-internal-app[bot]";
5: const AUTO_APPROVERS = new Set(["getsantry[bot]", "sentry-release-bot[bot]"]);
6: 
7: function isAutoApprovedRepository({
8:   actor,
9:   autoApprovedRepositories,
10:   issueTitle,
11: }) {
12:   if (!AUTO_APPROVERS.has(actor)) {
13:     return false;
14:   }
15: 
16:   const title = parsePublishTitle(issueTitle);
17: 
18:   if (!title) {
19:     return false;
20:   }
21: 
22:   return autoApprovedRepositories.has(
23:     `getsentry/${title.repo}${title.path || ""}`
24:   );
25: }
26: 
27: async function authorizeApproval({ actor, issueTitle, getPermission }) {
28:   const title = parsePublishTitle(issueTitle);
29: 
30:   if (!title) {
31:     return { authorized: ...
gateway · 2026-09-09 10:13:34
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:           state: "open",
23:           title,
24:         }),
25:         ...
gateway · 2026-09-09 10:13:49
<!-- lore-recall:833dc60f-de62-4ef7-9c61-3a0e6cf2557f --><!-- lore-recall:35093348-2963-4734-b5fe-d295d6b72209 -->
gpt-5.6-sol · 2026-09-09 10:13:49 · ~55 tokens
result
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
CI_POLLER_APP_PRIVATE_KEY
CLOUDFLARE_API_TOKEN
COCOAPODS_TRUNK_TOKEN
CRAFT_GCS_STORE_CREDS_JSON
CRAFT_GCS_TARGET_CREDS_JSON
CRATES_IO_TOKEN
DOCKER_PASSWORD
GEM_HOST_API_KEY
GPG_PASSPHRASE
GPG_PRIVATE_KEY
HEX_API_KEY
NPM_TOKEN
NUGET_API_TOKEN
OSSRH_PASSWORD
OSSRH_USERNAME
POWERSHELL_API_KEY
PUBDEV_ACCESS_TOKEN
PUBDEV_REFRESH_TOKEN
SENTRY_RELEASE_BOT_PRIVATE_KEY
TWINE_PASSWORD
VERCEL_TOKEN
CI_POLLER_APP_CLIENT_ID	Iv23ctZfkJQDhAmr8qnG
CI_POLLER_HAS_PENDING	false
VERCEL_ORG_ID	team_X2wL9ydOZqI86I3fHzJ6dmXs
gateway · 2026-09-09 10:14:30
result
<!DOCTYPE html><html data-color-mode="auto" data-light-theme="light" data-dark-theme="dark" lang="en"><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width, initial-scale=1" data-next-head=""/><link rel="icon" type="image/png" href="/assets/cb-345/images/site/favicon.png" data-next-head=""/><link href="/manifest.json" rel="manifest" data-next-head=""/><meta name="google-site-verification" content="c1kuD-K2HIVF635lypcsWPoD4kilo5-jA_wBFyT4uMY" data-next-head=""/><title data-next-head="">REST API endpoints for users - GitHub Docs</title><meta name="description" content="Use the REST API to get public and private information about authenticated users." data-next-head=""/><link rel="alternate" hrefLang="es" href="https://docs.github.com/es/rest/users/users?apiVersion=2022-11-28" data-next-head=""/><link rel="alternate" hrefLang="ja" href="https://docs.github.com/ja/rest/users/users?apiVersion=2022-11-28" data-next-head=""/><link rel="alternate" ...
gateway · 2026-09-09 10:15:12
result
# REST API endpoints for users

Use the REST API to get public and private information about authenticated users.

> [!NOTE]
> Most endpoints use `Authorization: Bearer <YOUR-TOKEN>` and `Accept: application/vnd.github+json` headers, plus `X-GitHub-Api-Version: 2022-11-28`. Curl examples below omit these standard headers for brevity.

## Get the authenticated user

```
GET /user
```

OAuth app tokens and personal access tokens (classic) need the read:user scope, or the broader user scope, for this endpoint to return the private user response. The private user response includes additional fields such as private_gists, total_private_repos, owned_private_repos, disk_usage, collaborators, and two_factor_authentication. Tokens without these scopes receive the public user response.
The private and public user response types are unrelated to the private profile setting. ...
gateway · 2026-09-09 10:15:58
result
HTTP 422: ERROR_TYPE_QUERY_PARSING_FATAL unable to parse query! (https://api.github.com/search/code?page=1&per_page=20&q=gh+api+user+--jq+.login+language%3AYAML)
gateway · 2026-09-09 10:16:10
result
# Permissions required for GitHub Apps

For each permission granted to a GitHub App, these are the REST API endpoints that the app can use.

## About GitHub App permissions

GitHub Apps are created with a set of permissions. Permissions define what resources the GitHub App can access via the API. For more information, see [Choosing permissions for a GitHub App](/en/apps/creating-github-apps/registering-a-github-app/choosing-permissions-for-a-github-app).

To help you choose the correct permissions, you will receive the `X-Accepted-GitHub-Permissions` header in the REST API response. The header will tell you what permissions are required in order to access the endpoint. For more information, see [Troubleshooting the REST API](/en/rest/using-the-rest-api/troubleshooting-the-rest-api#resource-not-accessible).

These permissions are required to access private resources. Some endpoints can also be used to access public resources without these permissions. ...
gateway · 2026-09-09 10:16:16
result
Found 29 matches
/home/byk/.local/share/opencode/tool-output/tool_085aaff0f001frdlQk04l8pgZa:
  Line 387: | `GET /user/memberships/orgs/{org}` | read | UAT | ✗ |

  Line 775: | `GET /user/repository_invitations` | read | UAT | ✗ |

  Line 874: | `GET /user/codespaces/{codespace_name}/exports/{export_id}` | read | UAT | ✗ |

  Line 882: | `GET /user/codespaces/{codespace_name}/machines` | read | UAT | ✗ |

  Line 910: | `GET /user/codespaces` | read | UAT | ✗ |

  Line 911: | `GET /user/codespaces/{codespace_name}` | read | UAT | ✗ |

  Line 1174: | `GET /user/installations/{installation_id}/repositories` | read | UAT | ✗ |

  Line 1175: | `GET /user/repos` | read | UAT | ✗ |

  Line 1358: | `GET /user/blocks` | read | UAT | ✗ |

  Line 1359: | `GET /user/blocks/{username}` | read | UAT | ✗ |

  Line 1370: | `GET /user/codespaces/secrets` | read | UAT | ✗ |

  Line 1371: | `GET /user/codespaces/secrets/public-key` | read | UAT | ✗ |

  Line 1372: | `GET ...
gateway · 2026-09-09 10:18:04
result
diff --git a/.github/workflows/ci-poller.yml b/.github/workflows/ci-poller.yml
index a3db430..17d5e91 100644
--- a/.github/workflows/ci-poller.yml
+++ b/.github/workflows/ci-poller.yml
@@ -1,53 +1,79 @@
 name: CI Status Poller
 
 on:
   schedule:
     - cron: "*/5 * * * *"
-  workflow_dispatch:
-    inputs:
-      attempt:
-        description: Self-dispatch attempt counter (internal use)
-        required: false
-        default: "0"
+  repository_dispatch:
+    types: [ci-poller]
+  workflow_run:
+    workflows: [Run CI Status Poller]
+    types: [completed]
 
 permissions:
   contents: read
-  issues: read
+  issues: write
 
 jobs:
   check-ci:
     runs-on: ubuntu-latest
     environment: production
     # Skip entirely (no runner provisioned) when there's nothing to check.
     # Set to "true" by ci-pending.yml, reset to "false" here when done.
-    # Always allow workflow_dispatch for manual recovery.
-    if: vars.CI_POLLER_HAS_PENDING == 'true' || github.event_name == ...
gateway · 2026-09-09 10:18:34
result
No files found
gateway · 2026-09-09 10:19:10
result
yarn run v1.22.22
$ vitest run --printConsoleTrace

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

 ✓ src/modules/__tests__/approval-authorizer.js (15 tests) 18ms
 ✓ src/modules/__tests__/approval-attestation.js (5 tests) 41ms
 ✓ src/publish/__tests__/validate-approval-attestation.js (9 tests) 25ms
 ✓ src/modules/__tests__/post-workflow-details.js (1 test) 22ms
 ✓ src/modules/__tests__/update-issue.js (6 tests) 25ms
 ✓ src/publish/__tests__/current-accepted-event.js (2 tests) 63ms
 ✓ src/modules/__tests__/process-end-state.js (4 tests) 86ms
 ✓ ...
gateway · 2026-09-09 10:19:20
result
yarn run v1.22.22
$ eslint src .github --ignore-pattern '!.github'
Done in 1.02s.
gateway · 2026-09-09 10:19:30
result
yarn run v1.22.22
$ prettier --write src --check .
Checking formatting...
[warn] .lore.md
[warn] AGENTS.md
[warn] Code style issues found in 2 files.
Done in 1.73s.
gateway · 2026-09-09 10:23:19
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-09 10:23:29
result
File not found: /home/byk/Code/getsentry/publish/src/publish/__tests__/ACLE_PLACEHOLDER
gateway · 2026-09-09 10:23:38
result
File not found: /home/byk/Code/getsentry/publish/src/publish/__tests__/ciодаря
gateway · 2026-09-09 10:23:45
result
File not found: /home/byk/Code/getsentry/publish/src/publish/__tests__/ci-pollerlass
gateway · 2026-09-09 10:24:25
result
7c60ddb (origin/main, origin/HEAD) feat: accept workspace publish requests
 .github/workflows/ci-poller.yml                    |   62 +-
 .github/workflows/cocoapods-keepalive.yml          |    2 +-
 .github/workflows/publish.yml                      |   84 +-
 README.md                                          |    5 +
 docs/publish-issue-format.md                       |  107 ++
 package.json                                       |    5 +-
 scripts/generate-publish-issue-title-parser.js     |  104 ++
 src/modules/__tests__/ci-poller-input.js           |   97 ++
 src/modules/__tests__/ci-poller-workflow.js        |  197 +++
 src/modules/__tests__/details-from-context.js      |  166 ++-
 .../generate-publish-issue-title-parser.js         |   40 +
 src/modules/__tests__/publish-location.js          |   91 ++
 src/modules/__tests__/publish-workflow.js          |  156 +++
 src/modules/__tests__/release-revision.js          |  150 ++
 src/modules/__tests__/update-issue.js              |   ...
gateway · 2026-09-09 10:26:04
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/ci-poller-workflow.js</path>
<type>file</type>
<content>
1: import { readFileSync } from "fs";
2: import { describe, expect, test } from "vitest";
3: 
4: const workflow = readFileSync(".github/workflows/ci-poller.yml", "utf8");
5: const manualDispatchWorkflow = readFileSync(
6:   ".github/workflows/ci-poller-dispatch.yml",
7:   "utf8"
8: );
9: 
10: describe("CI poller workflow", () => {
11:   test("checks out trusted code before requesting app tokens", () => {
12:     expect(workflow).toMatch(
13:       /- name: Get publish code\n\s+uses: actions\/checkout@[a-f0-9]{40}\n\s+with:\n(?:\s+#.*\n)*\s+ref: \$\{\{ github\.event\.repository\.default_branch \}\}/
14:     );
15: 
16:     expect(workflow.indexOf("Get publish code")).toBeLessThan(
17:       workflow.indexOf("Get auth token")
18:     );
19:   });
20: 
21:   test("uses a protected workflow dispatch relay for manual recovery", () => {
22:     ...
gateway · 2026-09-09 10:27:00
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/ci-poller-dispatch.yml</path>
<type>file</type>
<content>
1: name: Run CI Status Poller
2: 
3: on:
4:   workflow_dispatch:
5: 
6: permissions: {}
7: 
8: jobs:
9:   dispatch:
10:     # production only permits deployments from main. Repository secrets must
11:     # never be used by this workflow; protected environment secrets enforce
12:     # that an arbitrary dispatched ref cannot add them.
13:     environment: production
14:     runs-on: ubuntu-latest
15:     steps:
16:       - run: ":"

(End of file - total 16 lines)
</content>
gateway · 2026-09-09 10:29:10
result
 .github/workflows/auto-approve.yml                 |   23 +-
 .github/workflows/ci-poller.yml                    |  245 +++-
 .github/workflows/cocoapods-keepalive.yml          |    1 -
 .github/workflows/publish.yml                      |  219 ++-
 .github/workflows/test.yml                         |    6 +-
 AGENTS.md                                          |    6 +
 README.md                                          |    9 +-
 docs/key_rotation_vault_changes.md                 |   40 +-
 docs/publish-issue-format.md                       |  107 --
 docs/rfc.md                                        |    9 +-
 package.json                                       |    5 +-
 scripts/generate-publish-issue-title-parser.js     |  104 --
 src/libs/__tests__/github.js                       |    2 +-
 src/libs/github.js                                 |    6 +-
 src/modules/__tests__/ci-poller-input.js           |   97 --
 src/modules/__tests__/ci-poller-workflow.js        |  197 ---
 ...
gateway · 2026-09-09 10:29:41
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-09 10:29:53
result
{"deployment_branch_policy":{"custom_branch_policies":true,"protected_branches":false},"protection_rules":[{"id":42720759,"node_id":"GA_kwDOEgGvjc4Ci933","type":"branch_policy"}]}
{"default_workflow_permissions":"write","can_approve_pull_request_reviews":true}{"message":"Conflict","errors":"All actions and workflows are allowed on this repository","documentation_url":"https://docs.github.com/rest/actions/permissions#get-allowed-actions-and-reusable-workflows-for-a-repository","status":"409"}gh: All actions and workflows are allowed on this repository (Conflict)
gateway · 2026-09-09 10:30:01
result
main	branch
gateway · 2026-09-09 12:53:16
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:   issues: write
9: 
10: jobs:
11:   auto-approve:
12:     runs-on: ubuntu-latest
13:     environment: production
14:     if: |
15:       (github.actor == 'sentry-release-bot[bot]' || github.actor == 'getsantry[bot]') &&
16:       startsWith(github.event.issue.title, 'publish: ')
17:     steps:
18:       - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
19:         with:
20:           sparse-checkout: auto-approve-repos.txt
21:           sparse-checkout-cone-mode: false
22:       - name: Get auth token
23:         id: token
24:         uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
25:         with:
26:           client-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
27:           private-key: ${{ ...
gateway · 2026-09-09 12:53:25
result
publish: getsentry/toolkit/../.__publish__@1.2.3 {"repo":"toolkit","path":"/../.__publish__","version":"1.2.3"}
publish: getsentryreit/space name@1.2.3 null
publish: getsentry/toolkit@--dry-run {"repo":"toolkit","version":"--dry-run"}
gateway · 2026-09-09 12:54:05
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:   repository_dispatch:
7:     types: [ci-poller]
8:   workflow_run:
9:     workflows: [Run CI Status Poller]
10:     types: [completed]
11: 
12: permissions:
13:   contents: read
14:   issues: write
15: 
16: jobs:
17:   check-ci:
18:     runs-on: ubuntu-latest
19:     environment: production
20:     # Skip entirely (no runner provisioned) when there's nothing to check.
21:     # Set to "true" by ci-pending.yml, reset to "false" here when done.
22:     # repository_dispatch and a successful main-branch run of the protected
23:     # workflow_dispatch relay are the trusted manual-recovery paths.
24:     if: >-
25:       vars.CI_POLLER_HAS_PENDING == 'true'
26:       || github.event_name == 'repository_dispatch'
27:       || (
28:       github.event_name == 'workflow_run'
29:       && ...
gateway · 2026-09-09 12:54:48
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-09 12:54:59
<!-- lore-recall:7ac79457-2fb2-4fc6-97b8-69c89719b042 --><!-- lore-recall:9b531d16-3b99-4cfa-8bd3-65a67be7d798 -->
gpt-5.6-sol · 2026-09-09 12:54:59 · ~60 tokens
result
diff --git a/.github/workflows/ci-poller.yml b/.github/workflows/ci-poller.yml
index 487e9da..17d5e91 100644
--- a/.github/workflows/ci-poller.yml
+++ b/.github/workflows/ci-poller.yml
@@ -3,16 +3,15 @@ name: CI Status Poller
 on:
   schedule:
     - cron: "*/5 * * * *"
-  workflow_dispatch:
-    inputs:
-      attempt:
-        description: Self-dispatch attempt counter (internal use)
-        required: false
-        default: "0"
+  repository_dispatch:
+    types: [ci-poller]
+  workflow_run:
+    workflows: [Run CI Status Poller]
+    types: [completed]
 
 permissions:
   contents: read
-  issues: read
+  issues: write
 
 jobs:
   check-ci:
@@ -20,18 +19,45 @@ jobs:
     environment: production
     # Skip entirely (no runner provisioned) when there's nothing to check.
     # Set to "true" by ci-pending.yml, reset to "false" here when done.
-    # Always allow workflow_dispatch for manual recovery.
-    if: vars.CI_POLLER_HAS_PENDING == 'true' || github.event_name == ...
gateway · 2026-09-09 12:55:50
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 AUTO_APPROVAL_ATTESTATION_PREFIX = "<!-- publish-auto-approval ";
5: const CI_READY_ATTESTATION_PREFIX = "<!-- publish-ci-ready ";
6: const ATTESTATION_SUFFIX = " -->";
7: 
8: function createAttestation(prefix, value) {
9:   return `${prefix}${Buffer.from(JSON.stringify(value)).toString(
10:     "base64url"
11:   )}${ATTESTATION_SUFFIX}`;
12: }
13: 
14: function requestDigest({ body, labels, title }) {
15:   if (typeof body !== "string" || !Array.isArray(labels)) {
16:     throw new Error("Invalid publish request");
17:   }
18: 
19:   return crypto
20:     .createHash("sha256")
21:     .update(
22:       JSON.stringify({
23:         body,
24:         dryRun: labels.some((label) => label.name === "dry-run"),
25:         title,
26:       })
27:     )
28:     ...
gateway · 2026-09-09 12:56:00
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:   hasAutoApprovalAttestation,
6: } = require("../modules/approval-attestation.js");
7: const {
8:   AUTO_APPROVAL_LABELER,
9:   AUTO_APPROVERS,
10:   authorizeApproval,
11:   isAutoApprovedRepository,
12: } = require("../modules/approval-authorizer.js");
13: 
14: function getAutoApprovedRepositories() {
15:   return new Set(
16:     fs
17:       .readFileSync("auto-approve-repos.txt", "utf8")
18:       .split(/\r?\n/)
19:       .filter(Boolean)
20:   );
21: }
22: 
23: async function getPermission({ owner, repository, username }) {
24:   const response = await getGitHubResponse(
25:     `repos/${encodeURIComponent(owner)}/${encodeURIComponent(
26:       repository
27:     )}/collaborators/${encodeURIComponent(username)}/permission`
28:   );
29: 
30:   if (!response.ok) {
31:     ...
gateway · 2026-09-09 12:56:06
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 { parsePublishTitle } = require("./details-from-context.js");
3: 
4: const AUTO_APPROVAL_LABELER = "sentry-internal-app[bot]";
5: const AUTO_APPROVERS = new Set(["getsantry[bot]", "sentry-release-bot[bot]"]);
6: 
7: function isAutoApprovedRepository({
8:   actor,
9:   autoApprovedRepositories,
10:   issueTitle,
11: }) {
12:   if (!AUTO_APPROVERS.has(actor)) {
13:     return false;
14:   }
15: 
16:   const title = parsePublishTitle(issueTitle);
17: 
18:   if (!title) {
19:     return false;
20:   }
21: 
22:   return autoApprovedRepositories.has(
23:     `getsentry/${title.repo}${title.path || ""}`
24:   );
25: }
26: 
27: async function authorizeApproval({ actor, issueTitle, getPermission }) {
28:   const title = parsePublishTitle(issueTitle);
29: 
30:   if (!title) {
31:     return { authorized: ...
gateway · 2026-09-09 12:56:17
result
## main...origin/main [behind 1]
 M .github/workflows/auto-approve.yml
 M .github/workflows/ci-poller.yml
 M .github/workflows/cocoapods-keepalive.yml
 M .github/workflows/publish.yml
 M .github/workflows/test.yml
 M AGENTS.md
 M README.md
 M docs/key_rotation_vault_changes.md
 M docs/rfc.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
?? .github/workflows/ci-poller-dispatch.yml
?? .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__/
?? ...
gateway · 2026-09-09 12:58:20
result
yarn run v1.22.22
$ vitest run --printConsoleTrace

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

 ✓ src/publish/__tests__/current-accepted-event.js (2 tests) 39ms
 ✓ src/modules/__tests__/approval-attestation.js (5 tests) 34ms
 ✓ src/publish/__tests__/authorize-approval.js (7 tests) 38ms
 ✓ src/publish/__tests__/validate-approval-attestation.js (9 tests) 54ms
 ✓ src/modules/__tests__/update-issue.js (6 tests) 13ms
 ✓ src/modules/__tests__/process-end-state.js (4 tests) 97ms
 ✓ src/publish/__tests__/workflow-action-pinning.js (16 tests) 9ms
 ✓ ...
gateway · 2026-09-09 12:58:29

Resume and complete the independent read-only review requested previously. Return substantive file:line evidence and end exactly MERGE or DO-NOT-MERGE. Do not edit files.

gateway · 2026-09-09 13:02:43
result
# REST API endpoints for users

Use the REST API to get public and private information about authenticated users.

> [!NOTE]
> Most endpoints use `Authorization: Bearer <YOUR-TOKEN>` and `Accept: application/vnd.github+json` headers, plus `X-GitHub-Api-Version: 2026-03-10`. Curl examples below omit these standard headers for brevity.

## Get the authenticated user

```
GET /user
```

OAuth app tokens and personal access tokens (classic) need the read:user scope, or the broader user scope, for this endpoint to return the private user response. The private user response includes additional fields such as private_gists, total_private_repos, owned_private_repos, disk_usage, collaborators, and two_factor_authentication. Tokens without these scopes receive the public user response.
The private and public user response types are unrelated to the private profile setting. ...
gateway · 2026-09-09 13:03:23
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-09 13:05:01
result
# REST API endpoints for users

Use the REST API to get public and private information about authenticated users.

> [!NOTE]
> Most endpoints use `Authorization: Bearer <YOUR-TOKEN>` and `Accept: application/vnd.github+json` headers, plus `X-GitHub-Api-Version: 2026-03-10`. Curl examples below omit these standard headers for brevity.

## Get the authenticated user

```
GET /user
```

OAuth app tokens and personal access tokens (classic) need the read:user scope, or the broader user scope, for this endpoint to return the private user response. The private user response includes additional fields such as private_gists, total_private_repos, owned_private_repos, disk_usage, collaborators, and two_factor_authentication. Tokens without these scopes receive the public user response.
The private and public user response types are unrelated to the private profile setting. ...
gateway · 2026-09-09 13:07:20
result
<!DOCTYPE html><html data-color-mode="auto" data-light-theme="light" data-dark-theme="dark" lang="en"><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width, initial-scale=1" data-next-head=""/><link rel="icon" type="image/png" href="/assets/cb-345/images/site/favicon.png" data-next-head=""/><link href="/manifest.json" rel="manifest" data-next-head=""/><meta name="google-site-verification" content="c1kuD-K2HIVF635lypcsWPoD4kilo5-jA_wBFyT4uMY" data-next-head=""/><title data-next-head="">REST API endpoints for users - GitHub Docs</title><meta name="description" content="Use the REST API to get public and private information about authenticated users." data-next-head=""/><link rel="alternate" hrefLang="es" href="https://docs.github.com/es/rest/users/users?apiVersion=2026-03-10" data-next-head=""/><link rel="alternate" hrefLang="ja" href="https://docs.github.com/ja/rest/users/users?apiVersion=2026-03-10" data-next-head=""/><link rel="alternate" ...
gateway · 2026-09-09 13:07:29
result
Ripgrep JSON record exceeded 65536 bytes
gateway · 2026-09-09 13:07:57
result
# REST API endpoints for users

Use the REST API to get public and private information about authenticated users.

> [!NOTE]
> Most endpoints use `Authorization: Bearer <YOUR-TOKEN>` and `Accept: application/vnd.github+json` headers, plus `X-GitHub-Api-Version: 2026-03-10`. Curl examples below omit these standard headers for brevity.

## Get the authenticated user

```
GET /user
```

OAuth app tokens and personal access tokens (classic) need the read:user scope, or the broader user scope, for this endpoint to return the private user response. The private user response includes additional fields such as private_gists, total_private_repos, owned_private_repos, disk_usage, collaborators, and two_factor_authentication. Tokens without these scopes receive the public user response.
The private and public user response types are unrelated to the private profile setting. ...
gateway · 2026-09-09 13:10:28
result
{"meta":{"title":"REST API endpoints for users","intro":"Use the REST API to get public and private information about authenticated users.","product":"REST API","breadcrumbs":[{"href":"/en/rest","title":"REST API"},{"href":"/en/rest/users","title":"Users"},{"href":"/en/rest/users/users","title":"Users"}],"documentType":"article"},"body":"# REST API endpoints for users\n\nUse the REST API to get public and private information about authenticated users.\n\n> [!NOTE]\n> Most endpoints use `Authorization: Bearer <YOUR-TOKEN>` and `Accept: application/vnd.github+json` headers, plus `X-GitHub-Api-Version: 2026-03-10`. Curl examples below omit these standard headers for brevity.\n\n## Get the authenticated user\n\n```\nGET /user\n```\n\nOAuth app tokens and personal access tokens (classic) need the read:user scope, or the broader user scope, for this endpoint to return the private user response. ...
gateway · 2026-09-09 13:17:17
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:   repository_dispatch:
7:     types: [ci-poller]
8:   workflow_run:
9:     workflows: [Run CI Status Poller]
10:     types: [completed]
11: 
12: permissions:
13:   contents: read
14:   issues: write
15: 
16: jobs:
17:   check-ci:
18:     runs-on: ubuntu-latest
19:     environment: production
20:     # Skip entirely (no runner provisioned) when there's nothing to check.
21:     # Set to "true" by ci-pending.yml, reset to "false" here when done.
22:     # repository_dispatch and a successful main-branch run of the protected
23:     # workflow_dispatch relay are the trusted manual-recovery paths.
24:     if: >-
25:       vars.CI_POLLER_HAS_PENDING == 'true'
26:       || github.event_name == 'repository_dispatch'
27:       || (
28:       github.event_name == 'workflow_run'
29:       && ...
gateway · 2026-09-09 13:19:44
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-09 13:19:58
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-09 13:21:02
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 AUTO_APPROVAL_ATTESTATION_PREFIX = "<!-- publish-auto-approval ";
5: const CI_READY_ATTESTATION_PREFIX = "<!-- publish-ci-ready ";
6: const ATTESTATION_SUFFIX = " -->";
7: 
8: function createAttestation(prefix, value) {
9:   return `${prefix}${Buffer.from(JSON.stringify(value)).toString(
10:     "base64url"
11:   )}${ATTESTATION_SUFFIX}`;
12: }
13: 
14: function requestDigest({ body, labels, title }) {
15:   if (typeof body !== "string" || !Array.isArray(labels)) {
16:     throw new Error("Invalid publish request");
17:   }
18: 
19:   return crypto
20:     .createHash("sha256")
21:     .update(
22:       JSON.stringify({
23:         body,
24:         dryRun: labels.some((label) => label.name === "dry-run"),
25:         title,
26:       })
27:     )
28:     ...
gateway · 2026-09-09 13:21:32
result
not-base64 ��~m��
!!!! 
e30garbage {} j�ځ
YWJjZA==junk abcd
eyJhIjoxfQ {"a":1}
gateway · 2026-09-09 13:21:53
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(
20:   title,
21:   { body = "", labels = [{ name: "accepted" }], state = "open" } = {}
22: ) {
23:   return { body, labels, state, title };
24: }
25: 
26: describe("validateApprovalAttestation", () => {
27:   test("accepts the current accepted event and its trusted attestation", async () => {
28:     const title = "publish: getsentry/sentry-javascript@10.0.0";
29:    ...
gateway · 2026-09-09 13:22:18
result
2c4c77d1cafa8d792ab4a9d449799221baf95176a47692ad9a0b350b0a2618ed  -
{"date":"2026-05-12T23:31:18Z","message":"chore(main): release 3.2.0 (#370)\n\n:robot: I have created a release *beep* *boop*\n---\n\n\n##\n[3.2.0](https://github.com/actions/create-github-app-token/compare/v3.1.1...v3.2.0)\n(2026-05-12)\n\n\n### Features\n\n* add support for enterprise-level GitHub Apps\n([#263](https://github.com/actions/create-github-app-token/issues/263))\n([952a2a7](https://github.com/actions/create-github-app-token/commit/952a2a7073df6bfa5f49bc469ec895b6ec1acea4))\n* support full repository names in `repositories` input\n([#372](https://github.com/actions/create-github-app-token/issues/372))\n([85eb8dd](https://github.com/actions/create-github-app-token/commit/85eb8dd41472213aed25d1a126460e0069138ab6))\n\n\n### Bug Fixes\n\n* **deps:** bump @actions/core from 3.0.0 to 3.0.1 in the\nproduction-dependencies ...
gateway · 2026-09-09 13:22:31
result
action.yml	9f45ab3e2605ffeb987d3e029f27ee4d96bca6a0
dist/main.cjs	20b90dce8c14c24222d6552fac6a4d5e2866a7d6
lib/main.js	7108c3e584b8584b76d6dfa346b01706ca5fa5b4
gateway · 2026-09-09 13:22:51
result
{"date":"2026-07-16T19:43:33Z","message":"backport fixes to releases-v6 (#2527)","reason":"valid","sha":"d23441a48e516b6c34aea4fa41551a30e30af803","verified":true}
{"date":"2026-07-14T02:48:03Z","message":"Update @actions/cache to 5.1.0 and add security overrides for undici and fast-xml-parser (#1579)\n\n* Update @actions/cache to 5.1.0, add security overrides for undici and fast-xml-parser, update licenses\n\n* Fix debug message for unchanged cache validation in tests\n\n* Fix formatting in cache validation test for unchanged cache\n\n* Bump version to 6.5.0","reason":"valid","sha":"249970729cb0ef3589644e2896645e5dc5ba9c38","verified":true}
{"date":"2025-09-24T13:47:33Z","message":"Merge pull request #1655 from actions/Link-/prepare-4.3.0\n\nPrepare `v4.3.0` release","reason":"valid","sha":"0057852bfaa89a56745cba8c7296529d2fc39830","verified":true}
gateway · 2026-09-09 13:23:04
result
{"date":"2026-07-16T19:43:47Z","message":"backport fixes to releases-v4 (#2524)","reason":"valid","sha":"11d5960a326750d5838078e36cf38b85af677262","verified":true}
gateway · 2026-09-09 13:23:59
result
(no output)
gateway · 2026-09-09 13:24:37
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:   issues: write
9: 
10: jobs:
11:   auto-approve:
12:     runs-on: ubuntu-latest
13:     environment: production
14:     if: |
15:       (github.actor == 'sentry-release-bot[bot]' || github.actor == 'getsantry[bot]') &&
16:       startsWith(github.event.issue.title, 'publish: ')
17:     steps:
18:       - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
19:         with:
20:           sparse-checkout: auto-approve-repos.txt
21:           sparse-checkout-cone-mode: false
22:       - name: Get auth token
23:         id: token
24:         uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
25:         with:
26:           client-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
27:           private-key: ${{ ...
gateway · 2026-09-09 13:25:07
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:   hasAutoApprovalAttestation,
6: } = require("../modules/approval-attestation.js");
7: const {
8:   AUTO_APPROVAL_LABELER,
9:   AUTO_APPROVERS,
10:   authorizeApproval,
11:   isAutoApprovedRepository,
12: } = require("../modules/approval-authorizer.js");
13: 
14: function getAutoApprovedRepositories() {
15:   return new Set(
16:     fs
17:       .readFileSync("auto-approve-repos.txt", "utf8")
18:       .split(/\r?\n/)
19:       .filter(Boolean)
20:   );
21: }
22: 
23: async function getPermission({ owner, repository, username }) {
24:   const response = await getGitHubResponse(
25:     `repos/${encodeURIComponent(owner)}/${encodeURIComponent(
26:       repository
27:     )}/collaborators/${encodeURIComponent(username)}/permission`
28:   );
29: 
30:   if (!response.ok) {
31:     ...
gateway · 2026-09-09 13:25:26
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/ci-poller-dispatch.yml</path>
<type>file</type>
<content>
1: name: Run CI Status Poller
2: 
3: on:
4:   workflow_dispatch:
5: 
6: permissions: {}
7: 
8: jobs:
9:   dispatch:
10:     # production only permits deployments from main. Repository secrets must
11:     # never be used by this workflow; protected environment secrets enforce
12:     # that an arbitrary dispatched ref cannot add them.
13:     environment: production
14:     runs-on: ubuntu-latest
15:     steps:
16:       - run: ":"

(End of file - total 16 lines)
</content>
gateway · 2026-09-09 13:26:37
result
## main...origin/main [behind 1]
 M .github/workflows/auto-approve.yml
 M .github/workflows/ci-poller.yml
 M .github/workflows/cocoapods-keepalive.yml
 M .github/workflows/publish.yml
 M .github/workflows/test.yml
 M AGENTS.md
 M README.md
 M docs/key_rotation_vault_changes.md
 M docs/rfc.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
?? .github/workflows/ci-poller-dispatch.yml
?? .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__/
?? ...
gateway · 2026-09-09 13:27:40
result
.github/workflows/auto-approve.yml
.github/workflows/ci-poller.yml
.github/workflows/cocoapods-keepalive.yml
.github/workflows/publish.yml
.github/workflows/test.yml
AGENTS.md
README.md
docs/key_rotation_vault_changes.md
docs/rfc.md
src/libs/__tests__/github.js
src/libs/github.js
src/modules/__tests__/details-from-context.js
src/modules/__tests__/update-issue.js
src/modules/details-from-context.js
src/modules/post-workflow-details.js
src/modules/process-end-state.js
src/modules/update-issue.js
src/publish/inputs.js
src/publish/post-result.js
src/publish/post-workflow-details.js
src/publish/update-issue.js
21	2	.github/workflows/auto-approve.yml
172	23	.github/workflows/ci-poller.yml
1	2	.github/workflows/cocoapods-keepalive.yml
131	8	.github/workflows/publish.yml
3	3	.github/workflows/test.yml
6	0	AGENTS.md
3	1	README.md
20	20	docs/key_rotation_vault_changes.md
5	4	docs/rfc.md
1	1	src/libs/__tests__/github.js
4	2	src/libs/github.js
21	1	src/modules/__tests__/details-from-context.js
28	...
gateway · 2026-09-09 13:30:31
result
CHANGELOG.md
api.github.com.2022-11-28.json
api.github.com.2022-11-28.yaml
api.github.com.2026-03-10.json
api.github.com.2026-03-10.yaml
api.github.com.json
api.github.com.yaml
dereferenced
gateway · 2026-09-09 13:31:53
result
https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2026-03-10.json
gateway · 2026-09-09 13:32:48
result
Response too large (exceeds 5MB limit)
gateway · 2026-09-09 13:33:16
result
api.github.com.2022-11-28.deref.json
api.github.com.2022-11-28.deref.yaml
api.github.com.2026-03-10.deref.json
api.github.com.2026-03-10.deref.yaml
api.github.com.deref.json
api.github.com.deref.yaml
gateway · 2026-09-09 13:33:27
result
(no output)
gateway · 2026-09-09 13:33:44
result
diff --git a/src/modules/__tests__/details-from-context.js b/src/modules/__tests__/details-from-context.js
index e6411c5..c3a65b2 100644
--- a/src/modules/__tests__/details-from-context.js
+++ b/src/modules/__tests__/details-from-context.js
@@ -1,6 +1,9 @@
 import { test, expect } from "vitest";
 
-const { detailsFromContext } = require("../details-from-context.js");
+const {
+  detailsFromContext,
+  parsePublishTitle,
+} = require("../details-from-context.js");
 
 const inputsArgs = {
   context: {
@@ -63,6 +66,23 @@ test("can parse version containing +", async () => {
   expect(result.version).toEqual("4.2.6+sentry1");
 });
 
+test("parses qualified and legacy publish titles", () => {
+  expect(
+    parsePublishTitle(
+      "publish: getsentry/sentry-javascript/packages/core@10.0.0"
+    )
+  ).toEqual({
+    path: "/packages/core",
+    repo: "sentry-javascript",
+    version: "10.0.0",
+  });
+  expect(parsePublishTitle("publish: sentry-javascript@10.0.0")).toEqual({
+    path: ...
gateway · 2026-09-09 13:34:24
result
(no output)
gateway · 2026-09-09 13:36:18
result
{
  "summary": "Get the authenticated user",
  "security": null,
  "xGithub": {
    "githubCloudOnly": false,
    "enabledForGitHubApps": false,
    "category": "users",
    "subcategory": "users"
  }
}
gateway · 2026-09-09 13:37:55
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/test.yml</path>
<type>file</type>
<content>
1: name: Test
2: on:
3:   push:
4:     branches:
5:       - main
6:   pull_request:
7: 
8: permissions:
9:   contents: read
10: 
11: defaults:
12:   run:
13:     shell: bash
14: jobs:
15:   unit-test:
16:     runs-on: ubuntu-latest
17:     name: unit tests
18:     steps:
19:       - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803
20:       - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38
21:         with:
22:           node-version: 24
23:       - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
24:         id: cache
25:         with:
26:           path: node_modules
27:           key: ${{ runner.os }}-node_modules-${{ hashFiles('package.json', 'yarn.lock') }}
28: 
29:       - name: Install Dependencies
30:         if: steps.cache.outputs.cache-hit != 'true'
31:         run: yarn install --frozen-lockfile
32: 
33:       - name: Test
34:    ...
gateway · 2026-09-09 13:38:37
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/ci-poller-workflow.js</path>
<type>file</type>
<content>
1: import { readFileSync } from "fs";
2: import { describe, expect, test } from "vitest";
3: 
4: const workflow = readFileSync(".github/workflows/ci-poller.yml", "utf8");
5: const manualDispatchWorkflow = readFileSync(
6:   ".github/workflows/ci-poller-dispatch.yml",
7:   "utf8"
8: );
9: 
10: describe("CI poller workflow", () => {
11:   test("checks out trusted code before requesting app tokens", () => {
12:     expect(workflow).toMatch(
13:       /- name: Get publish code\n\s+uses: actions\/checkout@[a-f0-9]{40}\n\s+with:\n(?:\s+#.*\n)*\s+ref: \$\{\{ github\.event\.repository\.default_branch \}\}/
14:     );
15: 
16:     expect(workflow.indexOf("Get publish code")).toBeLessThan(
17:       workflow.indexOf("Get auth token")
18:     );
19:   });
20: 
21:   test("uses a protected workflow dispatch relay for manual recovery", () => {
22:     ...
gateway · 2026-09-09 13:39:09
result
b2b489b4e6427c0dece46fd79a25de419ecdc868
17d5e91f956f3b5d8018d11d1a5f07499941b02d
781bd0850f4ea15ae89417acabdd353fc98b6bd0
ffa59824a15397dee70c1f85255d813d08c52c01
9ff9a9d55b07734dcbb33e7d3f4fa69178edb39c
da7f78e280490ead1558d6db6d752677c643fd2f
096024af8c999595952a6f029cdb06794be23e11
d64534399a1a23a4d5578e2fc1c7d6f98c17c0f7
69f70a434528fa2adf1333d5045ad6b473304118
42bcf376d395dafbc410b704f0bed5544cf6d024
3ea8bb7bc2b76c5ae4ea2ba0d2bc9325b2bc8582
baa0ef86bfc0d7fc0db974197cc7a227833eb271
5c0baa5457a54f9c444de3cd1bf1b473c065f9b7
4989a1966b38b32111a8e9d1471caa1c81e5dff6
28ed176707f937156903fd82acbfa7d712efeba4
9009a04c9746fe800c3d7f490595d1c6dff3d69f
d5c1f254e9610e0832a775dbea366939344beb0b
gateway · 2026-09-09 13:39:34
result
yarn run v1.22.22
$ vitest run --run --printConsoleTrace

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

 ✓ src/publish/__tests__/current-accepted-event.js (2 tests) 31ms
 ✓ src/publish/__tests__/validate-approval-attestation.js (9 tests) 44ms
 ✓ src/modules/__tests__/approval-attestation.js (5 tests) 75ms
 ✓ src/publish/__tests__/authorize-approval.js (7 tests) 55ms
 ✓ src/modules/__tests__/approval-authorizer.js (15 tests) 49ms
 ✓ src/publish/__tests__/record-ci-ready-attestation.js (3 tests) 23ms
 ✓ src/modules/__tests__/process-end-state.js (4 tests) 143ms
 ...
gateway · 2026-09-09 13:39:53
result
yarn run v1.22.22
$ eslint src .github --ignore-pattern '!.github'
Done in 3.25s.
gateway · 2026-09-09 13:40:23
result
d0ec32e8a417ba16e7de5ad8f975ff6252ef751607ff7e214f0d24063b72f129  -
d861d7b470bfeb8a0c3caf03d2d92ed670fe93381b87de1bb6961636e48d231a  -
d0ec32e8a417ba16e7de5ad8f975ff6252ef751607ff7e214f0d24063b72f129  -
d861d7b470bfeb8a0c3caf03d2d92ed670fe93381b87de1bb6961636e48d231a  -
gateway · 2026-09-09 13:40:37
result
Preparing worktree (detached HEAD 7c60ddb)
HEAD is now at 7c60ddb feat: accept workspace publish requests
error: patch failed: .github/workflows/ci-poller.yml:92
error: .github/workflows/ci-poller.yml: patch does not apply
error: patch failed: .github/workflows/cocoapods-keepalive.yml:1
error: .github/workflows/cocoapods-keepalive.yml: patch does not apply
error: patch failed: .github/workflows/publish.yml:117
error: .github/workflows/publish.yml: patch does not apply
error: patch failed: src/modules/__tests__/update-issue.js:129
error: src/modules/__tests__/update-issue.js: patch does not apply
error: patch failed: src/modules/details-from-context.js:15
error: src/modules/details-from-context.js: patch does not apply
error: patch failed: src/modules/update-issue.js:4
error: src/modules/update-issue.js: patch does not apply
error: patch failed: src/publish/inputs.js:1
error: src/publish/inputs.js: patch does not apply
gateway · 2026-09-09 13:47:40
result
(no output)
gateway · 2026-09-09 13:48:13
result
## main...origin/main [behind 1]
 M .github/workflows/auto-approve.yml
 M .github/workflows/ci-poller.yml
 M .github/workflows/cocoapods-keepalive.yml
 M .github/workflows/publish.yml
 M .github/workflows/test.yml
 M AGENTS.md
 M README.md
 M docs/key_rotation_vault_changes.md
 M docs/rfc.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
?? .github/workflows/ci-poller-dispatch.yml
?? .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__/
?? ...
gateway · 2026-09-09 13:49:39
result
{
  "summary": "Get the authenticated user",
  "description": "OAuth app tokens and personal access tokens (classic) need the `read:user` scope, or the broader `user` scope, for this endpoint to return the private user response. The private user response includes additional fields such as `private_gists`, `total_private_repos`, `owned_private_repos`, `disk_usage`, `collaborators`, and `two_factor_authentication`. Tokens without these scopes receive the public user response.\n\nThe private and public user response types are unrelated to the [private profile](https://docs.github.com/account-and-profile/concepts/personal-profile#private-profiles) setting. A token without scopes still authenticates as the token's owner, so values subject to private profile visibility, such as `followers` and `following`, may differ from an unauthenticated response.",
  "tags": [
    "users"
  ],
  "operationId": "users/get-authenticated",
  "externalDocs": {
    "description": "API method documentation",
 ...
gateway · 2026-09-09 13:50:19
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/publish.yml</path>
<type>file</type>
<content>
170:   publish:
171:     runs-on: ubuntu-latest
172:     environment: production
173:     name: Publish a new version
174:     # Publish when ci-ready is present (added by the poller after CI passes).
175:     # Fires ONLY on ci-ready label events — not accepted — to avoid racing
176:     # with waiting-for-ci on the same event. The poller always adds ci-ready
177:     # after checking CI (even if ci-ready was already present, waiting-for-ci
178:     # removes it first so a fresh labeled event fires), so this gate is
179:     # guaranteed to trigger on the happy path.
180:     if: >-
181:       github.event.issue.state == 'open'
182:       && github.event.label.name == 'ci-ready'
183:       && contains(github.event.issue.labels.*.name, 'accepted')
184:       && contains(github.event.issue.labels.*.name, 'ci-ready')
185:       && !contains(github.event.issue.labels.*.name, ...
gateway · 2026-09-09 13:50:50
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:   repository_dispatch:
7:     types: [ci-poller]
8:   workflow_run:
9:     workflows: [Run CI Status Poller]
10:     types: [completed]
11: 
12: permissions:
13:   contents: read
14:   issues: write
15: 
16: jobs:
17:   check-ci:
18:     runs-on: ubuntu-latest
19:     environment: production
20:     # Skip entirely (no runner provisioned) when there's nothing to check.
21:     # Set to "true" by ci-pending.yml, reset to "false" here when done.
22:     # repository_dispatch and a successful main-branch run of the protected
23:     # workflow_dispatch relay are the trusted manual-recovery paths.
24:     if: >-
25:       vars.CI_POLLER_HAS_PENDING == 'true'
26:       || github.event_name == 'repository_dispatch'
27:       || (
28:       github.event_name == 'workflow_run'
29:       && ...
gateway · 2026-09-09 13:51:07
result
getsentry/craft@sha256:9a4a5d5efa44a00c2215078ead39800d4aaa5a97908b94f45a64d7d506d6e14b OK
ghcr.io/getsentry/craft@sha256:9a4a5d5efa44a00c2215078ead39800d4aaa5a97908b94f45a64d7d506d6e14b unavailable
gateway · 2026-09-09 13:51:26
result
:100644 100644 d9763c7 0000000 M	.github/workflows/auto-approve.yml
:100644 100644 a3db430 0000000 M	.github/workflows/ci-poller.yml
:100644 100644 21c0696 0000000 M	.github/workflows/cocoapods-keepalive.yml
:100644 100644 cff6bc3 0000000 M	.github/workflows/publish.yml
:100644 100644 44692d0 0000000 M	.github/workflows/test.yml
:100644 100644 cf473df 0000000 M	AGENTS.md
:100644 100644 b8e07f0 0000000 M	README.md
:100644 100644 b005518 0000000 M	docs/key_rotation_vault_changes.md
:100644 100644 abc3a55 0000000 M	docs/rfc.md
:100644 100644 710091c 0000000 M	src/libs/__tests__/github.js
:100644 100644 e4d506f 0000000 M	src/libs/github.js
:100644 100644 e6411c5 0000000 M	src/modules/__tests__/details-from-context.js
:100644 100644 954ef51 0000000 M	src/modules/__tests__/update-issue.js
:100644 100644 e58b0be 0000000 M	src/modules/details-from-context.js
:100644 100644 278d23e 0000000 M	src/modules/post-workflow-details.js
:100644 100644 d8af41c 0000000 ...
gateway · 2026-09-09 14:12:22
result
/home/byk/Code/getsentry/publish/src/publish/__tests__/workflow-action-pinning.js
/home/byk/Code/getsentry/publish/src/publish/__tests__/record-auto-approval-attestation.js
/home/byk/Code/getsentry/publish/src/publish/__tests__/publish-workflow.js
/home/byk/Code/getsentry/publish/src/publish/__tests__/auto-approval-workflow.js
/home/byk/Code/getsentry/publish/src/publish/__tests__/ci-poller-workflow.js
/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-09 14:13:00
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-09 14:13:43
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:   issues: write
9: 
10: jobs:
11:   auto-approve:
12:     runs-on: ubuntu-latest
13:     environment: production
14:     if: |
15:       (github.actor == 'sentry-release-bot[bot]' || github.actor == 'getsantry[bot]') &&
16:       startsWith(github.event.issue.title, 'publish: ')
17:     steps:
18:       - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
19:         with:
20:           sparse-checkout: auto-approve-repos.txt
21:           sparse-checkout-cone-mode: false
22:       - name: Get auth token
23:         id: token
24:         uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
25:         with:
26:           client-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
27:           private-key: ${{ ...
gateway · 2026-09-09 14:14:42
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-09 14:14:53
result
Found 8 matches
/home/byk/Code/getsentry/publish/src/publish/__tests__/record-ci-ready-attestation.js:
  Line 8: } = require("../record-ci-ready-attestation.js");

  Line 11:   test("binds the current approval to the app that will add ci-ready", async () => {

  Line 48:   test("rejects an approval that changes before ci-ready is recorded", async () => {


/home/byk/Code/getsentry/publish/src/publish/__tests__/validate-approval-attestation.js:
  Line 307:       labels: [{ name: "accepted" }, { name: "ci-ready" }],

  Line 334:             label: { name: "ci-ready" },

  Line 362:   test("rejects a manually added ci-ready label", async () => {

  Line 366:       labels: [{ name: "accepted" }, { name: "ci-ready" }],

  Line 393:             label: { name: "ci-ready" },
gateway · 2026-09-09 14:15:02
result
e6dcd42feff3f4d6d1dbfb05e7a9dc15c86a1dc370b4f257cf5257a611b9a071  -
eac73c8e8d90eab94d9a3fd039cadc5195233546e8ec92cd4e10cab5786b1101  -
## main...origin/main [behind 1]
 M .github/workflows/auto-approve.yml
 M .github/workflows/ci-poller.yml
 M .github/workflows/cocoapods-keepalive.yml
 M .github/workflows/publish.yml
 M .github/workflows/test.yml
 M AGENTS.md
 M README.md
 M docs/key_rotation_vault_changes.md
 M docs/rfc.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
?? .github/workflows/ci-poller-dispatch.yml
?? .lore.md
?? src/modules/__tests__/approval-attestation.js
?? ...
gateway · 2026-09-09 14:15:11
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-09 14:15:25
result
21	2	.github/workflows/auto-approve.yml
172	23	.github/workflows/ci-poller.yml
1	2	.github/workflows/cocoapods-keepalive.yml
131	8	.github/workflows/publish.yml
3	3	.github/workflows/test.yml
6	0	AGENTS.md
3	1	README.md
20	20	docs/key_rotation_vault_changes.md
5	4	docs/rfc.md
1	1	src/libs/__tests__/github.js
4	2	src/libs/github.js
21	1	src/modules/__tests__/details-from-context.js
28	0	src/modules/__tests__/update-issue.js
14	3	src/modules/details-from-context.js
2	2	src/modules/post-workflow-details.js
2	2	src/modules/process-end-state.js
23	19	src/modules/update-issue.js
8	9	src/publish/inputs.js
4	4	src/publish/post-result.js
4	4	src/publish/post-workflow-details.js
26	7	src/publish/update-issue.js
33
gateway · 2026-09-09 14:17:31
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 AUTO_APPROVAL_ATTESTATION_PREFIX = "<!-- publish-auto-approval ";
5: const CI_READY_ATTESTATION_PREFIX = "<!-- publish-ci-ready ";
6: const ATTESTATION_SUFFIX = " -->";
7: 
8: function createAttestation(prefix, value) {
9:   return `${prefix}${Buffer.from(JSON.stringify(value)).toString(
10:     "base64url"
11:   )}${ATTESTATION_SUFFIX}`;
12: }
13: 
14: function requestDigest({ body, labels, title }) {
15:   if (typeof body !== "string" || !Array.isArray(labels)) {
16:     throw new Error("Invalid publish request");
17:   }
18: 
19:   return crypto
20:     .createHash("sha256")
21:     .update(
22:       JSON.stringify({
23:         body,
24:         dryRun: labels.some((label) => label.name === "dry-run"),
25:         title,
26:       })
27:     )
28:     ...
gateway · 2026-09-09 14:17:41
result
14720c4c499a2de70a06fffa6cfa3312ab6e3c16dbdcbf19fec1f1aca34c9897  -
570dcb023ae08f4fb8338923496a55bf1c2e012a944f9969650bc31feadd485c  .github/workflows/ci-poller-dispatch.yml
51898ad7a79e99d1a22a116a40953e5b52030a8e90c9eda5c46eb3ed3998b701  src/modules/approval-attestation.js
c9ca690c7ca173c6cdc973e511dadb5479ff10cd958f9b0c4f10f83017e6ec55  src/modules/approval-authorizer.js
7ced3d4b0d6137122942a8e3978a992ea20a30d0f12625ef9c04bf03d9bccb5a  src/publish/authorize-approval.js
89327251626058674db523d8e541475bb9bf94cce2359adba06a85f99e359895  src/publish/current-accepted-event.js
dc1dee4b851b54efec8ab987fbc6e27210c5ed099b7094112b48511a9bfd9382  src/publish/record-auto-approval-attestation.js
ae06156794d4bb8b0559b1c5473d75b35bf1816960678c58e46f5f4c387dce3a  src/publish/record-ci-ready-attestation.js
943aacefbec471c91f264dca602f0a37199f7a29875cc41adaa2429828693fa7  src/publish/validate-approval-attestation.js
gateway · 2026-09-09 14:21:05
result
No files found
gateway · 2026-09-09 14:25:26
result
83d210b29553ea6f4d97508821724a834c43856e
7c60ddb7f43040fe8fbfea70efc833f689c04e75
## main...origin/main [behind 1]
 M .github/workflows/auto-approve.yml
 M .github/workflows/ci-poller.yml
 M .github/workflows/cocoapods-keepalive.yml
 M .github/workflows/publish.yml
 M .github/workflows/test.yml
 M AGENTS.md
 M README.md
 M docs/key_rotation_vault_changes.md
 M docs/rfc.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
?? .github/workflows/ci-poller-dispatch.yml
?? .lore.md
?? src/modules/__tests__/approval-attestation.js
?? src/modules/__tests__/approval-authorizer.js
?? src/modules/approval-attestation.js
?? ...
gateway · 2026-09-09 14:25:37
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/publish.yml</path>
<type>file</type>
<content>
170:   publish:
171:     runs-on: ubuntu-latest
172:     environment: production
173:     name: Publish a new version
174:     # Publish when ci-ready is present (added by the poller after CI passes).
175:     # Fires ONLY on ci-ready label events — not accepted — to avoid racing
176:     # with waiting-for-ci on the same event. The poller always adds ci-ready
177:     # after checking CI (even if ci-ready was already present, waiting-for-ci
178:     # removes it first so a fresh labeled event fires), so this gate is
179:     # guaranteed to trigger on the happy path.
180:     if: >-
181:       github.event.issue.state == 'open'
182:       && github.event.label.name == 'ci-ready'
183:       && contains(github.event.issue.labels.*.name, 'accepted')
184:       && contains(github.event.issue.labels.*.name, 'ci-ready')
185:       && !contains(github.event.issue.labels.*.name, ...
gateway · 2026-09-09 14:25:43
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:   repository_dispatch:
7:     types: [ci-poller]
8:   workflow_run:
9:     workflows: [Run CI Status Poller]
10:     types: [completed]
11: 
12: permissions:
13:   contents: read
14:   issues: write
15: 
16: jobs:
17:   check-ci:
18:     runs-on: ubuntu-latest
19:     environment: production
20:     # Skip entirely (no runner provisioned) when there's nothing to check.
21:     # Set to "true" by ci-pending.yml, reset to "false" here when done.
22:     # repository_dispatch and a successful main-branch run of the protected
23:     # workflow_dispatch relay are the trusted manual-recovery paths.
24:     if: >-
25:       vars.CI_POLLER_HAS_PENDING == 'true'
26:       || github.event_name == 'repository_dispatch'
27:       || (
28:       github.event_name == 'workflow_run'
29:       && ...
gateway · 2026-09-09 14:29:06
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-09 14:29:14
result
<path>/home/byk/Code/getsentry/publish/src/publish/update-issue.js</path>
<type>file</type>
<content>
1: const { updateIssue } = require("../modules/update-issue.js");
2: const { getGitHubToken } = require("../libs/github");
3: const github = require("@actions/github");
4: 
5: async function main() {
6:   const context = github.context;
7:   const octokit = github.getOctokit(getGitHubToken());
8:   let inputs;
9: 
10:   if (process.env.PUBLISH_ARGS) {
11:     try {
12:       inputs = JSON.parse(process.env.PUBLISH_ARGS);
13:     } catch {
14:       console.warn("Could not parse publish inputs; skipping target update");
15:     }
16:   }
17: 
18:   await updateIssue({ context, octokit, inputs });
19: }
20: 
21: if (require.main === module) {
22:   main().catch((error) => {
23:     console.error(error);
24:     process.exitCode = 1;
25:   });
26: }
27: 
28: module.exports = { main };

(End of file - total 28 lines)
</content>
gateway · 2026-09-09 14:29:19
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/publish-workflow.js</path>
<type>file</type>
<content>
1: import { readFileSync } from "fs";
2: import { describe, expect, test } from "vitest";
3: 
4: const workflow = readFileSync(".github/workflows/publish.yml", "utf8");
5: 
6: describe("publish workflow", () => {
7:   test("revalidates approval immediately before Craft", () => {
8:     const fence = workflow.indexOf("Revalidate approval attestation");
9: 
10:     expect(fence).toBeGreaterThan(workflow.indexOf("Set targets"));
11:     expect(fence).toBeLessThan(workflow.indexOf("Publish using Craft"));
12:     expect(workflow.slice(fence)).toContain(
13:       'REQUIRE_CI_READY_ATTESTATION: "true"'
14:     );
15:   });
16: 
17:   test("pins Craft to an immutable digest", () => {
18:     expect(workflow).toMatch(
19:       /uses: docker:\/\/getsentry\/craft@sha256:[a-f0-9]{64}/
20:     );
21:   });
22: });

(End of file - total 22 lines)
</content>
gateway · 2026-09-09 14:34:17
result
# REST API endpoints for users

Use the REST API to get public and private information about authenticated users.

> [!NOTE]
> Most endpoints use `Authorization: Bearer <YOUR-TOKEN>` and `Accept: application/vnd.github+json` headers, plus `X-GitHub-Api-Version: 2026-03-10`. Curl examples below omit these standard headers for brevity.

## Get the authenticated user

```
GET /user
```

OAuth app tokens and personal access tokens (classic) need the read:user scope, or the broader user scope, for this endpoint to return the private user response. The private user response includes additional fields such as private_gists, total_private_repos, owned_private_repos, disk_usage, collaborators, and two_factor_authentication. Tokens without these scopes receive the public user response.
The private and public user response types are unrelated to the private profile setting. ...
gateway · 2026-09-09 14:34:28
result
yarn run v1.22.22
$ vitest run --printConsoleTrace
warning From Yarn 1.0 onwards, scripts don't require "--" for options to be forwarded. In a future version, any explicit "--" will be forwarded as-is to the scripts.

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

 ✓ src/modules/__tests__/approval-attestation.js (5 tests) 56ms
 ✓ src/publish/__tests__/authorize-approval.js (7 tests) 47ms
 ✓ src/modules/__tests__/approval-authorizer.js (15 tests) 16ms
 ✓ src/publish/__tests__/validate-approval-attestation.js (9 tests) 29ms
 ✓ src/modules/__tests__/post-workflow-details.js (1 test) 19ms
 ✓ src/publish/__tests__/record-auto-approval-attestation.js (2 ...
gateway · 2026-09-09 14:38:33
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/ci-poller-dispatch.yml</path>
<type>file</type>
<content>
1: name: Run CI Status Poller
2: 
3: on:
4:   workflow_dispatch:
5: 
6: permissions: {}
7: 
8: jobs:
9:   dispatch:
10:     # production only permits deployments from main. Repository secrets must
11:     # never be used by this workflow; protected environment secrets enforce
12:     # that an arbitrary dispatched ref cannot add them.
13:     environment: production
14:     runs-on: ubuntu-latest
15:     steps:
16:       - run: ":"

(End of file - total 16 lines)
</content>
gateway · 2026-09-09 14:40:46
result
<path>/home/byk/Code/getsentry/publish/package.json</path>
<type>file</type>
<content>
1: {
2:   "name": "publish",
3:   "private": true,
4:   "version": "0.0.1",
5:   "description": "Approval-based publishing system for Sentry",
6:   "main": "index.js",
7:   "repository": "git@github.com:getsentry/publish.git",
8:   "author": "Sentry Open Source <oss@sentry.io>",
9:   "license": "Apache-2.0",
10:   "volta": {
11:     "node": "24.0.0",
12:     "yarn": "1.22.22"
13:   },
14:   "resolutions": {
15:     "undici": "^6.23.0"
16:   },
17:   "scripts": {
18:     "test": "vitest run",
19:     "test:watch": "vitest",
20:     "lint": "eslint src .github --ignore-pattern '!.github'",
21:     "prettier": "prettier --write src"
22:   },
23:   "devDependencies": {
24:     "eslint": "^8.9.0",
25:     "eslint-config-prettier": "^8.3.0",
26:     "eslint-plugin-yml": "^0.13.0",
27:     "prettier": "^2.2.1",
28:     "vitest": "^4.1.0"
29:   },
30:   "dependencies": {
31:     "@actions/core": ...
gateway · 2026-09-09 14:42:43
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:   issues: write
9: 
10: jobs:
11:   auto-approve:
12:     runs-on: ubuntu-latest
13:     environment: production
14:     if: |
15:       (github.actor == 'sentry-release-bot[bot]' || github.actor == 'getsantry[bot]') &&
16:       startsWith(github.event.issue.title, 'publish: ')
17:     steps:
18:       - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
19:         with:
20:           sparse-checkout: auto-approve-repos.txt
21:           sparse-checkout-cone-mode: false
22:       - name: Get auth token
23:         id: token
24:         uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
25:         with:
26:           client-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
27:           private-key: ${{ ...
gateway · 2026-09-09 14:42:53
result
Found 18 matches
/home/byk/Code/getsentry/publish/.github/workflows/publish.yml:
  Line 36:         uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803

  Line 42:         uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1

  Line 52:         uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1

  Line 149:         uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1

  Line 192:         uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803

  Line 223:         uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38

  Line 268:         uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1

  Line 274:       - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803

  Line 353:       - uses: ...
gateway · 2026-09-09 14:43:01
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-09 14:43:15
result
## main...origin/main [behind 1]
 M .github/workflows/auto-approve.yml
 M .github/workflows/ci-poller.yml
 M .github/workflows/cocoapods-keepalive.yml
 M .github/workflows/publish.yml
 M .github/workflows/test.yml
 M AGENTS.md
 M README.md
 M docs/key_rotation_vault_changes.md
 M docs/rfc.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
?? .github/workflows/ci-poller-dispatch.yml
?? .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__/
?? ...
gateway · 2026-09-09 14:45:01
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-09 14:48:24
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 AUTO_APPROVAL_ATTESTATION_PREFIX = "<!-- publish-auto-approval ";
5: const CI_READY_ATTESTATION_PREFIX = "<!-- publish-ci-ready ";
6: const ATTESTATION_SUFFIX = " -->";
7: 
8: function createAttestation(prefix, value) {
9:   return `${prefix}${Buffer.from(JSON.stringify(value)).toString(
10:     "base64url"
11:   )}${ATTESTATION_SUFFIX}`;
12: }
13: 
14: function requestDigest({ body, labels, title }) {
15:   if (typeof body !== "string" || !Array.isArray(labels)) {
16:     throw new Error("Invalid publish request");
17:   }
18: 
19:   return crypto
20:     .createHash("sha256")
21:     .update(
22:       JSON.stringify({
23:         body,
24:         dryRun: labels.some((label) => label.name === "dry-run"),
25:         title,
26:       })
27:     )
28:     ...
gateway · 2026-09-09 14:51:05
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-09 14:51:18
result
<path>/home/byk/Code/getsentry/publish/src/modules/__tests__/details-from-context.js</path>
<type>file</type>
<content>
1: import { test, expect } from "vitest";
2: 
3: const {
4:   detailsFromContext,
5:   parsePublishTitle,
6: } = require("../details-from-context.js");
7: 
8: const inputsArgs = {
9:   context: {
10:     repo: { owner: "getsentry", repo: "publish" },
11:     payload: {
12:       issue: {
13:         number: "223",
14:         title: "publish: getsentry/sentry@21.3.1",
15:         body: `
16: Requested by: @BYK
17: 
18: Merge target: custom-branch
19: 
20: Quick links:
21: - [View changes](https://github.com/getsentry/sentry/compare/21.3.0...refs/heads/releases/21.3.1)
22: - [View check runs](https://github.com/getsentry/sentry/commit/7e5ca7ed5581552de066e2a8bc295b8306be38ac/checks/)
23: 
24: Assign the **accepted** label to this issue to approve the release. ...
gateway · 2026-09-09 14:53:17
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/publish.yml</path>
<type>file</type>
<content>
170:   publish:
171:     runs-on: ubuntu-latest
172:     environment: production
173:     name: Publish a new version
174:     # Publish when ci-ready is present (added by the poller after CI passes).
175:     # Fires ONLY on ci-ready label events — not accepted — to avoid racing
176:     # with waiting-for-ci on the same event. The poller always adds ci-ready
177:     # after checking CI (even if ci-ready was already present, waiting-for-ci
178:     # removes it first so a fresh labeled event fires), so this gate is
179:     # guaranteed to trigger on the happy path.
180:     if: >-
181:       github.event.issue.state == 'open'
182:       && github.event.label.name == 'ci-ready'
183:       && contains(github.event.issue.labels.*.name, 'accepted')
184:       && contains(github.event.issue.labels.*.name, 'ci-ready')
185:       && !contains(github.event.issue.labels.*.name, ...
gateway · 2026-09-09 14:56:04
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:   repository_dispatch:
7:     types: [ci-poller]
8:   workflow_run:
9:     workflows: [Run CI Status Poller]
10:     types: [completed]
11: 
12: permissions:
13:   contents: read
14:   issues: write
15: 
16: jobs:
17:   check-ci:
18:     runs-on: ubuntu-latest
19:     environment: production
20:     # Skip entirely (no runner provisioned) when there's nothing to check.
21:     # Set to "true" by ci-pending.yml, reset to "false" here when done.
22:     # repository_dispatch and a successful main-branch run of the protected
23:     # workflow_dispatch relay are the trusted manual-recovery paths.
24:     if: >-
25:       vars.CI_POLLER_HAS_PENDING == 'true'
26:       || github.event_name == 'repository_dispatch'
27:       || (
28:       github.event_name == 'workflow_run'
29:       && ...
gateway · 2026-09-09 15:01:00
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/ci-poller-dispatch.yml</path>
<type>file</type>
<content>
1: name: Run CI Status Poller
2: 
3: on:
4:   workflow_dispatch:
5: 
6: permissions: {}
7: 
8: jobs:
9:   dispatch:
10:     # production only permits deployments from main. Repository secrets must
11:     # never be used by this workflow; protected environment secrets enforce
12:     # that an arbitrary dispatched ref cannot add them.
13:     environment: production
14:     runs-on: ubuntu-latest
15:     steps:
16:       - run: ":"

(End of file - total 16 lines)
</content>
gateway · 2026-09-09 15:05:00
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-09 15:05:53
result
<path>/home/byk/Code/getsentry/publish/src/publish/update-issue.js</path>
<type>file</type>
<content>
1: const { updateIssue } = require("../modules/update-issue.js");
2: const { getGitHubToken } = require("../libs/github");
3: const github = require("@actions/github");
4: 
5: async function main() {
6:   const context = github.context;
7:   const octokit = github.getOctokit(getGitHubToken());
8:   let inputs;
9: 
10:   if (process.env.PUBLISH_ARGS) {
11:     try {
12:       inputs = JSON.parse(process.env.PUBLISH_ARGS);
13:     } catch {
14:       console.warn("Could not parse publish inputs; skipping target update");
15:     }
16:   }
17: 
18:   await updateIssue({ context, octokit, inputs });
19: }
20: 
21: if (require.main === module) {
22:   main().catch((error) => {
23:     console.error(error);
24:     process.exitCode = 1;
25:   });
26: }
27: 
28: module.exports = { main };

(End of file - total 28 lines)
</content>
gateway · 2026-09-09 15:06:10
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:   hasAutoApprovalAttestation,
6: } = require("../modules/approval-attestation.js");
7: const {
8:   AUTO_APPROVAL_LABELER,
9:   AUTO_APPROVERS,
10:   authorizeApproval,
11:   isAutoApprovedRepository,
12: } = require("../modules/approval-authorizer.js");
13: 
14: function getAutoApprovedRepositories() {
15:   return new Set(
16:     fs
17:       .readFileSync("auto-approve-repos.txt", "utf8")
18:       .split(/\r?\n/)
19:       .filter(Boolean)
20:   );
21: }
22: 
23: async function getPermission({ owner, repository, username }) {
24:   const response = await getGitHubResponse(
25:     `repos/${encodeURIComponent(owner)}/${encodeURIComponent(
26:       repository
27:     )}/collaborators/${encodeURIComponent(username)}/permission`
28:   );
29: 
30:   if (!response.ok) {
31:     ...
gateway · 2026-09-09 15:06:59
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:           state: "open",
23:           title,
24:         }),
25:         ...
gateway · 2026-09-09 15:07:08
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/publish-workflow.js</path>
<type>file</type>
<content>
1: import { readFileSync } from "fs";
2: import { describe, expect, test } from "vitest";
3: 
4: const workflow = readFileSync(".github/workflows/publish.yml", "utf8");
5: 
6: describe("publish workflow", () => {
7:   test("revalidates approval immediately before Craft", () => {
8:     const fence = workflow.indexOf("Revalidate approval attestation");
9: 
10:     expect(fence).toBeGreaterThan(workflow.indexOf("Set targets"));
11:     expect(fence).toBeLessThan(workflow.indexOf("Publish using Craft"));
12:     expect(workflow.slice(fence)).toContain(
13:       'REQUIRE_CI_READY_ATTESTATION: "true"'
14:     );
15:   });
16: 
17:   test("pins Craft to an immutable digest", () => {
18:     expect(workflow).toMatch(
19:       /uses: docker:\/\/getsentry\/craft@sha256:[a-f0-9]{64}/
20:     );
21:   });
22: });

(End of file - total 22 lines)
</content>
gateway · 2026-09-09 15:07:34
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/ci-poller-workflow.js</path>
<type>file</type>
<content>
1: import { readFileSync } from "fs";
2: import { describe, expect, test } from "vitest";
3: 
4: const workflow = readFileSync(".github/workflows/ci-poller.yml", "utf8");
5: const manualDispatchWorkflow = readFileSync(
6:   ".github/workflows/ci-poller-dispatch.yml",
7:   "utf8"
8: );
9: 
10: describe("CI poller workflow", () => {
11:   test("checks out trusted code before requesting app tokens", () => {
12:     expect(workflow).toMatch(
13:       /- name: Get publish code\n\s+uses: actions\/checkout@[a-f0-9]{40}\n\s+with:\n(?:\s+#.*\n)*\s+ref: \$\{\{ github\.event\.repository\.default_branch \}\}/
14:     );
15: 
16:     expect(workflow.indexOf("Get publish code")).toBeLessThan(
17:       workflow.indexOf("Get auth token")
18:     );
19:   });
20: 
21:   test("uses a protected workflow dispatch relay for manual recovery", () => {
22:     ...
gateway · 2026-09-09 15:08:02
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/workflow-action-pinning.js</path>
<type>file</type>
<content>
1: import { readFileSync, readdirSync } from "fs";
2: import { describe, expect, test } from "vitest";
3: 
4: const workflowPaths = readdirSync(".github/workflows")
5:   .filter((path) => path.endsWith(".yml"))
6:   .map((path) => `.github/workflows/${path}`);
7: const workflows = workflowPaths.map((path) => ({
8:   path,
9:   contents: readFileSync(path, "utf8"),
10: }));
11: const SECRET_BEARING_JOBS = {
12:   ".github/workflows/auto-approve.yml": ["auto-approve"],
13:   ".github/workflows/ci-poller.yml": ["check-ci"],
14:   ".github/workflows/cocoapods-keepalive.yml": ["keepalive"],
15:   ".github/workflows/publish.yml": ["waiting-for-ci", "publish"],
16: };
17: 
18: function jobContents(workflow, jobName) {
19:   const header = `  ${jobName}:`;
20:   const jobStart = workflow.indexOf(header);
21:   const nextJobOffset = workflow
22:     .slice(jobStart + ...
gateway · 2026-09-09 15:09:51
result
# REST API endpoints for users

Use the REST API to get public and private information about authenticated users.

> [!NOTE]
> Most endpoints use `Authorization: Bearer <YOUR-TOKEN>` and `Accept: application/vnd.github+json` headers, plus `X-GitHub-Api-Version: 2026-03-10`. Curl examples below omit these standard headers for brevity.

## Get the authenticated user

```
GET /user
```

OAuth app tokens and personal access tokens (classic) need the read:user scope, or the broader user scope, for this endpoint to return the private user response. The private user response includes additional fields such as private_gists, total_private_repos, owned_private_repos, disk_usage, collaborators, and two_factor_authentication. Tokens without these scopes receive the public user response.
The private and public user response types are unrelated to the private profile setting. ...
gateway · 2026-09-09 15:13:59
result
# Authenticating as a GitHub App installation

You can make your GitHub App authenticate as an installation in order to make API requests that affect resources owned by the account where the app is installed.

## About authentication as a GitHub App installation

Once your GitHub App is installed on an account, you can make it authenticate as an app installation for API requests. This allows the app to access resources owned by that installation, as long as the app was granted the necessary repository access and permissions. API requests made by an app installation are attributed to the app. For more information about installing GitHub Apps, see [Installing your own GitHub App](/en/apps/using-github-apps/installing-your-own-github-app).

For example, if you want your app to change the `Status` field of an issue on a project owned by an organization called "octo-org," then you would authenticate as the octo-org installation of your app. ...
gateway · 2026-09-09 15:15:34
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:   issues: write
9: 
10: jobs:
11:   auto-approve:
12:     runs-on: ubuntu-latest
13:     environment: production
14:     if: |
15:       (github.actor == 'sentry-release-bot[bot]' || github.actor == 'getsantry[bot]') &&
16:       startsWith(github.event.issue.title, 'publish: ')
17:     steps:
18:       - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
19:         with:
20:           sparse-checkout: auto-approve-repos.txt
21:           sparse-checkout-cone-mode: false
22:       - name: Get auth token
23:         id: token
24:         uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
25:         with:
26:           client-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
27:           private-key: ${{ ...
gateway · 2026-09-09 15:16:05
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 { parsePublishTitle } = require("./details-from-context.js");
3: 
4: const AUTO_APPROVAL_LABELER = "sentry-internal-app[bot]";
5: const AUTO_APPROVERS = new Set(["getsantry[bot]", "sentry-release-bot[bot]"]);
6: 
7: function isAutoApprovedRepository({
8:   actor,
9:   autoApprovedRepositories,
10:   issueTitle,
11: }) {
12:   if (!AUTO_APPROVERS.has(actor)) {
13:     return false;
14:   }
15: 
16:   const title = parsePublishTitle(issueTitle);
17: 
18:   if (!title) {
19:     return false;
20:   }
21: 
22:   return autoApprovedRepositories.has(
23:     `getsentry/${title.repo}${title.path || ""}`
24:   );
25: }
26: 
27: async function authorizeApproval({ actor, issueTitle, getPermission }) {
28:   const title = parsePublishTitle(issueTitle);
29: 
30:   if (!title) {
31:     return { authorized: ...
gateway · 2026-09-09 15:16:14
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 AUTO_APPROVAL_ATTESTATION_PREFIX = "<!-- publish-auto-approval ";
5: const CI_READY_ATTESTATION_PREFIX = "<!-- publish-ci-ready ";
6: const ATTESTATION_SUFFIX = " -->";
7: 
8: function createAttestation(prefix, value) {
9:   return `${prefix}${Buffer.from(JSON.stringify(value)).toString(
10:     "base64url"
11:   )}${ATTESTATION_SUFFIX}`;
12: }
13: 
14: function requestDigest({ body, labels, title }) {
15:   if (typeof body !== "string" || !Array.isArray(labels)) {
16:     throw new Error("Invalid publish request");
17:   }
18: 
19:   return crypto
20:     .createHash("sha256")
21:     .update(
22:       JSON.stringify({
23:         body,
24:         dryRun: labels.some((label) => label.name === "dry-run"),
25:         title,
26:       })
27:     )
28:     ...
gateway · 2026-09-09 15:16:34
result
## main...origin/main [behind 1]
 M .github/workflows/auto-approve.yml
 M .github/workflows/ci-poller.yml
 M .github/workflows/cocoapods-keepalive.yml
 M .github/workflows/publish.yml
 M .github/workflows/test.yml
 M AGENTS.md
 M README.md
 M docs/key_rotation_vault_changes.md
 M docs/rfc.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
?? .github/workflows/ci-poller-dispatch.yml
?? .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__/
?? ...
gateway · 2026-09-09 15:16:56
result
diff --git a/.github/workflows/ci-poller.yml b/.github/workflows/ci-poller.yml
index a3db430..17d5e91 100644
--- a/.github/workflows/ci-poller.yml
+++ b/.github/workflows/ci-poller.yml
@@ -3,16 +3,15 @@ name: CI Status Poller
 on:
   schedule:
     - cron: "*/5 * * * *"
-  workflow_dispatch:
-    inputs:
-      attempt:
-        description: Self-dispatch attempt counter (internal use)
-        required: false
-        default: "0"
+  repository_dispatch:
+    types: [ci-poller]
+  workflow_run:
+    workflows: [Run CI Status Poller]
+    types: [completed]
 
 permissions:
   contents: read
-  issues: read
+  issues: write
 
 jobs:
   check-ci:
@@ -20,18 +19,45 @@ jobs:
     environment: production
     # Skip entirely (no runner provisioned) when there's nothing to check.
     # Set to "true" by ci-pending.yml, reset to "false" here when done.
-    # Always allow workflow_dispatch for manual recovery.
-    if: vars.CI_POLLER_HAS_PENDING == 'true' || github.event_name == ...
gateway · 2026-09-09 15:17:08
result
yarn run v1.22.22
$ vitest run --run --printConsoleTrace

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

 ✓ src/publish/__tests__/authorize-approval.js (7 tests) 39ms
 ✓ src/modules/__tests__/approval-attestation.js (5 tests) 80ms
 ✓ src/modules/__tests__/update-issue.js (6 tests) 33ms
 ✓ src/publish/__tests__/validate-approval-attestation.js (9 tests) 56ms
 ✓ src/publish/__tests__/current-accepted-event.js (2 tests) 49ms
 ✓ src/modules/__tests__/process-end-state.js (4 tests) 94ms
 ✓ src/publish/__tests__/record-ci-ready-attestation.js (3 tests) 19ms
 ...
gateway · 2026-09-09 15:17:34
result
yarn run v1.22.22
$ eslint src .github --ignore-pattern '!.github'
Done in 1.26s.
gateway · 2026-09-09 15:17:44
result
58b43354ebad6c25f3a689f9e6672a60632c9461d537dd9229c85a03bfc44ef4  -
8ca4aa7afcb6a1c171f695d488a06b8cf4d08436a414a3223a18b33a08db4fc6  -
gateway · 2026-09-09 15:18:31
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-09 15:18:38
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-09 15:19:00
result
Found 3 matches
/home/byk/.local/share/opencode/tool-output/tool_086bff915001ubSkY0fDYYjGVE:
  Line 1232: ## `workflow_run`

  Line 1236: | [`workflow_run`](/en/webhooks/webhook-events-and-payloads#workflow_run) | - `completed`<br/>- `requested`<br/>- `in_progress` | Last commit on default branch | Default branch |

  Line 1294: You can use the `branches` or `branches-ignore` filter to specify what branches the triggering workflow must run on in order to trigger your workflow. For more information, see [Workflow syntax for GitHub Actions](/en/actions/reference/workflows-and-actions/workflow-syntax#onworkflow_runbranchesbranches-ignore). For example, a workflow with the following trigger will only run when the workflow named `Build` runs on a branch named `canary`.
gateway · 2026-09-09 15:19:13
result
<path>/home/byk/.local/share/opencode/tool-output/tool_086bff915001ubSkY0fDYYjGVE</path>
<type>file</type>
<content>
1232: ## `workflow_run`
1233: 
1234: | Webhook event payload                                                   | Activity types                                      | `GITHUB_SHA`                  | `GITHUB_REF`   |
1235: | ----------------------------------------------------------------------- | --------------------------------------------------- | ----------------------------- | -------------- |
1236: | [`workflow_run`](/en/webhooks/webhook-events-and-payloads#workflow_run) | - `completed`<br/>- `requested`<br/>- `in_progress` | Last commit on default branch | Default branch |
1237: 
1238: > \[!NOTE]
1239: >
1240: > * More than one activity type triggers this event. The `requested` activity type does not occur when a workflow is re-run. For information about each activity type, see [Webhook events and payloads](/en/webhooks/webhook-events-and-payloads#workflow_run). ...
gateway · 2026-09-09 15:23:20
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-09 15:31:40
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/publish.yml</path>
<type>file</type>
<content>
170:   publish:
171:     runs-on: ubuntu-latest
172:     environment: production
173:     name: Publish a new version
174:     # Publish when ci-ready is present (added by the poller after CI passes).
175:     # Fires ONLY on ci-ready label events — not accepted — to avoid racing
176:     # with waiting-for-ci on the same event. The poller always adds ci-ready
177:     # after checking CI (even if ci-ready was already present, waiting-for-ci
178:     # removes it first so a fresh labeled event fires), so this gate is
179:     # guaranteed to trigger on the happy path.
180:     if: >-
181:       github.event.issue.state == 'open'
182:       && github.event.label.name == 'ci-ready'
183:       && contains(github.event.issue.labels.*.name, 'accepted')
184:       && contains(github.event.issue.labels.*.name, 'ci-ready')
185:       && !contains(github.event.issue.labels.*.name, ...
gateway · 2026-09-09 15:32:18
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:   repository_dispatch:
7:     types: [ci-poller]
8:   workflow_run:
9:     workflows: [Run CI Status Poller]
10:     types: [completed]
11: 
12: permissions:
13:   contents: read
14:   issues: write
15: 
16: jobs:
17:   check-ci:
18:     runs-on: ubuntu-latest
19:     environment: production
20:     # Skip entirely (no runner provisioned) when there's nothing to check.
21:     # Set to "true" by ci-pending.yml, reset to "false" here when done.
22:     # repository_dispatch and a successful main-branch run of the protected
23:     # workflow_dispatch relay are the trusted manual-recovery paths.
24:     if: >-
25:       vars.CI_POLLER_HAS_PENDING == 'true'
26:       || github.event_name == 'repository_dispatch'
27:       || (
28:       github.event_name == 'workflow_run'
29:       && ...
gateway · 2026-09-09 15:32:26
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/ci-poller.yml</path>
<type>file</type>
<content>
115:           # Check each issue's CI status
116:           echo "$issues" | jq -c '.[]' | while read -r issue; do
117:             number=$(echo "$issue" | jq -r '.number')
118:             title=$(echo "$issue" | jq -r '.title')
119:             body=$(echo "$issue" | jq -r '.body')
120: 
121:             # Bind this poll cycle to the current accepted-label event before
122:             # inspecting CI. Never move a release to ci-ready after it changes.
123:             if ! accepted_event=$(APPROVAL_TOKEN="$GH_TOKEN" \
124:               APPROVAL_ISSUE_NUMBER="$number" \
125:               APPROVAL_ISSUE_REPOSITORY="$GITHUB_REPOSITORY" \
126:               node src/publish/current-accepted-event.js); then
127:               echo "::warning::Could not bind approval event for issue #${number}. Removing accepted."
128:               gh issue edit "$number" -R "$GITHUB_REPOSITORY" ...
gateway · 2026-09-09 15:32:38
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/ci-poller.yml</path>
<type>file</type>
<content>
345:                 EXPECTED_ACCEPTED_EVENT_ID="$accepted_event_id" \
346:                 node src/publish/validate-approval-attestation.js; then
347:                 echo "::warning::Approval for issue #${number} changed before ci-ready. Removing accepted."
348:                 gh issue edit "$number" -R "$GITHUB_REPOSITORY" \
349:                   --remove-label "ci-pending" \
350:                   --remove-label "accepted"
351:                 gh issue comment "$number" -R "$GITHUB_REPOSITORY" \
352:                   --body "Approval is invalid or could not be verified. Re-add the accepted label to retry after resolving the issue."
353:                 continue
354:               fi
355: 
356:               echo "  CI passed! Adding ci-ready label."
357:               gh issue edit "$number" -R "$GITHUB_REPOSITORY" \
358:                 --remove-label "ci-pending" \
359:  ...
gateway · 2026-09-09 15:32:53
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/ci-poller-workflow.js</path>
<type>file</type>
<content>
1: import { readFileSync } from "fs";
2: import { describe, expect, test } from "vitest";
3: 
4: const workflow = readFileSync(".github/workflows/ci-poller.yml", "utf8");
5: const manualDispatchWorkflow = readFileSync(
6:   ".github/workflows/ci-poller-dispatch.yml",
7:   "utf8"
8: );
9: 
10: describe("CI poller workflow", () => {
11:   test("checks out trusted code before requesting app tokens", () => {
12:     expect(workflow).toMatch(
13:       /- name: Get publish code\n\s+uses: actions\/checkout@[a-f0-9]{40}\n\s+with:\n(?:\s+#.*\n)*\s+ref: \$\{\{ github\.event\.repository\.default_branch \}\}/
14:     );
15: 
16:     expect(workflow.indexOf("Get publish code")).toBeLessThan(
17:       workflow.indexOf("Get auth token")
18:     );
19:   });
20: 
21:   test("uses a protected workflow dispatch relay for manual recovery", () => {
22:     ...
gateway · 2026-09-09 15:33:10
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/ci-poller-dispatch.yml</path>
<type>file</type>
<content>
1: name: Run CI Status Poller
2: 
3: on:
4:   workflow_dispatch:
5: 
6: permissions: {}
7: 
8: jobs:
9:   dispatch:
10:     # production only permits deployments from main. Repository secrets must
11:     # never be used by this workflow; protected environment secrets enforce
12:     # that an arbitrary dispatched ref cannot add them.
13:     environment: production
14:     runs-on: ubuntu-latest
15:     steps:
16:       - run: ":"

(End of file - total 16 lines)
</content>
gateway · 2026-09-09 15:33:27
result
{"name":"main","type":"branch"}
{"can_admins_bypass":false,"deployment_branch_policy":{"custom_branch_policies":true,"protected_branches":false},"protection_rules":[{"id":42720759,"node_id":"GA_kwDOEgGvjc4Ci933","type":"branch_policy"}]}
gateway · 2026-09-09 15:33:38
result
diff --git a/.github/workflows/auto-approve.yml b/.github/workflows/auto-approve.yml
index d9763c7..b2b489b 100644
--- a/.github/workflows/auto-approve.yml
+++ b/.github/workflows/auto-approve.yml
@@ -5,25 +5,44 @@ on:
 
 permissions:
   contents: read
+  issues: write
 
 jobs:
   auto-approve:
     runs-on: ubuntu-latest
+    environment: production
     if: |
       (github.actor == 'sentry-release-bot[bot]' || github.actor == 'getsantry[bot]') &&
       startsWith(github.event.issue.title, 'publish: ')
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
         with:
           sparse-checkout: auto-approve-repos.txt
           sparse-checkout-cone-mode: false
       - name: Get auth token
         id: token
-        uses: actions/create-github-app-token@v3
+        uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
         with:
           client-id: ${{ vars.SENTRY_INTERNAL_APP_ID ...
gateway · 2026-09-09 15:34:09
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:   issues: write
9: 
10: jobs:
11:   auto-approve:
12:     runs-on: ubuntu-latest
13:     environment: production
14:     if: |
15:       (github.actor == 'sentry-release-bot[bot]' || github.actor == 'getsantry[bot]') &&
16:       startsWith(github.event.issue.title, 'publish: ')
17:     steps:
18:       - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
19:         with:
20:           sparse-checkout: auto-approve-repos.txt
21:           sparse-checkout-cone-mode: false
22:       - name: Get auth token
23:         id: token
24:         uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
25:         with:
26:           client-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
27:           private-key: ${{ ...
gateway · 2026-09-09 15:34:17
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:   hasAutoApprovalAttestation,
6: } = require("../modules/approval-attestation.js");
7: const {
8:   AUTO_APPROVAL_LABELER,
9:   AUTO_APPROVERS,
10:   authorizeApproval,
11:   isAutoApprovedRepository,
12: } = require("../modules/approval-authorizer.js");
13: 
14: function getAutoApprovedRepositories() {
15:   return new Set(
16:     fs
17:       .readFileSync("auto-approve-repos.txt", "utf8")
18:       .split(/\r?\n/)
19:       .filter(Boolean)
20:   );
21: }
22: 
23: async function getPermission({ owner, repository, username }) {
24:   const response = await getGitHubResponse(
25:     `repos/${encodeURIComponent(owner)}/${encodeURIComponent(
26:       repository
27:     )}/collaborators/${encodeURIComponent(username)}/permission`
28:   );
29: 
30:   if (!response.ok) {
31:     ...
gateway · 2026-09-09 15:34:26
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-09 15:34:36
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/authorize-approval.js</path>
<type>file</type>
<content>
1: import { afterEach, describe, expect, test, vi } from "vitest";
2: import fs from "fs";
3: const {
4:   createAutoApprovalAttestation,
5: } = require("../../modules/approval-attestation.js");
6: const {
7:   getAutoApprovedRepositories,
8:   main,
9: } = require("../authorize-approval.js");
10: 
11: const originalEnvironment = { ...process.env };
12: 
13: afterEach(() => {
14:   process.env = { ...originalEnvironment };
15:   vi.restoreAllMocks();
16:   vi.resetModules();
17: });
18: 
19: function jsonResponse(json) {
20:   return { ok: true, json: vi.fn().mockResolvedValue(json) };
21: }
22: 
23: async function runAuthorization({ actor, issueTitle, responses }) {
24:   process.env.GITHUB_OUTPUT = "/tmp/github-output";
25:   process.env.APPROVAL_TOKEN = "release-bot-token";
26:   process.env.APPROVAL_ACTOR = actor;
27:   process.env.APPROVAL_ISSUE_NUMBER = ...
gateway · 2026-09-09 15:34:45
result
{"can_approve_pull_request_reviews":true,"default_workflow_permissions":"write"}
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
CI_POLLER_APP_PRIVATE_KEY
CLOUDFLARE_API_TOKEN
COCOAPODS_TRUNK_TOKEN
CRAFT_GCS_STORE_CREDS_JSON
CRAFT_GCS_TARGET_CREDS_JSON
CRATES_IO_TOKEN
DOCKER_PASSWORD
GEM_HOST_API_KEY
GPG_PASSPHRASE
GPG_PRIVATE_KEY
HEX_API_KEY
NPM_TOKEN
NUGET_API_TOKEN
OSSRH_PASSWORD
OSSRH_USERNAME
POWERSHELL_API_KEY
PUBDEV_ACCESS_TOKEN
PUBDEV_REFRESH_TOKEN
SENTRY_RELEASE_BOT_PRIVATE_KEY
TWINE_PASSWORD
VERCEL_TOKEN
gateway · 2026-09-09 15:34:58
result
{"message":"You must be an org admin or have the actions secrets fine-grained permission.","documentation_url":"https://docs.github.com/rest/actions/secrets#list-selected-repositories-for-an-organization-secret","status":"403"}gh: You must be an org admin or have the actions secrets fine-grained permission. (HTTP 403)
gh: This API operation needs the "admin:org" scope. To request it, run:  gh auth refresh -h github.com -s admin:org
gateway · 2026-09-09 15:37:00
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-09 15:37:26
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-09 15:38:47
result
# REST API endpoints for users

Use the REST API to get public and private information about authenticated users.

> [!NOTE]
> Most endpoints use `Authorization: Bearer <YOUR-TOKEN>` and `Accept: application/vnd.github+json` headers, plus `X-GitHub-Api-Version: 2026-03-10`. Curl examples below omit these standard headers for brevity.

## Get the authenticated user

```
GET /user
```

OAuth app tokens and personal access tokens (classic) need the read:user scope, or the broader user scope, for this endpoint to return the private user response. The private user response includes additional fields such as private_gists, total_private_repos, owned_private_repos, disk_usage, collaborators, and two_factor_authentication. Tokens without these scopes receive the public user response.
The private and public user response types are unrelated to the private profile setting. ...
gateway · 2026-09-09 15:41:20
result
<path>/home/byk/Code/getsentry/publish/src/publish/record-auto-approval-attestation.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const {
3:   createAutoApprovalAttestation,
4: } = require("../modules/approval-attestation.js");
5: const { getIssue } = require("./validate-approval-attestation.js");
6: 
7: async function recordAutoApprovalAttestation({
8:   autoApprover,
9:   getIssue,
10:   issueNumber,
11:   issueTitle,
12:   repository,
13: }) {
14:   const issue = await getIssue({ repository, issueNumber });
15: 
16:   if (
17:     issue.state !== "open" ||
18:     issue.title !== issueTitle ||
19:     issue.user?.login?.toLowerCase() !== autoApprover.toLowerCase()
20:   ) {
21:     throw new Error("The automated approval request changed before approval");
22:   }
23: 
24:   return createAutoApprovalAttestation({ autoApprover, issue });
25: }
26: 
27: async function main() {
28:   for (const name of [
29:     "GITHUB_OUTPUT",
30:     "APPROVAL_TOKEN",
31:     ...
gateway · 2026-09-09 15:41:30
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/record-auto-approval-attestation.js</path>
<type>file</type>
<content>
1: import { describe, expect, test, vi } from "vitest";
2: 
3: const {
4:   parseAutoApprovalAttestation,
5: } = require("../../modules/approval-attestation.js");
6: const {
7:   recordAutoApprovalAttestation,
8: } = require("../record-auto-approval-attestation.js");
9: 
10: describe("recordAutoApprovalAttestation", () => {
11:   test("binds an automated requester to the live publish request", async () => {
12:     const title = "publish: getsentry/sentry-javascript@10.0.0";
13: 
14:     await expect(
15:       recordAutoApprovalAttestation({
16:         autoApprover: "getsantry[bot]",
17:         getIssue: vi.fn().mockResolvedValue({
18:           body: "Merge target: main",
19:           labels: [],
20:           state: "open",
21:           title,
22:           user: { login: "getsantry[bot]" },
23:         }),
24:         issueNumber: "123",
25:       ...
gateway · 2026-09-09 15:41:39
result
VERCEL_ORG_ID
CI_POLLER_APP_CLIENT_ID
CI_POLLER_HAS_PENDING
gateway · 2026-09-09 15:44:22
result
<path>/home/byk/Code/getsentry/publish/src/modules/__tests__/approval-authorizer.js</path>
<type>file</type>
<content>
1: import { describe, expect, test, vi } from "vitest";
2: 
3: const { authorizeApproval } = require("../approval-authorizer.js");
4: 
5: const issueTitle = "publish: getsentry/sentry-javascript@10.0.0";
6: 
7: describe("authorizeApproval", () => {
8:   test.each(["getsantry[bot]", "sentry-release-bot[bot]"])(
9:     "rejects direct approval by %s without a target repository lookup",
10:     async (actor) => {
11:       const getPermission = vi.fn();
12: 
13:       await expect(
14:         authorizeApproval({
15:           actor,
16:           issueTitle,
17:           getPermission,
18:           autoApprovedRepositories: new Set(["getsentry/sentry-javascript"]),
19:         })
20:       ).resolves.toEqual({
21:         authorized: false,
22:         repository: "getsentry/sentry-javascript",
23:       });
24: 
25:       ...
gateway · 2026-09-09 15:44:29
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/workflow-action-pinning.js</path>
<type>file</type>
<content>
1: import { readFileSync, readdirSync } from "fs";
2: import { describe, expect, test } from "vitest";
3: 
4: const workflowPaths = readdirSync(".github/workflows")
5:   .filter((path) => path.endsWith(".yml"))
6:   .map((path) => `.github/workflows/${path}`);
7: const workflows = workflowPaths.map((path) => ({
8:   path,
9:   contents: readFileSync(path, "utf8"),
10: }));
11: const SECRET_BEARING_JOBS = {
12:   ".github/workflows/auto-approve.yml": ["auto-approve"],
13:   ".github/workflows/ci-poller.yml": ["check-ci"],
14:   ".github/workflows/cocoapods-keepalive.yml": ["keepalive"],
15:   ".github/workflows/publish.yml": ["waiting-for-ci", "publish"],
16: };
17: 
18: function jobContents(workflow, jobName) {
19:   const header = `  ${jobName}:`;
20:   const jobStart = workflow.indexOf(header);
21:   const nextJobOffset = workflow
22:     .slice(jobStart + ...
gateway · 2026-09-09 15:44:42
result
21	2	.github/workflows/auto-approve.yml
172	23	.github/workflows/ci-poller.yml
1	2	.github/workflows/cocoapods-keepalive.yml
131	8	.github/workflows/publish.yml
3	3	.github/workflows/test.yml
6	0	AGENTS.md
3	1	README.md
20	20	docs/key_rotation_vault_changes.md
5	4	docs/rfc.md
1	1	src/libs/__tests__/github.js
4	2	src/libs/github.js
21	1	src/modules/__tests__/details-from-context.js
28	0	src/modules/__tests__/update-issue.js
14	3	src/modules/details-from-context.js
2	2	src/modules/post-workflow-details.js
2	2	src/modules/process-end-state.js
23	19	src/modules/update-issue.js
8	9	src/publish/inputs.js
4	4	src/publish/post-result.js
4	4	src/publish/post-workflow-details.js
26	7	src/publish/update-issue.js
.github/workflows/ci-poller-dispatch.yml
.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__/authorize-approval.js
src/publish/__tests__/auto-approv...
gateway · 2026-09-09 15:44:55
result
diff --git a/.github/workflows/ci-poller-dispatch.yml b/.github/workflows/ci-poller-dispatch.yml
new file mode 100644
index 0000000..781bd08
--- /dev/null
+++ b/.github/workflows/ci-poller-dispatch.yml
@@ -0,0 +1,16 @@
+name: Run CI Status Poller
+
+on:
+  workflow_dispatch:
+
+permissions: {}
+
+jobs:
+  dispatch:
+    # production only permits deployments from main. Repository secrets must
+    # never be used by this workflow; protected environment secrets enforce
+    # that an arbitrary dispatched ref cannot add them.
+    environment: production
+    runs-on: ubuntu-latest
+    steps:
+      - run: ":"
diff --git a/src/modules/approval-attestation.js b/src/modules/approval-attestation.js
new file mode 100644
index 0000000..096024a
--- /dev/null
+++ b/src/modules/approval-attestation.js
@@ -0,0 +1,259 @@
+const crypto = require("crypto");
+
+const APPROVAL_ATTESTATION_PREFIX = "<!-- publish-approval ";
+const AUTO_APPROVAL_ATTESTATION_PREFIX = "<!-- publish-auto-approval ...
gateway · 2026-09-09 15:48:46
result
 .github/workflows/auto-approve.yml                 |   23 +-
 .github/workflows/ci-poller.yml                    |  245 +++-
 .github/workflows/cocoapods-keepalive.yml          |    1 -
 .github/workflows/publish.yml                      |  219 ++-
 .github/workflows/test.yml                         |    6 +-
 AGENTS.md                                          |    6 +
 README.md                                          |    9 +-
 docs/key_rotation_vault_changes.md                 |   40 +-
 docs/publish-issue-format.md                       |  107 --
 docs/rfc.md                                        |    9 +-
 package.json                                       |    5 +-
 scripts/generate-publish-issue-title-parser.js     |  104 --
 src/libs/__tests__/github.js                       |    2 +-
 src/libs/github.js                                 |    6 +-
 src/modules/__tests__/ci-poller-input.js           |   97 --
 src/modules/__tests__/ci-poller-workflow.js        |  197 ---
 ...
gateway · 2026-09-09 15:52:19
result
* 4f4b00f (origin/fix/root-location-bootstrap, fix/root-location-bootstrap) fix: bootstrap root publish locations
* 7c60ddb (origin/main, origin/HEAD) feat: accept workspace publish requests
| * fd1e115 (origin/feat/workspace-acceptance, feat/workspace-acceptance) fix: address publish workspace review feedback
| * a81ab03 feat: resolve workspace publish paths
| * 52ef600 feat: resolve compact workspace publish requests
| * b658ffa fix: validate publish workspace JSON
| * 33de3f1 feat: accept workspace publish requests
|/  
* 83d210b (HEAD -> main, opencode/tidy-orchid) build(deps): bump js-yaml from 4.3.0 to 4.3.1 (#9174)
7c60ddb feat: accept workspace publish requests
 .github/workflows/ci-poller.yml                    |   62 +-
 .github/workflows/cocoapods-keepalive.yml          |    2 +-
 .github/workflows/publish.yml                      |   84 +-
 README.md                                          |    5 +
 docs/publish-issue-format.md                       |  107 ++
 ...
gateway · 2026-09-09 15:52:53
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:   issues: write
9: 
10: jobs:
11:   auto-approve:
12:     runs-on: ubuntu-latest
13:     environment: production
14:     if: |
15:       (github.actor == 'sentry-release-bot[bot]' || github.actor == 'getsantry[bot]') &&
16:       startsWith(github.event.issue.title, 'publish: ')
17:     steps:
18:       - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
19:         with:
20:           sparse-checkout: auto-approve-repos.txt
21:           sparse-checkout-cone-mode: false
22:       - name: Get auth token
23:         id: token
24:         uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
25:         with:
26:           client-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
27:           private-key: ${{ ...
gateway · 2026-09-09 15:53:03
result
## main...origin/main [behind 1]
 M .github/workflows/auto-approve.yml
 M .github/workflows/ci-poller.yml
 M .github/workflows/cocoapods-keepalive.yml
 M .github/workflows/publish.yml
 M .github/workflows/test.yml
 M AGENTS.md
 M README.md
 M docs/key_rotation_vault_changes.md
 M docs/rfc.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
?? .github/workflows/ci-poller-dispatch.yml
?? .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__/
?? ...
gateway · 2026-09-09 15:53:21
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/authorize-approval.js</path>
<type>file</type>
<content>
1: import { afterEach, describe, expect, test, vi } from "vitest";
2: import fs from "fs";
3: const {
4:   createAutoApprovalAttestation,
5: } = require("../../modules/approval-attestation.js");
6: const {
7:   getAutoApprovedRepositories,
8:   main,
9: } = require("../authorize-approval.js");
10: 
11: const originalEnvironment = { ...process.env };
12: 
13: afterEach(() => {
14:   process.env = { ...originalEnvironment };
15:   vi.restoreAllMocks();
16:   vi.resetModules();
17: });
18: 
19: function jsonResponse(json) {
20:   return { ok: true, json: vi.fn().mockResolvedValue(json) };
21: }
22: 
23: async function runAuthorization({ actor, issueTitle, responses }) {
24:   process.env.GITHUB_OUTPUT = "/tmp/github-output";
25:   process.env.APPROVAL_TOKEN = "release-bot-token";
26:   process.env.APPROVAL_ACTOR = actor;
27:   process.env.APPROVAL_ISSUE_NUMBER = ...
gateway · 2026-09-09 15:53:34
result
diff --git a/src/modules/__tests__/approval-attestation.js b/src/modules/__tests__/approval-attestation.js
new file mode 100644
index 0000000..f720052
--- /dev/null
+++ b/src/modules/__tests__/approval-attestation.js
@@ -0,0 +1,174 @@
+import { describe, expect, test } from "vitest";
+
+const {
+  createApprovalAttestation,
+  createCiReadyAttestation,
+  currentAcceptedEvent,
+  hasApprovalAttestation,
+  hasCiReadyAttestation,
+  parseApprovalAttestation,
+  requestDigest,
+} = require("../approval-attestation.js");
+
+function issue(title, { body = "", dryRun = false } = {}) {
+  return {
+    body,
+    labels: dryRun ? [{ name: "dry-run" }] : [],
+    title,
+  };
+}
+
+describe("approval attestations", () => {
+  test("matches the latest accepted event to a trusted attestation", () => {
+    const title = "publish: getsentry/relay/py@1.2.3";
+    const publishIssue = issue(title);
+    const attestation = createApprovalAttestation({
+      actor: "contractor",
+      eventId: ...
gateway · 2026-09-09 15:53:52
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-09 15:54:20
result
diff --git a/README.md b/README.md
index 4cbc9b9..8b716c8 100644
--- a/README.md
+++ b/README.md
@@ -39,11 +39,6 @@ flowchart TD
 1. Observe the issue for information about the triggered run
 1. The issue will automatically be closed when publishing succeeds
 
-## Publish Issue Format
-
-The release workflow creates publish requests with a stable title and body contract. See
-[Publish Issue Format](docs/publish-issue-format.md) for the accepted syntax and fields.
-
 ## CalVer
 
 To enable calendar versioning, add the following to your `.craft.yml`:
@@ -99,6 +94,8 @@ Automatic approvals are managed in the [`auto-approve.yml`](https://github.com/g
 
 The system uses [Craft](https://github.com/getsentry/craft) under the hood to prepare and publish releases. It uses tokens from [Sentry Release Bot](https://github.com/apps/sentry-release-bot), which is a GitHub App that is installed on all repos in `getsentry` with read and write access to code, PRs, and actions. ...
gateway · 2026-09-09 15:54:42
result
diff --git a/src/publish/__tests__/auto-approval-workflow.js b/src/publish/__tests__/auto-approval-workflow.js
new file mode 100644
index 0000000..9009a04
--- /dev/null
+++ b/src/publish/__tests__/auto-approval-workflow.js
@@ -0,0 +1,27 @@
+import { readFileSync } from "fs";
+import { describe, expect, test } from "vitest";
+
+const workflow = readFileSync(".github/workflows/auto-approve.yml", "utf8");
+
+describe("auto-approval workflow", () => {
+  test("records a request-bound attestation before adding accepted", () => {
+    expect(workflow).toContain(
+      "node src/publish/record-auto-approval-attestation.js"
+    );
+    expect(
+      workflow.indexOf("record-auto-approval-attestation.js")
+    ).toBeLessThan(workflow.indexOf("--add-label accepted"));
+  });
+
+  test("posts the proof as github-actions before the internal app labels", () => {
+    const proof = workflow.indexOf("Post automated approval attestation");
+    const label = workflow.indexOf("--add-label ...
gateway · 2026-09-09 15:55:11
result
diff --git a/.github/workflows/auto-approve.yml b/.github/workflows/auto-approve.yml
index d9763c7..b2b489b 100644
--- a/.github/workflows/auto-approve.yml
+++ b/.github/workflows/auto-approve.yml
@@ -5,25 +5,44 @@ on:
 
 permissions:
   contents: read
+  issues: write
 
 jobs:
   auto-approve:
     runs-on: ubuntu-latest
+    environment: production
     if: |
       (github.actor == 'sentry-release-bot[bot]' || github.actor == 'getsantry[bot]') &&
       startsWith(github.event.issue.title, 'publish: ')
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
         with:
           sparse-checkout: auto-approve-repos.txt
           sparse-checkout-cone-mode: false
       - name: Get auth token
         id: token
-        uses: actions/create-github-app-token@v3
+        uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
         with:
           client-id: ${{ vars.SENTRY_INTERNAL_APP_ID ...
gateway · 2026-09-09 15:57:26
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-09 15:57:43
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:   repository_dispatch:
7:     types: [ci-poller]
8:   workflow_run:
9:     workflows: [Run CI Status Poller]
10:     types: [completed]
11: 
12: permissions:
13:   contents: read
14:   issues: write
15: 
16: jobs:
17:   check-ci:
18:     runs-on: ubuntu-latest
19:     environment: production
20:     # Skip entirely (no runner provisioned) when there's nothing to check.
21:     # Set to "true" by ci-pending.yml, reset to "false" here when done.
22:     # repository_dispatch and a successful main-branch run of the protected
23:     # workflow_dispatch relay are the trusted manual-recovery paths.
24:     if: >-
25:       vars.CI_POLLER_HAS_PENDING == 'true'
26:       || github.event_name == 'repository_dispatch'
27:       || (
28:       github.event_name == 'workflow_run'
29:       && ...
gateway · 2026-09-09 15:57:56
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-09 15:58:12
result
Found 1 matches
/home/byk/.local/share/opencode/tool-output/tool_086e3fd06001SKuFRxjAjbUQYh:
  Line 1232: ## `workflow_run`
gateway · 2026-09-09 15:58:32
result
<path>/home/byk/.local/share/opencode/tool-output/tool_086e3fd06001SKuFRxjAjbUQYh</path>
<type>file</type>
<content>
1232: ## `workflow_run`
1233: 
1234: | Webhook event payload                                                   | Activity types                                      | `GITHUB_SHA`                  | `GITHUB_REF`   |
1235: | ----------------------------------------------------------------------- | --------------------------------------------------- | ----------------------------- | -------------- |
1236: | [`workflow_run`](/en/webhooks/webhook-events-and-payloads#workflow_run) | - `completed`<br/>- `requested`<br/>- `in_progress` | Last commit on default branch | Default branch |
1237: 
1238: > \[!NOTE]
1239: >
1240: > * More than one activity type triggers this event. The `requested` activity type does not occur when a workflow is re-run. For information about each activity type, see [Webhook events and payloads](/en/webhooks/webhook-events-and-payloads#workflow_run). ...
gateway · 2026-09-09 15:58:39
result
{
  "message": "Not Found",
  "documentation_url": "https://docs.github.com/rest",
  "status": "404"
}gh: Not Found (HTTP 404)
gateway · 2026-09-09 15:58:51
result
<path>/home/byk/Code/getsentry/publish/src/publish/post-workflow-details.js</path>
<type>file</type>
<content>
1: const postWorkflowDetails = require("../modules/post-workflow-details.js");
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: 
8: postWorkflowDetails({ context, octokit });

(End of file - total 8 lines)
</content>
gateway · 2026-09-09 15:59:03
result
yarn run v1.22.22
$ vitest run --run --printConsoleTrace

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

 ✓ src/publish/__tests__/validate-approval-attestation.js (9 tests) 53ms
 ✓ src/modules/__tests__/approval-attestation.js (5 tests) 21ms
 ✓ src/publish/__tests__/current-accepted-event.js (2 tests) 38ms
 ✓ src/publish/__tests__/authorize-approval.js (7 tests) 49ms
 ✓ src/modules/__tests__/update-issue.js (6 tests) 27ms
 ✓ src/modules/__tests__/approval-authorizer.js (15 tests) 33ms
 ✓ src/modules/__tests__/process-end-state.js (4 tests) 88ms
 ✓ ...
gateway · 2026-09-09 15:59:14
result
yarn run v1.22.22
$ eslint src .github --ignore-pattern '!.github'
Done in 1.92s.
gateway · 2026-09-09 15:59:37
result
b2b489b4e6427c0dece46fd79a25de419ecdc868
17d5e91f956f3b5d8018d11d1a5f07499941b02d
781bd0850f4ea15ae89417acabdd353fc98b6bd0
9ff9a9d55b07734dcbb33e7d3f4fa69178edb39c
096024af8c999595952a6f029cdb06794be23e11
d64534399a1a23a4d5578e2fc1c7d6f98c17c0f7
69f70a434528fa2adf1333d5045ad6b473304118
baa0ef86bfc0d7fc0db974197cc7a227833eb271
5c0baa5457a54f9c444de3cd1bf1b473c065f9b7
58b43354ebad6c25f3a689f9e6672a60632c9461d537dd9229c85a03bfc44ef4  -
gateway · 2026-09-09 16:15:32
result
M	.github/workflows/auto-approve.yml
M	.github/workflows/ci-poller.yml
M	.github/workflows/cocoapods-keepalive.yml
M	.github/workflows/publish.yml
M	.github/workflows/test.yml
M	AGENTS.md
M	README.md
M	docs/key_rotation_vault_changes.md
D	docs/publish-issue-format.md
M	docs/rfc.md
M	package.json
D	scripts/generate-publish-issue-title-parser.js
M	src/libs/__tests__/github.js
M	src/libs/github.js
D	src/modules/__tests__/ci-poller-input.js
D	src/modules/__tests__/ci-poller-workflow.js
M	src/modules/__tests__/details-from-context.js
D	src/modules/__tests__/generate-publish-issue-title-parser.js
D	src/modules/__tests__/publish-location.js
D	src/modules/__tests__/publish-workflow.js
D	src/modules/__tests__/release-revision.js
M	src/modules/__tests__/update-issue.js
D	src/modules/ci-poller-input.js
M	src/modules/details-from-context.js
M	src/modules/post-workflow-details.js
M	src/modules/process-end-state.js
D	src/modules/publish-issue-title.js
D	src/modules/publish-issue-title.peggy
D	src/mo...
gateway · 2026-09-09 16:19:42
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:   hasAutoApprovalAttestation,
6: } = require("../modules/approval-attestation.js");
7: const {
8:   AUTO_APPROVAL_LABELER,
9:   AUTO_APPROVERS,
10:   authorizeApproval,
11:   isAutoApprovedRepository,
12: } = require("../modules/approval-authorizer.js");
13: 
14: function getAutoApprovedRepositories() {
15:   return new Set(
16:     fs
17:       .readFileSync("auto-approve-repos.txt", "utf8")
18:       .split(/\r?\n/)
19:       .filter(Boolean)
20:   );
21: }
22: 
23: async function getPermission({ owner, repository, username }) {
24:   const response = await getGitHubResponse(
25:     `repos/${encodeURIComponent(owner)}/${encodeURIComponent(
26:       repository
27:     )}/collaborators/${encodeURIComponent(username)}/permission`
28:   );
29: 
30:   if (!response.ok) {
31:     ...
gateway · 2026-09-09 16:28:21
result
No files found
gateway · 2026-09-09 16:29:21
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:   issues: write
9: 
10: jobs:
11:   auto-approve:
12:     runs-on: ubuntu-latest
13:     environment: production
14:     if: |
15:       (github.actor == 'sentry-release-bot[bot]' || github.actor == 'getsantry[bot]') &&
16:       startsWith(github.event.issue.title, 'publish: ')
17:     steps:
18:       - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
19:         with:
20:           sparse-checkout: auto-approve-repos.txt
21:           sparse-checkout-cone-mode: false
22:       - name: Get auth token
23:         id: token
24:         uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
25:         with:
26:           client-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
27:           private-key: ${{ ...
gateway · 2026-09-09 16:30:19
result
## main...origin/main [behind 1]
 M .github/workflows/auto-approve.yml
 M .github/workflows/ci-poller.yml
 M .github/workflows/cocoapods-keepalive.yml
 M .github/workflows/publish.yml
 M .github/workflows/test.yml
 M AGENTS.md
 M README.md
 M docs/key_rotation_vault_changes.md
 M docs/rfc.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
?? .github/workflows/ci-poller-dispatch.yml
?? .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__/
?? ...
gateway · 2026-09-09 16:30:55

I’m resuming the read-only review against the exact current worktree and current origin/main. I will not edit files.

gpt-5.6-sol · 2026-09-09 16:30:55 · ~26 tokens
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:   repository_dispatch:
7:     types: [ci-poller]
8:   workflow_run:
9:     workflows: [Run CI Status Poller]
10:     types: [completed]
11: 
12: permissions:
13:   contents: read
14:   issues: write
15: 
16: jobs:
17:   check-ci:
18:     runs-on: ubuntu-latest
19:     environment: production
20:     # Skip entirely (no runner provisioned) when there's nothing to check.
21:     # Set to "true" by ci-pending.yml, reset to "false" here when done.
22:     # repository_dispatch and a successful main-branch run of the protected
23:     # workflow_dispatch relay are the trusted manual-recovery paths.
24:     if: >-
25:       vars.CI_POLLER_HAS_PENDING == 'true'
26:       || github.event_name == 'repository_dispatch'
27:       || (
28:       github.event_name == 'workflow_run'
29:       && ...
gateway · 2026-09-09 16:32:06
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-09 16:33:30
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-09 16:36:00
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 AUTO_APPROVAL_ATTESTATION_PREFIX = "<!-- publish-auto-approval ";
5: const CI_READY_ATTESTATION_PREFIX = "<!-- publish-ci-ready ";
6: const ATTESTATION_SUFFIX = " -->";
7: 
8: function createAttestation(prefix, value) {
9:   return `${prefix}${Buffer.from(JSON.stringify(value)).toString(
10:     "base64url"
11:   )}${ATTESTATION_SUFFIX}`;
12: }
13: 
14: function requestDigest({ body, labels, title }) {
15:   if (typeof body !== "string" || !Array.isArray(labels)) {
16:     throw new Error("Invalid publish request");
17:   }
18: 
19:   return crypto
20:     .createHash("sha256")
21:     .update(
22:       JSON.stringify({
23:         body,
24:         dryRun: labels.some((label) => label.name === "dry-run"),
25:         title,
26:       })
27:     )
28:     ...
gateway · 2026-09-09 16:37:13
result
No files found
gateway · 2026-09-09 16:38:48
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:           state: "open",
23:           title,
24:         }),
25:         ...
gateway · 2026-09-09 16:39:10
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/authorize-approval.js</path>
<type>file</type>
<content>
1: import { afterEach, describe, expect, test, vi } from "vitest";
2: import fs from "fs";
3: const {
4:   createAutoApprovalAttestation,
5: } = require("../../modules/approval-attestation.js");
6: const {
7:   getAutoApprovedRepositories,
8:   main,
9: } = require("../authorize-approval.js");
10: 
11: const originalEnvironment = { ...process.env };
12: 
13: afterEach(() => {
14:   process.env = { ...originalEnvironment };
15:   vi.restoreAllMocks();
16:   vi.resetModules();
17: });
18: 
19: function jsonResponse(json) {
20:   return { ok: true, json: vi.fn().mockResolvedValue(json) };
21: }
22: 
23: async function runAuthorization({ actor, issueTitle, responses }) {
24:   process.env.GITHUB_OUTPUT = "/tmp/github-output";
25:   process.env.APPROVAL_TOKEN = "release-bot-token";
26:   process.env.APPROVAL_ACTOR = actor;
27:   process.env.APPROVAL_ISSUE_NUMBER = ...
gateway · 2026-09-09 16:40:44
result
# REST API endpoints for users

Use the REST API to get public and private information about authenticated users.

> [!NOTE]
> Most endpoints use `Authorization: Bearer <YOUR-TOKEN>` and `Accept: application/vnd.github+json` headers, plus `X-GitHub-Api-Version: 2026-03-10`. Curl examples below omit these standard headers for brevity.

## Get the authenticated user

```
GET /user
```

OAuth app tokens and personal access tokens (classic) need the read:user scope, or the broader user scope, for this endpoint to return the private user response. The private user response includes additional fields such as private_gists, total_private_repos, owned_private_repos, disk_usage, collaborators, and two_factor_authentication. Tokens without these scopes receive the public user response.
The private and public user response types are unrelated to the private profile setting. ...
gateway · 2026-09-09 16:41: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-09 16:41:46
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-09 16:44:05
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-09 16:45:50
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/ci-poller-dispatch.yml</path>
<type>file</type>
<content>
1: name: Run CI Status Poller
2: 
3: on:
4:   workflow_dispatch:
5: 
6: permissions: {}
7: 
8: jobs:
9:   dispatch:
10:     # production only permits deployments from main. Repository secrets must
11:     # never be used by this workflow; protected environment secrets enforce
12:     # that an arbitrary dispatched ref cannot add them.
13:     environment: production
14:     runs-on: ubuntu-latest
15:     steps:
16:       - run: ":"

(End of file - total 16 lines)
</content>
gateway · 2026-09-09 16:46:16
result
yarn run v1.22.22
$ vitest run --run --printConsoleTrace

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

 ✓ src/publish/__tests__/authorize-approval.js (7 tests) 36ms
 ✓ src/publish/__tests__/validate-approval-attestation.js (9 tests) 58ms
 ✓ src/modules/__tests__/approval-authorizer.js (15 tests) 15ms
 ✓ src/publish/__tests__/current-accepted-event.js (2 tests) 38ms
 ✓ src/modules/__tests__/update-issue.js (6 tests) 26ms
 ✓ src/modules/__tests__/post-workflow-details.js (1 test) 22ms
 ✓ src/modules/__tests__/process-end-state.js (4 tests) 80ms
 ✓ ...
gateway · 2026-09-09 16:46:37

Distillations (50)

Gen 0 2026-09-09 17:04:37 · 399 tokens

Date: Sep 9, 2026 * 🔴 (16:41) GitHub’s REST API endpoint for the authenticated user is `GET /user`; most endpoints use `Authorization: Bearer <YOUR-TOKEN>`, `Accept: application/vnd.github+json`, and `X-GitHub-Api-Version: 2026-03-10`. * 🔴 (16:41) OAuth app tokens and personal access tokens (classic) require the `read:user` scope or broader `user` scope for `GET /user` to return the private-use…

Gen 0 2026-09-09 16:50:31 · 3526 tokens

* 🔴 (16:33) `.github/workflows/publish.yml` is 435 lines and defines workflow `Publish`, triggered by issue `labeled` events, with concurrency group `${{ github.event.issue.title }}`, `cancel-in-progress: false`, and permissions `contents: read`, `issues: write`, and `packages: write`. * 🔴 (16:33) The `waiting-for-ci` job runs on `ubuntu-latest` in the `production` environment only when the add…

Gen 0 2026-09-09 16:37:20 · 4021 tokens

* 🔴 (16:15) Tool output listed identifiers in this exact order: `b2b489b4e6427c0dece46fd79a25de419ecdc868`, `17d5e91f956f3b5d8018d11d1a5f07499941b02d`, `781bd0850f4ea15ae89417acabdd353fc98b6bd0`, `9ff9a9d55b07734dcbb33e7d3f4fa69178edb39c`, `096024af8c999595952a6f029cdb06794be23e11`, `d64534399a1a23a4d5578e2fc1c7d6f98c17c0f7`, `69f70a434528fa2adf1333d5045ad6b473304118`, `baa0ef86bfc0d7fc0db974197…

Gen 0 2026-09-09 16:27:52 · 760 tokens

* 🔴 (15:58) GitHub Actions documentation states `workflow_run` supports activity types `completed`, `requested`, and `in_progress`; `requested` does not occur when a workflow is re-run, and the triggered workflow file must exist on the default branch. * 🔴 (15:58) GitHub Actions documentation states `workflow_run` chains are limited to 3 levels: in an `A` → `B` → `C` → `D` → `E` → `F` sequence, …

Gen 0 2026-09-09 16:23:57 · 762 tokens

* 🔴 (15:57) `.github/workflows/ci-poller.yml` lines 99–113 lists up to 200 open issues carrying both `ci-pending` and `accepted`; when none exist, it prints `No ci-pending + accepted issues found.` and exits successfully. * 🔴 (15:57) `.github/workflows/ci-poller.yml` lines 170–217 extracts a 40-character commit SHA from the issue body’s `/commit/{SHA}/checks` link, discovers the release branch …

Gen 0 2026-09-09 16:17:40 · 2683 tokens

* 🔴 (15:57) User directive: “Always allow workflow_dispatch for manual recovery.” Manual recovery is implemented through the protected `.github/workflows/ci-poller-dispatch.yml` relay rather than direct `workflow_dispatch` in `.github/workflows/ci-poller.yml`. * 🔴 (15:57) User directive: “Always run trusted code.” `.github/workflows/ci-poller.yml` checks out `${{ github.event.repository.default…

Gen 0 2026-09-09 16:13:53 · 1721 tokens

* 🔴 (15:55) New test file `src/publish/__tests__/auto-approval-workflow.js` contains 2 tests: (1) `.github/workflows/auto-approve.yml` must run `node src/publish/record-auto-approval-attestation.js` before `--add-label accepted`; (2) the “Post automated approval attestation” step must use `GH_TOKEN: ${{ github.token }}` before labeling, while the later label operation uses `GH_TOKEN: ${{ steps.t…

Gen 0 2026-09-09 16:08:50 · 2058 tokens

* 🔴 (15:52) Git history shows `fix/root-location-bootstrap` at `4f4b00f`, `origin/main` at `7c60ddb` (`feat: accept workspace publish requests`), feature branch `feat/workspace-acceptance` ending at `fd1e115`, and local `main`/`opencode/tidy-orchid` at `83d210b29553ea6f4d97508821724a834c43856e`, one commit behind `origin/main`. * 🔴 (15:52) Commit `7c60ddb7f43040fe8fbfea70efc833f689c04e75` chang…

Gen 0 2026-09-09 16:04:19 · 2947 tokens

* 🔴 (15:52) The repository change set spans 44 files with 557 insertions and 3,317 deletions. Modified files include `.github/workflows/auto-approve.yml`, `.github/workflows/ci-poller.yml`, `.github/workflows/cocoapods-keepalive.yml`, `.github/workflows/publish.yml`, `.github/workflows/test.yml`, `AGENTS.md`, `README.md`, `docs/key_rotation_vault_changes.md`, `docs/rfc.md`, `package.json`, `src/…

Gen 0 2026-09-09 15:59:34 · 2847 tokens

* 🔴 (15:48) New workflow `.github/workflows/ci-poller-dispatch.yml` is named `Run CI Status Poller`, is triggered only by `workflow_dispatch:`, declares `permissions: {}`, and has one `dispatch` job using `environment: production`, `runs-on: ubuntu-latest`, and a single no-op step `run: ":"`. * 🔴 (15:48) Repository secrets must never be used by `.github/workflows/ci-poller-dispatch.yml`; the `p…

Gen 0 2026-09-09 15:53:31 · 1755 tokens

* 🔴 (15:41) `/home/byk/Code/getsentry/publish/src/publish/record-auto-approval-attestation.js` defines `recordAutoApprovalAttestation({ autoApprover, getIssue, issueNumber, issueTitle, repository })`; it retrieves the live issue with `getIssue({ repository, issueNumber })` and rejects with `Error("The automated approval request changed before approval")` if the issue is not open, its title diffe…

Gen 0 2026-09-09 15:48:57 · 711 tokens

* 🔴 (15:41) GitHub REST API examples use `Authorization: Bearer <YOUR-TOKEN>`, `Accept: application/vnd.github+json`, and `X-GitHub-Api-Version: 2026-03-10`; the provided curl examples omit these standard headers. * 🔴 (15:41) `GET /user` retrieves the authenticated user. OAuth app tokens and personal access tokens (classic) need `read:user` or broader `user` scope for the private-user response;…

Gen 0 2026-09-09 15:47:49 · 997 tokens

* 🔴 (15:37) Attempting to list selected repositories for an organization Actions secret failed with GitHub HTTP 403: `You must be an org admin or have the actions secrets fine-grained permission.` The API operation requires the `admin:org` scope; GitHub suggested `gh auth refresh -h github.com -s admin:org`. * 🔴 (15:37) `/home/byk/Code/getsentry/publish/src/modules/update-issue.js` is 82 lines …

Gen 0 2026-09-09 15:47:13 · 1465 tokens

* 🔴 (15:34) `/home/byk/Code/getsentry/publish/src/publish/validate-approval-attestation.js` is 153 lines and imports `currentAcceptedEvent`, `currentCiReadyEvent`, `hasApprovalAttestation`, and `hasCiReadyAttestation` from `../modules/approval-attestation.js`. * 🔴 (15:34) `validate-approval-attestation.js` sends authenticated GitHub requests to `https://api.github.com/${path}` using `APPROVAL_T…

Gen 0 2026-09-09 15:46:06 · 3908 tokens

* 🔴 (15:32) In `/home/byk/Code/getsentry/publish/.github/workflows/publish.yml`, the `publish` job requires an open issue, a `ci-ready` label event, both `accepted` and `ci-ready` currently present, and neither `ci-pending` nor `ci-failed`; timeout is 90 minutes and `SENTRY_DSN` is configured. * 🔴 (15:32) User stated the poller “always adds ci-ready” after checking CI; `waiting-for-ci` first re…

Gen 0 2026-09-09 15:40:58 · 1229 tokens

* 🔴 (15:31) File `/home/byk/Code/getsentry/publish/.github/workflows/publish.yml` defines the GitHub Actions workflow `Publish`, triggered by `issues` with `types: [labeled]`. * 🔴 (15:31) `/home/byk/Code/getsentry/publish/.github/workflows/publish.yml` uses concurrency group `${{ github.event.issue.title }}` with `cancel-in-progress: false`, so duplicate issues for the same `repo@version` title…

Gen 0 2026-09-09 15:40:07 · 501 tokens

* 🔴 (15:23) GitHub Actions documentation states that the `workflow_run` activity type `requested` does not occur when a workflow is re-run; by default all supported activity types trigger the workflow, and `types` can restrict them. * 🔴 (15:23) GitHub Actions documentation states that a workflow using `workflow_run` triggers only if its workflow file exists on the default branch. * 🔴 (15:23) G…

Gen 0 2026-09-09 15:39:42 · 856 tokens

* 🔴 (15:18) A checksum command returned two SHA-256-like digests for stdin (`-`): `58b43354ebad6c25f3a689f9e6672a60632c9461d537dd9229c85a03bfc44ef4` and `8ca4aa7afcb6a1c171f695d488a06b8cf4d08436a414a3223a18b33a08db4fc6`. * 🔴 (15:18) `src/publish/record-ci-ready-attestation.js` is 109 lines and imports `createCiReadyAttestation` and `currentAcceptedEvent` from `../modules/approval-attestation.js…

Gen 0 2026-09-09 15:37:41 · 2639 tokens

* 🔴 (15:16) Repository `/home/byk/Code/getsentry/publish` was on `main...origin/main [behind 1]`, with 21 tracked files modified, 11 untracked paths, and a tracked diff totaling 499 insertions and 117 deletions. * 🔴 (15:16) Modified tracked files were `.github/workflows/auto-approve.yml`, `.github/workflows/ci-poller.yml`, `.github/workflows/cocoapods-keepalive.yml`, `.github/workflows/publish.…

Gen 0 2026-09-09 15:26:39 · 2783 tokens

* 🔴 (15:13) GitHub REST user endpoints generally use `Authorization: Bearer <YOUR-TOKEN>`, `Accept: application/vnd.github+json`, and `X-GitHub-Api-Version: 2026-03-10`; the provided curl examples omit these standard headers. * 🔴 (15:13) `GET /user` returns the authenticated user. OAuth app tokens and personal access tokens (classic) require `read:user` or broader `user` scope for the private r…

Gen 0 2026-09-09 15:17:54 · 2221 tokens

* 🔴 (15:05) `/home/byk/Code/getsentry/publish/.github/workflows/ci-poller-dispatch.yml` defines the manual `Run CI Status Poller` relay with only a `workflow_dispatch` trigger, `permissions: {}`, and one `dispatch` job in the protected `production` environment on `ubuntu-latest`; its sole step runs `":"`. * 🔴 (15:05) User directed that repository secrets must never be used by `.github/workflows…

Gen 0 2026-09-09 15:13:48 · 1876 tokens

* 🔴 (15:01) `/home/byk/Code/getsentry/publish/.github/workflows/ci-poller.yml` defines `CI Status Poller`, triggered every 5 minutes by cron `*/5 * * * *`, by `repository_dispatch` type `ci-poller`, and when workflow `Run CI Status Poller` completes. * 🔴 (15:01) The `check-ci` job runs on `ubuntu-latest` in the `production` environment with concurrency group `ci-status-poller` and `cancel-in-pr…

Gen 0 2026-09-09 15:05:24 · 3271 tokens

* 🔴 (14:42) `/home/byk/Code/getsentry/publish/package.json` defines package `publish` version `0.0.1`, private, Apache-2.0, repository `git@github.com:getsentry/publish.git`, description `Approval-based publishing system for Sentry`, author `Sentry Open Source <oss@sentry.io>`, Volta Node `24.0.0` and Yarn `1.22.22`, with resolution `undici: ^6.23.0`. * 🔴 (14:42) `/home/byk/Code/getsentry/publi…

Gen 0 2026-09-09 14:41:10 · 538 tokens

* 🔴 (14:38) Test run `yarn vitest run --printConsoleTrace` using Vitest `v4.0.17` in `/home/byk/Code/getsentry/publish` passed all `16` test files and all `84` tests: `src/modules/__tests__/approval-attestation.js` (5), `src/publish/__tests__/authorize-approval.js` (7), `src/modules/__tests__/approval-authorizer.js` (15), `src/publish/__tests__/validate-approval-attestation.js` (9), `src/modules…

Gen 0 2026-09-09 14:37:14 · 524 tokens

* 🔴 (14:34) `src/publish/__tests__/publish-workflow.js` contains 2 Vitest tests reading `.github/workflows/publish.yml`: (1) `revalidates approval immediately before Craft` asserts `Revalidate approval attestation` occurs after `Set targets`, before `Publish using Craft`, and its remaining workflow slice contains `REQUIRE_CI_READY_ATTESTATION: "true"`; (2) `pins Craft to an immutable digest` req…

Gen 0 2026-09-09 14:36:49 · 2436 tokens

* 🔴 (14:25) User’s repository was on `main...origin/main [behind 1]`; reported revisions were `83d210b29553ea6f4d97508821724a834c43856e` and `7c60ddb7f43040fe8fbfea70efc833f689c04e75`. * 🔴 (14:25) Working-tree diff covered 44 files with 557 insertions and 3317 deletions. Modified files included `.github/workflows/auto-approve.yml`, `.github/workflows/ci-poller.yml`, `.github/workflows/cocoapods…

Gen 0 2026-09-09 14:23:11 · 473 tokens

* 🔴 (14:21) SHA-256 output reported `14720c4c499a2de70a06fffa6cfa3312ab6e3c16dbdcbf19fec1f1aca34c9897` for standard input (`-`). * 🔴 (14:21) File SHA-256 values were: `.github/workflows/ci-poller-dispatch.yml` → `570dcb023ae08f4fb8338923496a55bf1c2e012a944f9969650bc31feadd485c`; `src/modules/approval-attestation.js` → `51898ad7a79e99d1a22a116a40953e5b52030a8e90c9eda5c46eb3ed3998b701`; `src/modu…

Gen 0 2026-09-09 14:21:47 · 3098 tokens

* 🔴 (14:13) `src/publish/__tests__/` contains 9 test files: `workflow-action-pinning.js`, `record-auto-approval-attestation.js`, `publish-workflow.js`, `auto-approval-workflow.js`, `ci-poller-workflow.js`, `current-accepted-event.js`, `record-ci-ready-attestation.js`, `validate-approval-attestation.js`, and `authorize-approval.js`. * 🔴 (14:13) `src/modules/details-from-context.js` defines `PUBL…

Gen 0 2026-09-09 14:19:55 · 2224 tokens

* 🔴 (13:50) User provided GitHub REST API details for `users/get-authenticated`: OAuth app tokens and classic personal access tokens need `read:user` or broader `user` scope for a private-user response; unscoped tokens receive a public-user response while still authenticating as the token owner. Responses include `200`, `304`, `401`, and `403`; the endpoint is not enabled for GitHub Apps. * 🔴 (…

Gen 0 2026-09-09 14:14:23 · 1022 tokens

* 🔴 (13:25) `.github/workflows/auto-approve.yml` defines `auto-approve non-sdks`, triggered when issues are opened; job `auto-approve` runs only when `github.actor` is `sentry-release-bot[bot]` or `getsantry[bot]` and the issue title starts with `publish: `. * 🔴 (13:25) `.github/workflows/auto-approve.yml` uses the `production` environment, `contents: read` and `issues: write` permissions, `act…

Gen 0 2026-09-09 14:01:17 · 515 tokens

Date: Sep 9, 2026 * 🔴 (13:47) A worktree was prepared at detached HEAD `7c60ddb` (`feat: accept workspace publish requests`), but patch application failed for 7 files: `.github/workflows/ci-poller.yml:92`, `.github/workflows/cocoapods-keepalive.yml:1`, `.github/workflows/publish.yml:117`, `src/modules/__tests__/update-issue.js:129`, `src/modules/details-from-context.js:15`, `src/modules/update-i…

Gen 0 2026-09-09 14:00:25 · 3344 tokens

Date: Sep 9, 2026 * 🔴 (13:30) Diff statistics for 21 tracked files were: `.github/workflows/auto-approve.yml` +21/-2; `.github/workflows/ci-poller.yml` +172/-23; `.github/workflows/cocoapods-keepalive.yml` +1/-2; `.github/workflows/publish.yml` +131/-8; `.github/workflows/test.yml` +3/-3; `AGENTS.md` +6/-0; `README.md` +3/-1; `docs/key_rotation_vault_changes.md` +20/-20; `docs/rfc.md` +5/-4; `sr…

Gen 0 2026-09-09 13:50:17 · 997 tokens

Date: Sep 9, 2026 * 🔴 (12:58) Git status for `/home/byk/Code/getsentry/publish` showed branch `main` tracking `origin/main` and behind by 1 commit. * 🔴 (12:58) Git status showed 21 modified tracked files: `.github/workflows/auto-approve.yml`, `.github/workflows/ci-poller.yml`, `.github/workflows/cocoapods-keepalive.yml`, `.github/workflows/publish.yml`, `.github/workflows/test.yml`, `AGENTS.md`…

Gen 0 2026-09-09 13:49:41 · 1107 tokens

Date: Sep 9, 2026 * 🔴 (13:22) `/home/byk/Code/getsentry/publish/src/publish/__tests__/validate-approval-attestation.js` is a 420-line Vitest suite importing `validateApprovalAttestation`, `createApprovalAttestation`, and `createCiReadyAttestation`; `afterEach()` calls `vi.restoreAllMocks()`, `jsonResponse(json)` creates an OK mocked response, and `issue(title, { body = "", labels = [{ name: "acc…

Gen 0 2026-09-09 13:42:52 · 2062 tokens

🔴 (13:19) [enforced-workflow] User directed: “Always run trusted code.” Because `workflow_dispatch` can target any ref, the CI poller workflow must check out trusted code rather than code from the trigger’s arbitrary ref.  🔴 (13:19) [enforced-workflow] User directed: “Never move a release to ci-ready after it changes.” Each polling cycle must bind itself to the current accepted-label event befo…

Gen 0 2026-09-09 13:38:13 · 760 tokens

🔴 (13:17) User directed that the `events_url` URI template’s `{/privacy}` component be replaced with `/public` to retrieve only public events; omitting it retrieves public events and, when authenticated as that user, private events.  🔴 (13:17) GitHub REST API requests generally use `Authorization: Bearer <YOUR-TOKEN>`, `Accept: application/vnd.github+json`, and `X-GitHub-Api-Version: 2026-03-10…

Gen 0 2026-09-09 13:36:32 · 530 tokens

🔴 (13:10) User directed that the `events_url` URI template’s `{/privacy}` component be replaced with `/public` to retrieve only public events; omitting it retrieves public events and, when authenticated as that user, private events.  🔴 (13:10) The `GET /user` private-user response adds `private_gists`, `total_private_repos`, `owned_private_repos`, `disk_usage`, `collaborators`, and `two_factor_…

Gen 0 2026-09-09 13:35:41 · 1728 tokens

🟡 (13:02) [requested-review] User asked to resume and complete the previously requested independent read-only review, provide substantive `file:line` evidence, make no file edits, and end the response with exactly `MERGE` or `DO-NOT-MERGE`.  🔴 (13:03) User directed that the `events_url` URI template’s `{/privacy}` component be replaced with `/public` to retrieve only public events; omitting it …

Gen 0 2026-09-09 13:28:47 · 1365 tokens

🔴 (12:56) `/home/byk/Code/getsentry/publish/src/publish/authorize-approval.js` is 241 lines. It imports `createApprovalAttestation`, `currentAcceptedEvent`, and `hasAutoApprovalAttestation` from `../modules/approval-attestation.js`, plus `AUTO_APPROVAL_LABELER`, `AUTO_APPROVERS`, `authorizeApproval`, and `isAutoApprovedRepository` from `../modules/approval-authorizer.js`.  🔴 (12:56) `getAutoApp…

Gen 0 2026-09-09 13:27:44 · 1562 tokens

🔴 (12:55) User’s manual-recovery directive is exact: “Always allow workflow_dispatch for manual recovery.” The current design preserves manual recovery through a protected `workflow_dispatch` relay whose successful default-branch `workflow_run` can start `.github/workflows/ci-poller.yml`.  🔴 (12:55) User’s trusted-execution directive is exact: “Always run trusted code.” `.github/workflows/ci-po…

Gen 0 2026-09-09 13:26:29 · 2334 tokens

🔴 (12:53) Current checked-out branch is `main`.  🔴 (12:53) `/home/byk/Code/getsentry/publish/.github/workflows/auto-approve.yml` is 54 lines. Workflow `auto-approve non-sdks` runs on opened issues with `contents: read` and `issues: write`; job `auto-approve` uses `environment: production` only when actor is `sentry-release-bot[bot]` or `getsantry[bot]` and the title starts with `publish: `.  🔴…

Gen 0 2026-09-09 13:24:52 · 2260 tokens

🔴 (10:23) `yarn prettier --write src --check .` ran under Yarn `v1.22.22`; formatting check warned on exactly 2 files, `.lore.md` and `AGENTS.md`, reported `Code style issues found in 2 files.`, and completed in `1.73s`.  🔴 (10:23) Current `/home/byk/Code/getsentry/publish/src/modules/details-from-context.js` is 75 lines and uses `PUBLISH_TITLE_REGEX = /^publish: (?:getsentry\/)?(?<repo>[^/@]+)…

Gen 0 2026-09-09 13:20:20 · 2587 tokens

🔴 (10:18) User requires `workflow_dispatch` always remain available for manual recovery.  🔴 (10:18) User requires manually dispatched workflows to always run trusted code from the repository default branch rather than code from an arbitrary dispatch ref.  🔴 (10:18) User requires that a release never move to `ci-ready` after its issue or approval changes.  🔴 (10:18) User requires a renamed or …

Gen 0 2026-09-09 13:17:39 · 479 tokens

🔴 (10:16) GitHub App permissions define which REST API resources an app may access; responses include `X-Accepted-GitHub-Permissions` to identify permissions required for an endpoint. Permissions are required for private resources, while some endpoints allow public-resource access without them.  🔴 (10:16) GitHub App permission tables use token abbreviations `UAT` = user access token and `IAT` =…

Gen 0 2026-09-09 13:14:30 · 567 tokens

🔴 (10:15) For GitHub’s `events_url` URI template, replace `{/privacy}` with `/public` to retrieve only public events; omit `{/privacy}` to retrieve public events plus private events when authenticated as that user.  🔴 (10:15) GitHub REST endpoint `GET /user` returns the authenticated user; OAuth app tokens and personal access tokens (classic) need `read:user` or broader `user` scope for the pri…

Gen 0 2026-09-09 13:13:24 · 1178 tokens

🔴 (10:13) `src/modules/approval-authorizer.js` defines `ALLOWED_ROLE_NAMES = new Set(["write", "maintain", "admin"])`, `AUTO_APPROVAL_LABELER = "sentry-internal-app[bot]"`, and `AUTO_APPROVERS = new Set(["getsantry[bot]", "sentry-release-bot[bot]"])`.  🔴 (10:13) `isAutoApprovedRepository({ actor, autoApprovedRepositories, issueTitle })` rejects actors outside `AUTO_APPROVERS`, parses the issue …

Gen 0 2026-09-09 13:12:28 · 2319 tokens

🔴 (10:11) Repository `/home/byk/Code/getsentry/publish` was on `main...origin/main [behind 1]` with 21 tracked files changed: 499 insertions and 117 deletions.  🔴 (10:11) Modified tracked files were `.github/workflows/auto-approve.yml`, `.github/workflows/ci-poller.yml`, `.github/workflows/cocoapods-keepalive.yml`, `.github/workflows/publish.yml`, `.github/workflows/test.yml`, `AGENTS.md`, `REA…

Gen 0 2026-09-09 13:06:36 · 1684 tokens

🔴 (10:10) Supplied token-creation implementation imports `pRetry` from `"p-retry"` and `isNetworkError` from `"is-network-error"`; exported `main(clientId, privateKey, enterprise, owner, repositories, permissions, core, createAppAuth, request, skipTokenRevoke)` orchestrates target resolution, GitHub App authentication, installation-token creation, output publication, and optional post-job revoca…

Gen 0 2026-09-09 13:05:34 · 1292 tokens

🔴 (10:09) User directed that when creating workflows and actions, always consider whether code might execute untrusted input from possible attackers; attacker-controlled GitHub contexts must be treated as untrusted input.  🔴 (10:09) GitHub Actions contexts documented in the supplied reference are: `github`, `env`, `vars`, `job`, `jobs` (reusable workflows only), `steps`, `runner`, `secrets`, `s…

Gen 0 2026-09-09 13:01:16 · 1892 tokens

🔴 (10:07) `/home/byk/Code/getsentry/publish/package.json` defines package `publish` version `0.0.1`, private, with Node `24.0.0` and Yarn `1.22.22` via Volta. Scripts are `test: vitest run`, `test:watch: vitest`, `lint: eslint src .github --ignore-pattern '!.github'`, and `prettier: prettier --write src`. Dependencies are `@actions/core ^2.0.0`, `@actions/github ^7.0.0`, and `@sentry/node ^10.0.…