DashboardcraftSession 0Kjs9yW3R3Q7

Session 0Kjs9yW3R3Q7

Full ID: 0Kjs9yW3R3Q78ax2O
Messages: 35
Distillations: 6
Time range: 2026-08-25 14:33:27 — 2026-08-25 15:19:40

Conversation (35 messages)

Perform a read-only adversarial correctness/security review of the uncommitted changes in /home/byk/Code/getsentry/publish-workspace-acceptance on feat/workspace-acceptance, compared with origin/main. Do NOT modify files. Scope .github/workflows/publish.yml, src/modules/details-from-context.js and its tests. Verify exact backward compatibility for legacy issue grammar/output/state files; workspace grammar matches Craft action publish: repo/path [workspace: <JSON string>] @version; parsing fail closed including malformed JSON and dangerous strings; shell/YAML injection; Base64URL state file byte-level match with Craft getPublishStateFilename; CI ready/accepted gating preserved; issue concurrency identity; dry run; target state resume isolation. Inspect relevant source/tests. Report findings by severity with file:line evidence, PASS/FAIL per concern, test gaps, clear MERGE/DO-NOT-MERGE. No file changes.

gateway · 2026-08-25 14:33:27
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance</path>
<type>directory</type>
<entries>
.eslintrc.js
.git
.github/
.gitignore
AGENTS.md
auto-approve-repos.txt
docs/
LICENSE
node_modules/
package.json
README.md
src/
vitest.config.js
yarn.lock

(14 entries)
</entries>
gateway · 2026-08-25 14:34:20
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/AGENTS.md</path>
<type>file</type>
<content>
1: # AGENTS.md - Coding Agent Guidelines
2: 
3: This document provides guidelines for AI coding agents working in this repository.
4: 
5: ## Project Overview
6: 
7: This is the **Sentry Publish Repository** - a GitHub Actions-based approval system for publishing releases across multiple Sentry projects using [Craft](https://github.com/getsentry/craft). It's a pure JavaScript (Node.js 24.0.0) project using CommonJS modules.
8: 
9: ## Build/Lint/Test Commands
10: 
11: ### Package Manager
12: 
13: - **Yarn 1.22.22** (classic) - Do NOT use npm
14: - Version management via Volta (pinned in package.json)
15: 
16: ### Commands
17: 
18: ```bash
19: # Install dependencies
20: yarn install
21: 
22: # Run all tests
23: yarn test
24: 
25: # Run tests in watch mode
26: yarn test:watch
27: 
28: # Run a single test file
29: yarn test src/modules/__tests__/update-issue.js
30: 
31: # Run tests ...
gateway · 2026-08-25 14:35:51
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/.git</path>
<type>file</type>
<content>
1: gitdir: /home/byk/Code/getsentry/publish/.git/worktrees/publish-workspace-acceptance

(End of file - total 1 lines)
</content>
gateway · 2026-08-25 14:36:05
result
 M .github/workflows/publish.yml
 M src/modules/__tests__/details-from-context.js
 M src/modules/details-from-context.js
feat/workspace-acceptance
83d210b29553ea6f4d97508821724a834c43856e
 .github/workflows/publish.yml                 |  9 ++-
 src/modules/__tests__/details-from-context.js | 97 ++++++++++++++++++++++++++-
 src/modules/details-from-context.js           | 24 +++++--
 3 files changed, 123 insertions(+), 7 deletions(-)
M	.github/workflows/publish.yml
M	src/modules/__tests__/details-from-context.js
M	src/modules/details-from-context.js
gateway · 2026-08-25 14:37:35
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/.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 ...
gateway · 2026-08-25 14:38:22
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/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: 
19: async function detailsFromContext({ context }) {
20:   if (!context || ...
gateway · 2026-08-25 14:39:29
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/__tests__/details-from-context.js</path>
<type>file</type>
<content>
1: import { test, expect } from "vitest";
2: 
3: const { detailsFromContext } = require("../details-from-context.js");
4: 
5: const inputsArgs = {
6:   context: {
7:     repo: { owner: "getsentry", repo: "publish" },
8:     payload: {
9:       issue: {
10:         number: "223",
11:         title: "publish: getsentry/sentry@21.3.1",
12:         body: `
13: Requested by: @BYK
14: 
15: Merge target: custom-branch
16: 
17: Quick links:
18: - [View changes](https://github.com/getsentry/sentry/compare/21.3.0...refs/heads/releases/21.3.1)
19: - [View check runs](https://github.com/getsentry/sentry/commit/7e5ca7ed5581552de066e2a8bc295b8306be38ac/checks/)
20: 
21: Assign the **accepted** label to this issue to approve the release. ...
gateway · 2026-08-25 14:40:30
result
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index cff6bc3..37519e9 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -113,200 +113,207 @@ jobs:
       && !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: Install yarn dependencies
         run: yarn install --cwd ".__publish__"
 
       - name: Parse and set inputs
         id: inputs
         run: node .__publish__/src/publish/inputs.js
 
       - name: ...
gateway · 2026-08-25 14:41:36
result
<path>/home/byk/Code/getsentry/craft/action.yml</path>
<type>file</type>
<content>
1: name: 'Craft Prepare Release'
2: description: 'Prepare a new release using Craft'
3: 
4: inputs:
5:   version:
6:     description: >
7:       Version to release. Can be a semver string (e.g., "1.2.3"),
8:       a bump type ("major", "minor", "patch"), or "auto" for automatic detection.
9:     required: false
10:   merge_target:
11:     description: Target branch to merge into. Uses the default branch as a fallback. ...
gateway · 2026-08-25 14:43:21
result
No files found
gateway · 2026-08-25 14:47:01
result
Found 13 matches
/home/byk/Code/getsentry/craft/src/utils/publishState.ts:
  Line 78: export function getPublishStateFilename(

  Line 108:     getPublishStateFilename(version, githubConfig, cwd),


/home/byk/Code/getsentry/craft/src/utils/__tests__/publishState.test.ts:
  Line 7:   getPublishStateFilename,

  Line 42:   describe('getPublishStateFilename', () => {

  Line 46:       const name = getPublishStateFilename(

  Line 57:       const a = getPublishStateFilename(

  Line 62:       const b = getPublishStateFilename(

  Line 71:       const name = getPublishStateFilename(

  Line 82:       const name = getPublishStateFilename('1.2.3', null, cwd);

  Line 87:       const a = getPublishStateFilename('1.0.0', null, '/a');

  Line 88:       const b = getPublishStateFilename('1.0.0', null, '/b');

  Line 93:       const a = getPublishStateFilename(

  Line 98:       const b = getPublishStateFilename(
gateway · 2026-08-25 14:47:55
result
<path>/home/byk/Code/getsentry/craft/src/utils/publishState.ts</path>
<type>file</type>
<content>
1: /**
2:  * Helpers for locating Craft's publish-state file in a path that is NOT
3:  * writable by the repository being published.
4:  *
5:  * Background: `craft publish` writes a small JSON file listing targets
6:  * that have completed so a resumed run can skip them. Before this module
7:  * existed, the file lived at `.craft-publish-<version>.json` in the
8:  * project's cwd. That path is inside the repository checkout, so any
9:  * committed file at the same path (or any earlier CI step) could
10:  * pre-populate the "published" set and trick Craft into silently
11:  * skipping targets.
12:  *
13:  * The file now lives under `$XDG_STATE_HOME/craft/` (falling back to
14:  * `$HOME/.local/state/craft/`). The filename is keyed on
15:  * owner, repo, a hash of cwd (to disambiguate monorepo subpaths), and
16:  * the version being published. ...
gateway · 2026-08-25 14:52:15
result
<path>/home/byk/Code/getsentry</path>
<type>directory</type>
<entries>
cli-api-1423/
cli-wt/
cli/
codecov-action/
craft-vercel-prebuilt-output/
craft-vercel-project-config/
craft-workspace-action-propagation/
craft-workspaces-schema/
craft/
devinfra-coder-infra/
devinfra-deployment-service/
getsentry/
ops/
package_grant_publish_repo/
publish-workspace-acceptance/
publish/
security-as-code/
self-hosted/
sentry-api-schema/
sentry-docs/
sentry-forge/
sentry-go/
sentry-javascript/
sentry-orbital/
sentry-python/
sentry-release-registry/
sentry-wizard/
sentry/
snuba/
spotlight/
sqlish/
static-sites/
stripe-app/
symbolic/

(34 entries)
</entries>
gateway · 2026-08-25 14:53:10
result
Found 11 matches
/home/byk/Code/getsentry/craft-workspace-action-propagation/action.yml:
  Line 34:   workspace:

  Line 35:     description: Named Craft release workspace to prepare and publish

  Line 168:         WORKSPACE: ${{ inputs.workspace }}

  Line 185:           CRAFT_ARGS+=(--workspace "$WORKSPACE")

  Line 202:         WORKSPACE: ${{ inputs.workspace }}

  Line 206:           CRAFT_ARGS=(--workspace "$WORKSPACE")

  Line 231:         WORKSPACE: ${{ inputs.workspace }}

  Line 258:         workspace_title=""

  Line 261:           workspace_json=$(jq -Rn --arg workspace "$WORKSPACE" '$workspace')

  Line 262:           workspace_title=" [workspace: ${workspace_json}]"

  Line 265:         title="publish: ${GITHUB_REPOSITORY}${SUBDIRECTORY}${workspace_title}${version_separator}${RESOLVED_VERSION}"
gateway · 2026-08-25 14:54:06
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/action.yml</path>
<type>file</type>
<content>
20:     default: 'release-blocker'
21:   publish_repo:
22:     description: Repository for publish issues (owner/repo format)
23:     required: false
24:   git_user_name:
25:     description: Git committer name
26:     required: false
27:   git_user_email:
28:     description: Git committer email
29:     required: false
30:   path:
31:     description: The path that Craft will run inside
32:     required: false
33:     default: '.'
34:   workspace:
35:     description: Named Craft release workspace to prepare and publish
36:     required: false
37:   craft_config_from_merge_target:
38:     description: Use the craft config from the merge target branch
39:     required: false
40:     default: 'false'
41:   craft_version:
42:     description: >
43:       Version of Craft to install (tag or "latest").
44:       Defaults to the action ref (e.g., "v2") if not specified. ...
gateway · 2026-08-25 14:55:05
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/utils/publishState.ts</path>
<type>file</type>
<content>
1: /**
2:  * Helpers for locating Craft's publish-state file in a path that is NOT
3:  * writable by the repository being published.
4:  *
5:  * Background: `craft publish` writes a small JSON file listing targets
6:  * that have completed so a resumed run can skip them. Before this module
7:  * existed, the file lived at `.craft-publish-<version>.json` in the
8:  * project's cwd. That path is inside the repository checkout, so any
9:  * committed file at the same path (or any earlier CI step) could
10:  * pre-populate the "published" set and trick Craft into silently
11:  * skipping targets.
12:  *
13:  * The file now lives under `$XDG_STATE_HOME/craft/` (falling back to
14:  * `$HOME/.local/state/craft/`). The filename is keyed on
15:  * owner, repo, a hash of cwd (to disambiguate monorepo subpaths), and
16:  * the version being published. ...
gateway · 2026-08-25 14:56:41
result
Found 100 matches (more matches available)
/home/byk/Code/getsentry/craft-workspace-action-propagation/src/utils/workspaces.ts:
  Line 15: /** Information about a workspace package */

  Line 25:   /** Dependencies that are also workspace packages */

  Line 26:   workspaceDependencies: string[];

  Line 29: /** Result of workspace discovery */

  Line 31:   /** The type of workspace manager detected */

  Line 37: /** Structure of pnpm-workspace.yaml */

  Line 45:   workspaces?: string[] | { packages?: string[] };

  Line 93:  * Extract workspaces array from package.json workspaces field

  Line 97:   workspaces: string[] | { packages?: string[] } | undefined,

  Line 99:   if (!workspaces) {

  Line 102:   if (Array.isArray(workspaces)) {

  Line 103:     return workspaces;

  Line 105:   return workspaces.packages || [];

  Line 115:   // First: collect all workspace package names and locations

  Line 116:   const workspaceLocations: Array<{

  Line 120:   const workspaceNames = ...
gateway · 2026-08-25 14:56:56
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/utils/helpers.ts</path>
<type>file</type>
<content>
1: import { appendFileSync, mkdirSync, writeFileSync } from 'fs';
2: import path from 'path';
3: import { parseArgs } from 'node:util';
4: 
5: import prompts from 'prompts';
6: import { logger, LogLevel, setLevel } from '../logger';
7: 
8: /**
9:  * Maximum size (in bytes) for step output values passed through GITHUB_OUTPUT.
10:  * Values exceeding this are truncated to avoid E2BIG errors when GitHub Actions
11:  * expands them into environment variables for subsequent steps.
12:  *
13:  * 64 KB is well under the ~2 MB ARG_MAX kernel limit and also under GitHub's
14:  * ~65 536-character issue body limit, so truncated changelogs still render. ...
gateway · 2026-08-25 14:58:05
result
Found 2 matches
/home/byk/Code/getsentry/craft-workspace-action-propagation/src/config.ts:
  Line 110: function resolveWorkspaceConfig(

  Line 239:   return resolveWorkspaceConfig(config, _activeWorkspaceName);
gateway · 2026-08-25 14:58:58
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/config.ts</path>
<type>file</type>
<content>
80:  * Must be called before the configuration is first resolved/cached; it clears
81:  * the caches so a later read re-resolves against the new selection.
82:  */
83: export function setActiveWorkspace(name: string | undefined): void {
84:   _activeWorkspaceName = name;
85:   // Invalidate resolved caches so the next read applies the new selection.
86:   _configCache = undefined as unknown as CraftProjectConfig;
87:   _globalGitHubConfigCache = undefined;
88: }
89: 
90: /**
91:  * Returns the name of the currently-selected workspace, if any.
92:  */
93: export function getActiveWorkspace(): string | undefined {
94:   return _activeWorkspaceName;
95: }
96: 
97: /**
98:  * Merges a workspace's overrides onto the top-level (base) config, producing a
99:  * flat `CraftProjectConfig` that the rest of craft consumes unchanged. ...
gateway · 2026-08-25 15:00:44
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/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: 
13: inputs();

(End of file - total 13 lines)
</content>
gateway · 2026-08-25 15:02:27
result
Found 12 matches
/home/byk/Code/getsentry/craft-workspace-action-propagation/src/__tests__/versionBump.test.ts:
  Line 218:   describe('workspace:* handling (issue #804)', () => {

  Line 223:      *   /packages/app/package.json           (@scope/app, 0.0.1, deps: core @ workspace:*)

  Line 258:           dependencies: { '@scope/core': 'workspace:*' },

  Line 296:                 dependencies: { '@scope/core': 'workspace:*' },

  Line 300:               'npm error code EUNSUPPORTEDPROTOCOL\nnpm error Unsupported URL Type "workspace:": workspace:*',

  Line 390:             throw new Error('EUNSUPPORTEDPROTOCOL workspace:*');

  Line 446:         "@scope/core": "workspace:*",

  Line 451:     "@scope/core": ["@scope/core@0.0.1", "workspace:packages/core"],

  Line 486:       // Nested dependency version pins must stay as "workspace:*".

  Line 487:       expect(patched).toContain('"@scope/core": "workspace:*"');

  Line 713:             throw new Error('EUNSUPPORTEDPROTOCOL ...
gateway · 2026-08-25 15:02:40
result
No files found
gateway · 2026-08-25 15:03:40
result
Found 14 matches
/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/inputs.js:
  Line 3: const { detailsFromContext } = require('../modules/details-from-context');

  Line 6:     const result = await detailsFromContext({


/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/details-from-context.js:
  Line 19: async function detailsFromContext({ context }) {

  Line 76:   detailsFromContext,


/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/__tests__/details-from-context.js:
  Line 3: const { detailsFromContext } = require("../details-from-context.js");

  Line 38:   const result = await detailsFromContext(inputsArgs);

  Line 50:   const result = await detailsFromContext({

  Line 93:   const result = await detailsFromContext(defaultTargetInputsArgs);

  Line 105:   const result = await detailsFromContext({

  Line 127:   const result = await detailsFromContext({

  Line 147:     detailsFromContext({

  Line 164:     ...
gateway · 2026-08-25 15:04:37
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/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: const context = github.context;
6: const octokit = github.getOctokit(getGitHubToken());
7: const inputs = JSON.parse(process.env.PUBLISH_ARGS);
8: 
9: updateIssue({ context, octokit, inputs });

(End of file - total 9 lines)
</content>
gateway · 2026-08-25 15:05:44
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/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 = ...
gateway · 2026-08-25 15:07:37
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/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:    ...
gateway · 2026-08-25 15:09:17
result
yarn run v1.22.22
$ vitest run src/modules/__tests__/details-from-context.js
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
/bin/sh: 1: vitest: not found
error Command failed with exit code 127.
gateway · 2026-08-25 15:11:15
result
[eval]:1
const assert = require("node:assert/strict"); const { detailsFromContext } = require("./src/modules/details-from-context.js"); const input = title => ({context:{payload:{issue:{title,body:"",labels:[]}}}}); (async () => { assert.equal((await detailsFromContext(input("publish: getsentry/toolkit/packages/cli [workspace: \"cli [preview] \\\\"next\\\\\"\"] @1.2.3"))).workspace, "cli [preview] \\\"next\\\""); await assert.rejects(() => detailsFromContext(input("publish: getsentry/toolkit [workspace: \"invalid\\q\"] @1.2.3"))); const nul = await detailsFromContext(input("publish: getsentry/toolkit [workspace: \"\\u0000\"] @1.2.3")); assert.equal(nul.workspace.charCodeAt(0), 0); })().then(() => process.stdout.write("parser checks completed\n"));
                                                                                                                                                                                                                                                  ...
gateway · 2026-08-25 15:12:26
result
parser checks completed
gateway · 2026-08-25 15:14:31
result
Base64URL parity checks completed
gateway · 2026-08-25 15:16:27
result
Found 16 matches
/home/byk/Code/getsentry/craft-workspace-action-propagation/src/commands/publish.ts:
  Line 51: import { getPublishStatePath } from '../utils/publishState';

  Line 164:   published: {

  Line 189:  * @param revision Git commit SHA of the commit to be published

  Line 693:     // Fall through with null — getPublishStatePath() handles this by

  Line 697:   const publishStateFile = getPublishStatePath(

  Line 710:   // published" set. Users / workflows that were writing to the legacy

  Line 718:         `If you were pre-seeding published targets, update your workflow to write to the new location.`,

  Line 729:     publishState = { published: Object.create(null) };

  Line 732:   for (const published of Object.keys(publishState.published)) {

  Line 734:       `Skipping target ${published} as it is marked as successful in state file.`,

  Line 736:     targetsToPublish.delete(published);

  Line 773:         publishState.published[BaseTarget.getId(target.config)] = ...
gateway · 2026-08-25 15:17:26
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/commands/publish.ts</path>
<type>file</type>
<content>
660:   await checkRevisionStatus(statusProvider, revision, argv.noStatusCheck);
661: 
662:   await printRevisionSummary(artifactProvider, revision);
663: 
664:   await checkRequiredArtifacts(artifactProvider, revision, config.requireNames);
665: 
666:   // Find targets
667:   let targetsToPublish: Set<string> = new Set(
668:     (typeof argv.target === 'string' ? [argv.target] : argv.target) || [
669:       SpecialTarget.All,
670:     ],
671:   );
672: 
673:   // Treat "all"/"none" specially
674:   for (const specialTarget of [SpecialTarget.All, SpecialTarget.None]) {
675:     if (targetsToPublish.size > 1 && targetsToPublish.has(specialTarget)) {
676:       logger.error(
677:         `Target "${specialTarget}" specified together with other targets. Exiting.`,
678:       );
679:       return undefined;
680:     }
681:   }
682: 
683:   // Expand any npm workspace ...
gateway · 2026-08-25 15:19:39

No files changed.

Findings

High — target-resume state remains insecure and nonfunctional (pre-existing, still blocks this requirement)

publish.yml writes Craft’s state to the secure XDG location (.github/workflows/publish.yml:199-233), but failure handling calls update-issue.js (:293-298), which reads the attacker-controlled legacy file inside the target checkout:

  • src/modules/update-issue.js:8-11 reads __repo__/.craft-publish-<version>.json.
  • Craft explicitly never reads that legacy file because repository contents can pre-populate target state (craft .../publish.ts:707-718).
  • Craft writes and resumes from the XDG filename (publish.ts:697-702; publishState.ts:89-95).
  • The existing test codifies the obsolete path (src/modules/__tests__/update-issue.js:73-81).

On a failed run, a legacy file can mark issue checkboxes as completed despite not reflecting Craft’s state. The next retry seeds Craft’s secure state from those checkboxes, so targets can be skipped. It also means ordinary failed runs do not persist actual completed targets for retry.

FAIL — target state resume isolation.

Medium — state filename does not always byte-match Craft’s filename

The title parser allows non-canonical paths including . and .. (src/modules/details-from-context.js:25). The workflow hashes the unnormalised constructed path (.github/workflows/publish.yml:213-220), while Craft hashes process.cwd() (craft .../publish.ts:697-701; publishState.ts:63-64), which Node canonicalises after the workflow’s cd (publish.yml:244-245).

Example: a title with /packages/../cli produces a hash for:

/github/workspace/__repo__/packages/../cli

but Craft uses:

/github/workspace/__repo__/cli

The seeded file is ignored, breaking checked-target resume. This also exists on origin/main, but the workspace change does not resolve it.

FAIL — full filename parity.
PASS — Base64URL workspace segment: the ...

gpt-5.6-terra · 2026-08-25 15:19:40 · ~1.2K tokens

Distillations (6)

Gen 0 2026-08-25 15:20:45 · 1936 tokens

Date: Aug 25, 2026 * 🟡 (15:11) Attempted `yarn run v1.22.22` / `vitest run src/modules/__tests__/details-from-context.js`; failed because `/bin/sh: 1: vitest: not found`, exiting with code `127`. * 🟡 (15:12) An inline Node parser-check command failed to compile with `SyntaxError: missing ) after argument list` / `Expected unicode escape` under Node.js `v24.0.0`; the attempted command tested `de…

Gen 0 2026-08-25 15:13:28 · 678 tokens

Date: Aug 25, 2026 * 🟡 (15:09) Inspected `/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/__tests__/update-issue.js` (180 lines). It imports Vitest (`vi`, `describe`, `test`, `expect`, `beforeAll`, `beforeEach`, `it`) and Node `fs`, then requires `{ updateIssue, transformIssueBody }` from `../update-issue.js`. * 🟡 (15:09) `updateTargetsArgs` test fixture uses `inputs: { repo: …

Gen 0 2026-08-25 15:12:32 · 2346 tokens

Date: Aug 25, 2026 * 🟡 (14:52) Inspected `/home/byk/Code/getsentry/craft/src/utils/publishState.ts` (110 lines). Publish-resume state was moved from repository-local `.craft-publish-<version>.json` to `$XDG_STATE_HOME/craft/`, falling back to `$HOME/.local/state/craft/`, to prevent committed repository contents or prior CI steps from pre-populating completed targets and causing silent skips. `ge…

Gen 0 2026-08-25 15:10:08 · 214 tokens

Date: Aug 25, 2026 * 🟡 (14:47) Tool search returned “No files found” for an unspecified query. * 🟡 (14:47) Tool search found 13 matches for `getPublishStateFilename`: definition in `/home/byk/Code/getsentry/craft/src/utils/publishState.ts` line 78; invocation in the same file line 108; import/reference in `/home/byk/Code/getsentry/craft/src/utils/__tests__/publishState.test.ts` line 7; test sui…

Gen 0 2026-08-25 14:48:39 · 396 tokens

Date: Aug 25, 2026 * 🟡 (14:43) User supplied an additional, heavily elided workflow-file excerpt for review. Visible details include a `blocker_label` input; a target-branch description stating it falls back to the default branch; workflow outputs mapped from `steps.craft.outputs.version`, `steps.craft.outputs.sha`, and `steps.craft.outputs.changelog`; and a Bash shell. * 🟡 (14:43) Visible work…

Gen 0 2026-08-25 14:46:09 · 2561 tokens

Date: Aug 25, 2026 * 🟡 [requested-security-review] (14:33) User requested a read-only adversarial correctness/security review of uncommitted changes in `/home/byk/Code/getsentry/publish-workspace-acceptance` on branch `feat/workspace-acceptance`, compared with `origin/main`; explicitly requested no file modifications. * 🟡 (14:33) Review scope requested: `.github/workflows/publish.yml`, `src/mod…