DashboardpublishSession 15ettvO0JdWd

Session 15ettvO0JdWd

Full ID: 15ettvO0JdWdUJXF6
Messages: 29
Distillations: 1
Time range: 2026-09-14 12:11:39 — 2026-09-14 12:20:22

Conversation (29 messages)

Perform a fresh independent READ-ONLY lifecycle audit of the exact current worktree at /home/byk/Code/getsentry/publish. Do not use recall, and do not edit, format, generate, stage, or mutate files. Inspect the current files and full diff against origin/main directly. Return a NON-EMPTY report answering only these five questions with current file:line citations: 1. Do accepted, waiting-for-CI, retry, and manual-recovery paths always preserve a recoverable label state? 2. Do transient/malformed poller inputs, branch lookup failures, revision movement, CI failure, and ci-ready add failure always stop safely without stranding an authorized release? 3. Are stale ci-ready removal, final approval revalidation, proof recording, and fresh ci-ready emission ordered correctly? 4. Across publish setup failure, Craft success/failure/cancellation, do cleanup, deauthorization, target restoration, issue closure, comments, and telemetry have correct dependency and failure isolation? 5. Does updateIssue handle an absent accepted label while still surfacing target-restoration failures, and do tests genuinely exercise these paths? Classify each answer PASS, CONCERN, or MUST-FIX. Cite exact current file:line evidence and identify any missing test. If a tool blocks inspection, begin BLOCKED and quote the exact tool/error. End exactly MERGE or DO-NOT-MERGE. Never return empty output or a progress/context warning.

gateway · 2026-09-14 12:11:39

I will freeze the audit scope with read-only status and diff fingerprints, then trace each lifecycle path through the current workflows, modules, and tests.

gpt-5.6-sol · 2026-09-14 12:11:39 · ~29 tokens
result
No files found
gateway · 2026-09-14 12:12:27
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
scripts/
src/
vitest.config.js
yarn.lock

(16 entries)
</entries>
gateway · 2026-09-14 12:12:48
result
## main...origin/main
MM .github/workflows/auto-approve.yml
MM .github/workflows/ci-poller.yml
M  .github/workflows/cocoapods-keepalive.yml
MM .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__/ci-poller-input.js
 M src/modules/__tests__/ci-poller-workflow.js
MM src/modules/__tests__/details-from-context.js
 M src/modules/__tests__/process-end-state.js
 M src/modules/__tests__/publish-location.js
 M src/modules/__tests__/publish-workflow.js
 M src/modules/__tests__/release-revision.js
MM src/modules/__tests__/update-issue.js
 M src/modules/ci-poller-input.js
M  src/modules/details-from-context.js
M  src/modules/post-workflow-details.js
MM src/modules/process-end-state.js
 M src/modules/publish-location.js
 M src/modules/release-revision.js
 M src/modules/update-issue.js
 M src/publish/__tests__/discover-location.js
 M ...
gateway · 2026-09-14 12:13:13
result
7c60ddb7f43040fe8fbfea70efc833f689c04e75
7c60ddb7f43040fe8fbfea70efc833f689c04e75
gateway · 2026-09-14 12:13:32
result
 .github/workflows/auto-approve.yml                |  30 ++-
 .github/workflows/ci-poller.yml                   | 267 +++++++++++++++++----
 .github/workflows/cocoapods-keepalive.yml         |   1 -
 .github/workflows/publish.yml                     | 272 +++++++++++++++++++---
 .github/workflows/test.yml                        |   6 +-
 AGENTS.md                                         |   6 +
 README.md                                         |   4 +-
 docs/key_rotation_vault_changes.md                |  40 ++--
 docs/rfc.md                                       |   9 +-
 src/libs/__tests__/github.js                      |   2 +-
 src/libs/github.js                                |   6 +-
 src/modules/__tests__/ci-poller-input.js          |  22 +-
 src/modules/__tests__/ci-poller-workflow.js       | 252 +++++++++++++++++---
 src/modules/__tests__/details-from-context.js     |  22 +-
 src/modules/__tests__/process-end-state.js        |  27 +++
 ...
gateway · 2026-09-14 12:13:47
result
...output truncated...

Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_09fd687aa001rOeyL26oxDDIhK

+              exit 0
             fi
 
             echo "Checking CI for ${repo}@${version} commit ${sha:0:8} (issue #${number})..."
@@ -215,10 +291,117 @@ jobs:
             if [[ "$status_ok" == "true" \
                   && "$pending_checks" == "0" && "$unsuccessful_checks" == "0" ]]; then
 
+              # CI checks can take hours. Revalidate after the final check so
+              # a renamed or re-approved issue never reaches ci-ready.
+              if ! ...
gateway · 2026-09-14 12:14:02
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-14 12:14: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-14 12:14:30
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-14 12:14:46
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-14 12:14:56
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:           persist-credentials: false
21:       - name: Get auth token
22:         id: token
23:         uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
24:         with:
25:           client-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
26:           private-key: ${{ secrets.SENTRY_INTERNAL_APP_PRIVATE_KEY }}
27:       - name: Bind request snapshot
28:   ...
gateway · 2026-09-14 12:15:17
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({
8:   octokit,
9:   stateFilePath,
10:   publishRepo,
11:   issue_number,
12: }) {
13:   if (!stateFilePath || !fs.existsSync(stateFilePath)) {
14:     return;
15:   }
16: 
17:   const issueRequest = octokit.rest.issues.get({
18:     ...publishRepo,
19:     issue_number,
20:   });
21: 
22:   const craftStateRequest = fs.promises
23:     .readFile(stateFilePath, { encoding: "utf-8" })
24:     .then((data) => JSON.parse(data));
25: 
26:   const [{ data: issue }, craftState] = await Promise.all([
27:     issueRequest,
28:     craftStateRequest,
29:   ]);
30: 
31:   const newIssueBody = transformIssueBody(craftState, issue.body);
32: 
33:   await octokit.rest.issues.update({
34:     ...publishRepo,
35:     ...
gateway · 2026-09-14 12:15:49
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-14 12:15:53
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: 
8:   // Validate the status before applying any terminal state transition.
9:   sentryInfoFromDetails({ status, repo });
10: 
11:   if (status === "success") {
12:     await octokit.rest.issues.update({
13:       ...publishRepo,
14:       issue_number,
15:       state: "closed",
16:     });
17:   }
18: 
19:   const workflowInfo = (
20:     await octokit.rest.actions.getWorkflowRun({
21:       ...publishRepo,
22:       run_id,
23:     })
24:   ).data;
25: 
26:   const details = {
27:     repo,
28:     version,
29:     publishRepo,
30:     run_id,
31:     issue_number,
32:     workflowInfo,
33:     ...
gateway · 2026-09-14 12:16:12
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.js");
2: const { getGitHubToken } = require("../libs/github.js");
3: const github = require("@actions/github");
4: 
5: async function main({
6:   context = github.context,
7:   octokit = github.getOctokit(getGitHubToken()),
8:   report = processEndState,
9:   status = process.argv.slice(2)[0],
10: } = {}) {
11:   let inputs = {};
12: 
13:   if (process.env.PUBLISH_ARGS) {
14:     try {
15:       inputs = JSON.parse(process.env.PUBLISH_ARGS);
16:     } catch {
17:       console.warn("Could not parse publish inputs; reporting without them");
18:     }
19:   }
20: 
21:   await report({ context, octokit, inputs, status });
22: }
23: 
24: if (require.main === module) {
25:   main().catch((error) => {
26:     console.error(error);
27:     process.exitCode = 1;
28:   });
29: }
30: 
31: module.exports = { main };

(End of file - ...
gateway · 2026-09-14 12:16:29
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("binds approval and publication to the triggering issue snapshot", () => {
8:     expect(workflow).toContain("node src/publish/request-digest-from-event.js");
9:     expect(
10:       workflow.match(
11:         /EXPECTED_REQUEST_DIGEST: \$\{\{ steps\.request-digest\.outputs\.request_digest \}\}/g
12:       )
13:     ).toHaveLength(4);
14:   });
15: 
16:   test("revalidates approval immediately before Craft", () => {
17:     const fence = workflow.indexOf("Revalidate approval attestation");
18: 
19:     expect(fence).toBeGreaterThan(workflow.indexOf("Set targets"));
20:     expect(fence).toBeLessThan(workflow.indexOf("Publish using Craft"));
21:     ...
gateway · 2026-09-14 12:16:37
result
<path>/home/byk/Code/getsentry/publish/src/modules/__tests__/publish-workflow.js</path>
<type>file</type>
<content>
1: import { spawnSync } from "child_process";
2: import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
3: import { tmpdir } from "os";
4: import { join } from "path";
5: 
6: import { afterEach, expect, test } from "vitest";
7: 
8: const { resolvePublishLocation } = require("../publish-location.js");
9: 
10: const temporaryDirectories = [];
11: 
12: function getWorkflow() {
13:   return readFileSync(
14:     join(__dirname, "../../../.github/workflows/publish.yml"),
15:     "utf8"
16:   );
17: }
18: 
19: function getSetTargetsScript() {
20:   const workflow = getWorkflow();
21:   const section = workflow.match(
22:     / {6}- name: Set targets[\s\S]*? {8}run: \|\n(?<script>[\s\S]*?)\n {6}- name: Revalidate approval attestation/
23:   );
24:   if (!section?.groups?.script) {
25:     throw new Error("Missing Set targets workflow script");
26:   }
27:   ...
gateway · 2026-09-14 12:16:43
result
<path>/home/byk/Code/getsentry/publish/src/modules/__tests__/ci-poller-workflow.js</path>
<type>file</type>
<content>
1: import {
2:   mkdtempSync,
3:   mkdirSync,
4:   readFileSync,
5:   readdirSync,
6:   rmSync,
7:   writeFileSync,
8: } from "fs";
9: import { tmpdir } from "os";
10: import { join } from "path";
11: import { spawnSync } from "child_process";
12: 
13: import { afterEach, expect, test } from "vitest";
14: 
15: const temporaryDirectories = [];
16: const INITIAL_REVISION = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
17: const UPDATED_REVISION = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
18: 
19: function getPollerScript() {
20:   const workflow = readFileSync(
21:     join(__dirname, "../../../.github/workflows/ci-poller.yml"),
22:     "utf8"
23:   );
24:   const section = workflow.match(
25:     / {6}- name: Check CI status for ci-pending issues[\s\S]*? {8}run: \|\n(?<script>[\s\S]*?)\n {6}- name: Check for remaining pending issues/
26:   );
27: 
28:   if ...
gateway · 2026-09-14 12:17:06
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 (
16:     typeof body !== "string" ||
17:     !Array.isArray(labels) ||
18:     typeof title !== "string"
19:   ) {
20:     throw new Error("Invalid publish request");
21:   }
22: 
23:   return crypto
24:     .createHash("sha256")
25:     .update(
26:       JSON.stringify({
27:         body,
28:         dryRun: labels.some((label) => label.name === ...
gateway · 2026-09-14 12:17:32
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:   requestDigest,
6: } = require("../modules/approval-attestation.js");
7: const {
8:   getAllPages,
9:   getGitHubResponse,
10:   getIssue,
11: } = require("./validate-approval-attestation.js");
12: 
13: async function getAuthenticatedLogin() {
14:   const response = await getGitHubResponse("user");
15: 
16:   if (!response.ok) {
17:     throw new Error(
18:       `Could not retrieve the authenticated user: GitHub returned ${response.status}`
19:     );
20:   }
21: 
22:   const { login } = await response.json();
23: 
24:   if (typeof login !== "string") {
25:     throw new Error("GitHub returned no authenticated user login");
26:   }
27: 
28:   return login;
29: }
30: 
31: async function recordCiReadyAttestation({
32:   expectedAcceptedEvent,
33:   ...
gateway · 2026-09-14 12:17:42
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:   requestDigest,
7: } = require("../modules/approval-attestation.js");
8: 
9: async function getGitHubResponse(path) {
10:   return fetch(`https://api.github.com/${path}`, {
11:     headers: {
12:       Accept: "application/vnd.github+json",
13:       Authorization: `Bearer ${process.env.APPROVAL_TOKEN}`,
14:       "X-GitHub-Api-Version": "2026-03-10",
15:     },
16:   });
17: }
18: 
19: async function getIssue({ repository, issueNumber }) {
20:   const response = await getGitHubResponse(
21:     `repos/${repository}/issues/${issueNumber}`
22:   );
23: 
24:   if (!response.ok) {
25:     throw new Error(
26:       `Could not retrieve issue #${issueNumber}: GitHub returned ${response.status}`
27:     );
28:   }
29: 
30:   return ...
gateway · 2026-09-14 12:18:22
result
<path>/home/byk/Code/getsentry/publish/src/modules/__tests__/process-end-state.js</path>
<type>file</type>
<content>
1: import { vi, describe, test, expect } from "vitest";
2: 
3: vi.mock("fs");
4: 
5: const processEndState = require("../process-end-state.js");
6: 
7: describe("publish failed", () => {
8:   const failureArgs = {
9:     status: "failure",
10:     inputs: { repo: "sentry", version: "21.3.1" },
11:     context: {
12:       runId: "1234",
13:       repo: { owner: "getsentry", repo: "publish" },
14:       payload: { issue: { number: "211" } },
15:     },
16:     octokit: {
17:       rest: {
18:         actions: {
19:           getWorkflowRun: async () => ({
20:             data: {
21:               html_url: "https://github.com/getsentry/sentry/actions/runs/1234",
22:             },
23:           }),
24:         },
25:         issues: {
26:           createComment: vi.fn(),
27:         },
28:       },
29:     },
30:     Sentry: {
31:       Scope: class Scope {
32:         ...
gateway · 2026-09-14 12:18:41
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-14 12:19:00
result
Found 15 matches
/home/byk/Code/getsentry/publish/src/modules/update-issue.js:
  Line 76:     octokit.rest.issues.removeLabel({

  Line 83:   if (labelResult.status === "rejected" && labelResult.reason?.status !== 404) {


/home/byk/Code/getsentry/publish/src/modules/__tests__/update-issue.js:
  Line 27:         removeLabel: vi.fn(),

  Line 123:     const removeLabel = updateTargetsArgs.octokit.rest.issues.removeLabel;

  Line 124:     expect(removeLabel).toHaveBeenCalledTimes(1);

  Line 125:     expect(removeLabel).toHaveBeenCalledWith({

  Line 150:         removeLabel: vi.fn(),

  Line 164:   expect(octokit.rest.issues.removeLabel).toHaveBeenCalledWith({

  Line 172: test("restores targets when accepted was already removed", async () => {

  Line 182:         removeLabel: vi

  Line 185:             Object.assign(new Error("Not Found"), { status: 404 })


/home/byk/Code/getsentry/publish/src/publish/__tests__/authorize-approval.js:
  Line 266:     global.fetch = ...
gateway · 2026-09-14 12:19:12
result
<path>/home/byk/.local/share/opencode/tool-output/tool_09fd687aa001rOeyL26oxDDIhK</path>
<type>file</type>
<content>
1: diff --git a/.github/workflows/auto-approve.yml b/.github/workflows/auto-approve.yml
2: index d9763c7..5f86245 100644
3: --- a/.github/workflows/auto-approve.yml
4: +++ b/.github/workflows/auto-approve.yml
5: @@ -5,25 +5,47 @@ on:
6:  
7:  permissions:
8:    contents: read
9: +  issues: write
10:  
11:  jobs:
12:    auto-approve:
13:      runs-on: ubuntu-latest
14: +    environment: production
15:      if: |
16:        (github.actor == 'sentry-release-bot[bot]' || github.actor == 'getsantry[bot]') &&
17:        startsWith(github.event.issue.title, 'publish: ')
18:      steps:
19: -      - uses: actions/checkout@v4
20: +      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
21:          with:
22: -          sparse-checkout: auto-approve-repos.txt
23: -          sparse-checkout-cone-mode: false
24: +          persist-credentials: false
25:        - ...
gateway · 2026-09-14 12:19:26
result
<path>/home/byk/.local/share/opencode/tool-output/tool_09fd687aa001rOeyL26oxDDIhK</path>
<type>file</type>
<content>
1058:    "rejects unsafe repository identity before CI API calls: %s",
1059: -  repo => {
1060: +  (repo) => {
1061:      expect(() =>
1062:        getCiPollerInput({
1063:          issueBody,
1064: @@ -66,7 +74,7 @@ test.each(["-toolkit", ".", "..", "__proto__"])(
1065:  
1066:  test.each(["--config", "1.2"])(
1067:    "rejects invalid release version before CI API calls: %s",
1068: -  version => {
1069: +  (version) => {
1070:      expect(() =>
1071:        getCiPollerInput({
1072:          issueBody,
1073: @@ -78,7 +86,7 @@ test.each(["--config", "1.2"])(
1074:  
1075:  test.each(["publish: getsentry/@1.2.3", "publish: getsentry/toolkit$@1.2.3"])(
1076:    "rejects malformed repository identity before CI API calls: %s",
1077: -  title => {
1078: +  (title) => {
1079:      expect(() => getCiPollerInput({ issueBody, title })).toThrow(
1080:        "Invalid publish ...
gateway · 2026-09-14 12:19:57
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-14 12:20:22

Distillations (1)

Gen 0 2026-09-14 12:18:39 · 1826 tokens

Date: Sep 14, 2026 * 🔴 [requested-audit] (12:11) User requested a fresh, independent, READ-ONLY lifecycle audit of the exact current worktree at `/home/byk/Code/getsentry/publish`, based on direct inspection of current files and the full diff against `origin/main`; user prohibited use of recall and prohibited editing, formatting, generating, staging, or otherwise mutating files. * 🔴 (12:11) Use…