DashboardcraftSession 1VAe7oiQQmkH

Session 1VAe7oiQQmkH

Full ID: 1VAe7oiQQmkHJeHjX
Messages: 47
Distillations: 1
Time range: 2026-09-03 23:52:20 — 2026-09-04 00:02:54

Conversation (47 messages)

Strict READ-ONLY adversarial code review. Do not edit, format, commit, or change VCS state. Inspect the full uncommitted diffs plus relevant source, tests, docs, and workflow in BOTH:

  • /home/byk/Code/getsentry/craft-workspace-action-propagation
  • /home/byk/Code/getsentry/publish-workspace-acceptance

Return substantive evidence only. Find bugs, security risks, behavioral regressions, or missing tests. Treat this contract as exact:

  • Craft workspace keys can be literal paths or globs; glob expansions are directory-only, relative to .craft.yml, exclude node_modules, preserve exact case, and overlaps fail.
  • Action validation must accept valid slash-separated workspace names including packages/CLI, but reject empty components, ./.., __proto__ in ANY component, leading-hyphen components, controls/non-ASCII, before any side effect.
  • New titles carry the checkout repository identity and full path suffix only, not JSON workspace syntax. Only after checkout at the CI-authoritative revision, exact full suffix matching against craft workspace list turns it into root workspace; all nonmatches remain checkout paths.
  • Exact path/workspace identity must reach state isolation and craft publish --rev; no traversal/injection/cross-repository state bleed.
  • Publish only runs on the fresh CI-poller ci-ready event, never directly on accepted.
  • Legacy behavior required by the updated docs must remain accurate.

For every contract point give PASS, CONCERN, or MUST-FIX with exact file:line evidence. Prioritize findings by severity. End exactly MERGE or DO-NOT-MERGE. Do not return an empty response or generic summary; if a tool blocks inspection, begin BLOCKED and state the exact command/error.

gateway · 2026-09-03 23:52:20
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation</path>
<type>directory</type>
<entries>
.craft.yml
.dockerignore
.eslintcache
.git
.github/
.gitignore
.lore.md
.nojekyll
.npmignore
.prettierignore
.prettierrc.yml
.tsbuildinfo
.vscode/
action.yml
AGENTS.md
blog-post-draft.md
build.mjs
CHANGELOG.md
CONTRIBUTING.md
dist/
Dockerfile
docs/
eslint.config.mjs
img/
LICENSE
node_modules/
package.json
pnpm-lock.yaml
README.md
src/
tsconfig.build.json
tsconfig.json
vitest.config.mts

(33 entries)
</entries>
gateway · 2026-09-03 23:53:36
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
scripts/
src/
vitest.config.js
yarn.lock

(15 entries)
</entries>
gateway · 2026-09-03 23:53:40
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/AGENTS.md</path>
<type>file</type>
<content>
1: # AGENTS.md
2: 
3: This file provides guidance for AI coding assistants working with the Craft codebase.
4: 
5: ## Package Management
6: 
7: - **Always use `pnpm`** for package management. Never use `npm` or `yarn`.
8: - Node.js version is managed by [Volta](https://volta.sh/) (currently v22.12.0).
9: - Install dependencies with `pnpm install --frozen-lockfile`.
10: 
11: ## Development Commands
12: 
13: | Command      | Description                                 |
14: | ------------ | ------------------------------------------- |
15: | `pnpm build` | Build the project (outputs to `dist/craft`) |
16: | `pnpm test`  | Run tests                                   |
17: | `pnpm lint`  | Run ESLint                                  |
18: | `pnpm fix`   | Auto-fix lint issues                        |
19: 
20: To manually test changes:
21: 
22: ```bash
23: pnpm build && ...
gateway · 2026-09-03 23:53:43
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-09-03 23:53:48
result
 M action.yml
 M docs/src/content/docs/targets/github.md
 M src/__tests__/action.test.ts
 M src/__tests__/config.test.ts
 M src/config.ts
 action.yml                              |  4 +-
 docs/src/content/docs/targets/github.md | 19 ++++---
 src/__tests__/action.test.ts            | 55 +++++++++++++------
 src/__tests__/config.test.ts            | 52 +++++++++++++++++-
 src/config.ts                           | 94 +++++++++++++++++++++++++++++----
 5 files changed, 187 insertions(+), 37 deletions(-)
diff --git a/action.yml b/action.yml
index 94f3158..84377a8 100644
--- a/action.yml
+++ b/action.yml
@@ -81,8 +81,8 @@ runs:
           echo "::error::The path and workspace inputs cannot be used together."
           exit 1
         fi
-        if [[ -n "$WORKSPACE" ]] && ! node -e 'process.exit(/^(?!\.{1,2}$)(?!__proto__$)(?!-)[A-Za-z0-9_.-]+$/.test(process.env.WORKSPACE) ? ...
gateway · 2026-09-03 23:53:53
result
 M .github/workflows/publish.yml
 M docs/publish-issue-format.md
 M src/modules/__tests__/ci-poller-input.js
 M src/modules/__tests__/details-from-context.js
 M src/modules/__tests__/publish-location.js
 M src/modules/__tests__/publish-workflow.js
 M src/modules/details-from-context.js
 M src/modules/post-workflow-details.js
 M src/modules/publish-issue-title.js
 M src/modules/publish-issue-title.peggy
 M src/modules/publish-location.js
 M src/publish/inputs.js
 M src/publish/resolve-location.js
 .github/workflows/publish.yml                 |   7 +-
 docs/publish-issue-format.md                  |  40 ++---
 src/modules/__tests__/ci-poller-input.js      |   4 +-
 src/modules/__tests__/details-from-context.js | 198 +----------------------
 src/modules/__tests__/publish-location.js     |  75 +++------
 src/modules/__tests__/publish-workflow.js     |   8 +-
 src/modules/details-from-context.js           |  24 +--
 src/modules/post-workflow-details.js          |   4 +-
 ...
gateway · 2026-09-03 23:53:58
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-09-03 23:54:02
result
Found 27 matches
/home/byk/Code/getsentry/craft-workspace-action-propagation/src/commands/workspace_cmds/__tests__/list.test.ts:
  Line 4:   getWorkspaceNames: vi.fn(),

  Line 10: import { getWorkspaceNames } from '../../../config';

  Line 13: describe('workspace list command', () => {

  Line 14:   test('prints exact configured workspace names', () => {

  Line 15:     vi.mocked(getWorkspaceNames).mockReturnValue(['cli', 'mcp.v2']);

  Line 23:   test('prints an empty array when no workspaces are configured', () => {

  Line 24:     vi.mocked(getWorkspaceNames).mockReturnValue([]);


/home/byk/Code/getsentry/craft-workspace-action-propagation/src/commands/workspace_cmds/list.ts:
  Line 1: import { getWorkspaceNames } from '../../config';

  Line 5: export const description = 'List defined release workspaces as a JSON array';

  Line 8:   console.log(formatJson(getWorkspaceNames()));


/home/byk/Code/getsentry/craft-workspace-action-propagation/src/commands/workspace.ts:
  Line 3: ...
gateway · 2026-09-03 23:54:05
result
Found 100 matches (more matches available)
/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/resolve-release-revision.js:
  Line 5:   const { repo } = JSON.parse(process.env.PUBLISH_ARGS || "");


/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/resolve-location.js:
  Line 2: const { resolvePublishLocation } = require("../modules/publish-location");

  Line 5:   const input = JSON.parse(process.env.PUBLISH_ARGS || "");

  Line 6:   const workspaceNames = JSON.parse(process.env.CRAFT_WORKSPACE_NAMES || "");

  Line 8:   if (!Array.isArray(workspaceNames)) {

  Line 10:       "Craft workspace discovery returned an invalid workspace list."

  Line 16:     resolvePublishLocation({

  Line 18:       workspaceNames,


/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/update-issue.js:
  Line 7: const inputs = JSON.parse(process.env.PUBLISH_ARGS);


/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/post-result.js:
  Line 7: ...
gateway · 2026-09-03 23:54:10
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/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-09-03 23:54:14
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/action.yml</path>
<type>file</type>
<content>
175:     - name: Craft Prepare
176:       id: craft
177:       shell: bash
178:       env:
179:         CRAFT_LOG_LEVEL: Debug
180:         CRAFT_CONFIG_FROM_MERGE_TARGET: ${{ inputs.craft_config_from_merge_target }}
181:         MERGE_TARGET: ${{ inputs.merge_target }}
182:         VERSION: ${{ inputs.version }}
183:         WORKSPACE: ${{ inputs.workspace }}
184:       working-directory: ${{ inputs.path }}
185:       run: |
186:         # Ensure we have origin/HEAD set
187:         git remote set-head origin --auto
188: 
189:         # Build command with optional flags
190:         CRAFT_ARGS=()
191:         if [[ "$CRAFT_CONFIG_FROM_MERGE_TARGET" == 'true' && -n "$MERGE_TARGET" ]]; then
192:           CRAFT_ARGS=(--config-from "$MERGE_TARGET")
193:         fi
194:         if [[ -n "$WORKSPACE" ]]; then
195:           CRAFT_ARGS+=("--workspace=$WORKSPACE")
196:         ...
gateway · 2026-09-03 23:54:26
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/config.ts</path>
<type>file</type>
<content>
1: import { existsSync, lstatSync, readFileSync } from 'fs';
2: import path from 'path';
3: import { globSync, hasMagic } from 'glob';
4: 
5: import { load } from 'js-yaml';
6: import GitUrlParse from 'git-url-parse';
7: import { createGitClient } from './utils/git';
8: import { ZodError } from 'zod';
9: 
10: import { logger } from './logger';
11: import {
12:   CraftProjectConfig,
13:   CraftProjectConfigSchema,
14:   GitHubGlobalConfig,
15:   ArtifactProviderName,
16:   StatusProviderName,
17:   TargetConfig,
18:   ChangelogPolicy,
19:   VersioningPolicy,
20:   Workspace,
21: } from './schemas/project_config';
22: import { ConfigurationError } from './utils/errors';
23: import { isCompiledGitHubAction } from './utils/detection';
24: import {
25:   getPackageVersion,
26:   parseVersion,
27:   versionGreaterOrEqualThan,
28:   SemVer,
29: } from './utils/version';
30: // ...
gateway · 2026-09-03 23:54:31
result
Found 6 matches
/home/byk/Code/getsentry/craft-workspace-action-propagation/src/schemas/project_config.ts:
  Line 171:  * unit) and each entry under the top-level `workspaces` map (an explicit,

  Line 176:  * different concept from the `npm` target's `workspaces: true` field, which

  Line 207: export const WorkspaceSchema = z.object({

  Line 216: export type Workspace = z.infer<typeof WorkspaceSchema>;

  Line 247:     workspaces: z.record(WorkspaceNameSchema, WorkspaceSchema).optional(),

  Line 251:       Object.keys(config.workspaces || {}).length > 0 &&
gateway · 2026-09-03 23:54:36
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/schemas/project_config.ts</path>
<type>file</type>
<content>
150:   calver: CalVerConfigSchema.optional(),
151: });
152: 
153: export type VersioningConfig = z.infer<typeof VersioningConfigSchema>;
154: 
155: /**
156:  * Changelog configuration
157:  */
158: export const ChangelogConfigSchema = z.union([
159:   z.string(),
160:   z.object({
161:     filePath: z.string().optional(),
162:     policy: z.enum(['auto', 'simple', 'none']).optional(),
163:     scopeGrouping: z.boolean().optional(),
164:   }),
165: ]);
166: 
167: /**
168:  * Fields that describe how a single release unit is built and published.
169:  *
170:  * These are shared between the top-level config (the implicit/default release
171:  * unit) and each entry under the top-level `workspaces` map (an explicit,
172:  * independently-versioned release unit). A workspace inherits the top-level
173:  * values as defaults and overrides the fields it declares. ...
gateway · 2026-09-03 23:54:51
result
Found 1 matches
/home/byk/Code/getsentry/craft-workspace-action-propagation/package.json:
  Line 49:     "glob": "^11.0.0",
gateway · 2026-09-03 23:54:56
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/__tests__/config.test.ts</path>
<type>file</type>
<content>
1: import { describe, test, expect, vi, afterEach, beforeEach } from 'vitest';
2: import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'fs';
3: import { tmpdir } from 'os';
4: import { join } from 'path';
5: /**
6:  * Tests of our ability to read craft config files. (This is NOT general test
7:  * configuration).
8:  */
9: 
10: import {
11:   getGitTagPrefix,
12:   loadConfigurationFromString,
13:   validateConfiguration,
14:   setActiveWorkspace,
15:   getActiveWorkspace,
16:   getConfiguration,
17:   getVersioningPolicy,
18:   getWorkspaceNames,
19:   WORKSPACES_MIN_VERSION,
20: } from '../config';
21: import { CraftProjectConfigSchema } from '../schemas/project_config';
22: import { logger } from '../logger';
23: 
24: describe('validateConfiguration', () => {
25:   test('parses minimal configuration', () => {
26:     const data = { github: { ...
gateway · 2026-09-03 23:55:01
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-09-03 23:55:07
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-09-03 23:55:28
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-09-03 23:56:01
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/__tests__/action.test.ts</path>
<type>file</type>
<content>
1: import {
2:   chmodSync,
3:   mkdtempSync,
4:   mkdirSync,
5:   readFileSync,
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 { load } from 'js-yaml';
14: import { afterEach, expect, test } from 'vitest';
15: 
16: interface ActionStep {
17:   env?: Record<string, string>;
18:   name?: string;
19:   run?: string;
20: }
21: 
22: function getActionSteps(): ActionStep[] {
23:   const action = load(
24:     readFileSync(join(__dirname, '../../action.yml'), 'utf8'),
25:   ) as {
26:     runs?: { steps?: ActionStep[] };
27:   };
28:   return action.runs?.steps || [];
29: }
30: 
31: function getActionStep(name: string): ActionStep {
32:   const step = getActionSteps().find(step => step.name === name);
33:   if (!step?.run) {
34:     ...
gateway · 2026-09-03 23:56:08
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/details-from-context.js</path>
<type>file</type>
<content>
1: const { parse: parsePublishIssueTitle } = require("./publish-issue-title");
2: 
3: /**
4:  * Matches the entire "Targets" section of a github publish issue body.
5:  */
6: const TARGETS_SECTION_PARSER_REGEX =
7:   /^(?!### Targets$\s)(?: *- \[[ xX]\] \S+\s*$(?:\r?\n)?)+/m;
8: 
9: /**
10:  * Matches all targets of a github publish issue body in a section that was already matched and extracted with `TARGETS_PARSER_REGEX`.
11:  * The "id" of the targets is captured within a capture group.
12:  */
13: const TARGETS_PARSER_REGEX = /^\s*- \[[ x]\] (\S+)/gim;
14: 
15: /**
16:  * Matches checked targets of a github publish issue body in a section that was already matched and extracted with `TARGETS_PARSER_REGEX`.
17:  * The "id" of the targets is captured within a capture group. ...
gateway · 2026-09-03 23:56:14
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/publish-issue-title.peggy</path>
<type>file</type>
<content>
1: {
2:   function join(characters) {
3:     return characters.join("");
4:   }
5: }
6: 
7: // Canonical grammar for publish issue titles. A path suffix is syntactic only:
8: // the controller resolves the complete suffix as a workspace after checking
9: // out the CI-approved revision.
10: PublishIssueTitle
11:   = "publish: " "getsentry/"? repo:Repository path:Path? "@" version:Version !. {
12:       return {
13:         repo,
14:         path: path || "",
15:         version,
16:       };
17:     }
18: 
19: Repository
20:   = characters:RepositoryCharacter+ { return join(characters); }
21: 
22: RepositoryCharacter
23:   = [A-Za-z0-9_.-]
24: 
25: Path
26:   = segments:("/" segment:PathSegment { return `/${segment}`; })+ { return join(segments); }
27: 
28: PathSegment
29:   = characters:RepositoryCharacter+ { return join(characters); }
30: 
31: ...
gateway · 2026-09-03 23:56:18
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/publish-location.js</path>
<type>file</type>
<content>
1: function resolvePublishLocation({ path, workspaceNames }) {
2:   if (path === ".") {
3:     return { path };
4:   }
5: 
6:   if (!workspaceNames.every(isWorkspaceName)) {
7:     throw new Error(
8:       "Craft workspace discovery returned an invalid workspace list."
9:     );
10:   }
11: 
12:   const workspace = path.slice(2);
13:   if (workspaceNames.includes(workspace)) {
14:     return { path: ".", workspace };
15:   }
16: 
17:   return { path };
18: }
19: 
20: function isWorkspaceName(name) {
21:   return (
22:     typeof name === "string" &&
23:     name !== "__proto__" &&
24:     name !== "." &&
25:     name !== ".." &&
26:     !name.split("/").includes("..") &&
27:     /^(?:[A-Za-z0-9_.-]+\/)*[A-Za-z0-9_.-]+$/.test(name)
28:   );
29: }
30: 
31: module.exports = { resolvePublishLocation };

(End of file - total 31 lines)
</content>
gateway · 2026-09-03 23:56:37
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/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:     "generate": "node scripts/generate-publish-issue-title-parser.js",
19:     "check:generated": "node scripts/generate-publish-issue-title-parser.js --check",
20:     "test": "yarn check:generated && vitest run",
21:     "test:watch": "vitest",
22:     "lint": "eslint src .github --ignore-pattern '!.github'",
23:     "prettier": "prettier --write src"
24:   },
25:   "devDependencies": {
26:     "eslint": ...
gateway · 2026-09-03 23:56:41
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/scripts/generate-publish-issue-title-parser.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const path = require("path");
3: const peggy = require("peggy");
4: const prettier = require("prettier");
5: 
6: const grammarPath = path.join(
7:   __dirname,
8:   "..",
9:   "src",
10:   "modules",
11:   "publish-issue-title.peggy"
12: );
13: const outputPath = path.join(
14:   __dirname,
15:   "..",
16:   "src",
17:   "modules",
18:   "publish-issue-title.js"
19: );
20: const grammar = fs.readFileSync(grammarPath, "utf8");
21: const parser = peggy.generate(grammar, {
22:   format: "commonjs",
23:   grammarSource: "publish-issue-title.peggy",
24:   output: "source",
25: });
26: 
27: const generatedParser = prettier.format(`/* eslint-disable */\n${parser}`, {
28:   filepath: outputPath,
29: });
30: 
31: if (process.argv.includes("--check")) {
32:   const currentParser = fs.readFileSync(outputPath, "utf8");
33:   ...
gateway · 2026-09-03 23:56:53
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/docs/publish-issue-format.md</path>
<type>file</type>
<content>
1: # Publish Issue Format
2: 
3: The Publish workflow treats a publish issue as a release request. The title identifies
4: the release; the body supplies the merge target and selected targets. Do not edit the
5: title by hand unless it remains valid under this format.
6: 
7: ## Title
8: 
9: Every title starts with `publish: `. This EBNF is canonical:
10: 
11: ```text
12: title             = "publish: ", [ "getsentry/" ], repository, [ path ],
13:                     "@", version ;
14: repository        = token, { token } ;
15: path              = "/", path-segment, { "/", path-segment } ;
16: path-segment      = token, { token } ;
17: version           = version-character, { version-character } ;
18: token             = ? ASCII letter, digit, ".", "_", or "-" ? ...
gateway · 2026-09-03 23:57:07
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/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}- uses: docker:\/\/getsentry\/craft:latest/
23:   );
24:   if (!section?.groups?.script) {
25:     throw new Error("Missing Set targets workflow ...
gateway · 2026-09-03 23:57:12
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/.github/workflows/ci-poller.yml</path>
<type>file</type>
<content>
1: name: CI Status Poller
2: 
3: on:
4:   schedule:
5:     - cron: "*/5 * * * *"
6:   workflow_dispatch:
7:     inputs:
8:       attempt:
9:         description: Self-dispatch attempt counter (internal use)
10:         required: false
11:         default: "0"
12: 
13: permissions:
14:   contents: read
15:   issues: read
16: 
17: jobs:
18:   check-ci:
19:     runs-on: ubuntu-latest
20:     environment: production
21:     # Skip entirely (no runner provisioned) when there's nothing to check.
22:     # Set to "true" by ci-pending.yml, reset to "false" here when done.
23:     # Always allow workflow_dispatch for manual recovery.
24:     if: vars.CI_POLLER_HAS_PENDING == 'true' || github.event_name == 'workflow_dispatch'
25:     concurrency:
26:       group: ci-status-poller
27:       cancel-in-progress: false
28:     steps:
29:       # sentry-internal-app token ...
gateway · 2026-09-03 23:57:24
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/resolve-release-revision.js</path>
<type>file</type>
<content>
1: const core = require("@actions/core");
2: const { getReleaseRevision } = require("../modules/release-revision");
3: 
4: function resolveReleaseRevision() {
5:   const { repo } = JSON.parse(process.env.PUBLISH_ARGS || "");
6:   core.setOutput(
7:     "revision",
8:     getReleaseRevision({
9:       issueBody: process.env.PUBLISH_ISSUE_BODY || "",
10:       repo,
11:     })
12:   );
13: }
14: 
15: resolveReleaseRevision();

(End of file - total 15 lines)
</content>
gateway · 2026-09-03 23:57:28
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/release-revision.js</path>
<type>file</type>
<content>
1: const CHECK_RUNS_LINK =
2:   /^Requested by: @[^\r\n]+\r?\n(?:[ \t]*\r?\n)?[ \t]*Merge target: [^\r\n]+\r?\n(?:[ \t]*\r?\n)?[ \t]*Quick links:\r?\n(?:[ \t]*\r?\n)?[ \t]*- \[View changes\]\([^\r\n]+\)\r?\n[ \t]*- \[View check runs\]\(https:\/\/github\.com\/getsentry\/(?<repo>[A-Za-z0-9_.-]+)\/commit\/(?<revision>[0-9a-f]{40})\/checks\/?\)(?=\r?\n|$)/;
3: const CHECK_RUNS_LINK_COUNT = /^[ \t]*- \[View check runs\]\(/gm;
4: 
5: function getReleaseRevision({ issueBody, repo }) {
6:   if ((issueBody.match(CHECK_RUNS_LINK_COUNT) || []).length !== 1) {
7:     throw new Error(
8:       `Expected exactly one View check runs link in Quick links for getsentry/${repo}.`
9:     );
10:   }
11: 
12:   const match = issueBody.match(CHECK_RUNS_LINK);
13:   if (!match?.groups || match.groups.repo !== repo) {
14:     throw new Error(
15:       `Expected a View check runs link ...
gateway · 2026-09-03 23:57:41
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/commands/publish.ts</path>
<type>file</type>
<content>
680:   const revision = await git.revparse('HEAD');
681:   logger.debug('Revision to publish: ', revision);
682: 
683:   const statusProvider = await getStatusProviderFromConfig();
684:   const artifactProvider = await getArtifactProviderFromConfig();
685: 
686:   // Check status of all CI builds linked to the revision
687:   await checkRevisionStatus(statusProvider, revision, argv.noStatusCheck);
688: 
689:   await printRevisionSummary(artifactProvider, revision);
690: 
691:   await checkRequiredArtifacts(artifactProvider, revision, config.requireNames);
692: 
693:   // Find targets
694:   let targetsToPublish: Set<string> = new Set(
695:     (typeof argv.target === 'string' ? [argv.target] : argv.target) || [
696:       SpecialTarget.All,
697:     ],
698:   );
699: 
700:   // Treat "all"/"none" specially
701:   for (const specialTarget of [SpecialTarget.All, ...
gateway · 2026-09-03 23:57:48
result
Found 10 matches
/home/byk/Code/getsentry/craft-workspace-action-propagation/src/commands/publish.ts:
  Line 177: export function getPublishStateGitHubConfig(

  Line 180:     .CRAFT_PUBLISH_STATE_GITHUB_REPO,

  Line 191:       'CRAFT_PUBLISH_STATE_GITHUB_REPO must be a GitHub owner/repository pair.',

  Line 726:     getPublishStateGitHubConfig(publishStateGithubConfig),


/home/byk/Code/getsentry/craft-workspace-action-propagation/src/commands/__tests__/publish.test.ts:
  Line 5:   getPublishStateGitHubConfig,

  Line 168: describe('getPublishStateGitHubConfig', () => {

  Line 175:     const stateGithub = getPublishStateGitHubConfig(

  Line 201:     expect(getPublishStateGitHubConfig(githubConfig, undefined)).toBe(

  Line 208:       getPublishStateGitHubConfig(null, 'getsentry/toolkit/extra'),

  Line 209:     ).toThrow('CRAFT_PUBLISH_STATE_GITHUB_REPO');
gateway · 2026-09-03 23:57:53
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/commands/publish.ts</path>
<type>file</type>
<content>
148:   /** Target branch to merge the release into, auto detected when empty */
149:   mergeTarget?: string;
150:   /** One or more targets we want to publish */
151:   target?: string | string[];
152:   /** The new version to publish */
153:   newVersion: string;
154:   /** Do not perform merge after publishing */
155:   noMerge: boolean;
156:   /** Do not remove downloads after publishing */
157:   keepDownloads: boolean;
158:   /** Do not perform build status check */
159:   noStatusCheck: boolean;
160:   /** Do not remove release branch after publishing */
161:   keepBranch: boolean;
162:   /** Do not perform basic git checks */
163:   noGitChecks: boolean;
164: }
165: 
166: export interface PublishState {
167:   published: {
168:     [targetId: string]: boolean;
169:   };
170: }
171: 
172: /**
173:  * The Publish controller prepopulates a secure state file ...
gateway · 2026-09-03 23:58:10
result
No files found
gateway · 2026-09-03 23:58:15
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-09-03 23:58:35
result
true
gateway · 2026-09-03 23:58:50
result
Found 22 matches
/home/byk/Code/getsentry/craft-workspace-action-propagation/src/config.ts:
  Line 429:  * This is used by --config-from to load config from a remote branch.

  Line 433: export function loadConfigurationFromString(


/home/byk/Code/getsentry/craft-workspace-action-propagation/src/commands/prepare.ts:
  Line 17:   loadConfigurationFromString,

  Line 125:     .option('config-from', {

  Line 796:   // Handle --config-from: load config from remote branch. The caller

  Line 810:       loadConfigurationFromString(configContent);


/home/byk/Code/getsentry/craft-workspace-action-propagation/src/__tests__/config.test.ts:
  Line 12:   loadConfigurationFromString,

  Line 178:       loadConfigurationFromString(

  Line 229:     loadConfigurationFromString(

  Line 330:     loadConfigurationFromString(

  Line 338:     const config = loadConfigurationFromString(WS_CONFIG);

  Line 352:     const config = loadConfigurationFromString(WS_CONFIG);

  Line 361:     expect(() => ...
gateway · 2026-09-03 23:58:54
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/commands/prepare.ts</path>
<type>file</type>
<content>
760:     }
761: 
762:     const latestTag = await getLatestTag(git, getGitTagPrefix());
763: 
764:     // Determine bump type - either from arg or from commit analysis
765:     let bumpType: BumpType;
766:     if (version === 'auto') {
767:       const changelogResult = await getChangelogWithBumpType(git, latestTag);
768:       validateBumpType(changelogResult);
769:       bumpType = changelogResult.bumpType;
770:     } else {
771:       bumpType = version as BumpType;
772:     }
773: 
774:     // Calculate new version from latest tag
775:     const currentVersion = (latestTag && getVersion(latestTag)) || '0.0.0';
776: 
777:     const newVersion = calculateNextVersion(currentVersion, bumpType);
778:     logger.info(
779:       `Version bump: ${currentVersion} -> ${newVersion} (${bumpType} bump)`,
780:     );
781:     return newVersion;
782:   }
783: 
784:   // ...
gateway · 2026-09-03 23:59:15
result
[]
gateway · 2026-09-03 23:59:44
result
Found 9 matches
/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/update-issue.js:
  Line 79:       name: "accepted",


/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/__tests__/update-issue.js:
  Line 63: Assign the **accepted** label to this issue to approve the release.\r

  Line 129:       name: "accepted",

  Line 161: Assign the **accepted** label to this issue to approve the release.

  Line 180: Assign the **accepted** label to this issue to approve the release.


/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/__tests__/details-from-context.js:
  Line 21: Assign the **accepted** label to this issue to approve the release.

  Line 31:         labels: ["accepted"],

  Line 79: Assign the **accepted** label to this issue to approve the release.

  Line 86:         labels: ["accepted"],
gateway · 2026-09-03 23:59:52
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/__tests__/ci-poller-input.js</path>
<type>file</type>
<content>
1: import { readFileSync } from "fs";
2: import { join } from "path";
3: 
4: import { expect, test } from "vitest";
5: 
6: const { getCiPollerInput } = require("../ci-poller-input.js");
7: 
8: const REVISION = "7e5ca7ed5581552de066e2a8bc295b8306be38ac";
9: const issueBody = `Requested by: @byk
10: 
11: Merge target: (default)
12: 
13: Quick links:
14: - [View changes](https://github.com/getsentry/toolkit/compare/1.2.2...release/1.2.3)
15: - [View check runs](https://github.com/getsentry/toolkit/commit/${REVISION}/checks/)`;
16: 
17: test("parses root and full-path publish titles", () => {
18:   expect(
19:     getCiPollerInput({
20:       issueBody,
21:       title: "publish: getsentry/toolkit/cli@1.2.3",
22:     })
23:   ).toEqual({
24:     repo: "getsentry/toolkit",
25:     revision: REVISION,
26:     version: "1.2.3",
27:   });
28: 
29:   ...
gateway · 2026-09-04 00:00:06
result
false
gateway · 2026-09-04 00:00:12
result
["../publish-workspace-acceptance/node_modules/yocto-queue","../publish-workspace-acceptance/node_modules/yaml-eslint-parser","../publish-workspace-acceptance/node_modules/yaml","../publish-workspace-acceptance/node_modules/xtend","../publish-workspace-acceptance/node_modules/wrappy","../publish-workspace-acceptance/node_modules/word-wrap","../publish-workspace-acceptance/node_modules/why-is-node-running","../publish-workspace-acceptance/node_modules/which","../publish-workspace-acceptance/node_modules/vitest","../publish-workspace-acceptance/node_modules/vite","../publish-workspace-acceptance/node_modules/uri-js","../publish-workspace-acceptance/node_modules/universal-user-agent","../publish-workspace-acceptance/node_modules/undici-types","../publish-workspace-acceptance/node_modules/undici","../publish-workspace-acceptance/node_modules/type-fest","../publish-workspace-acceptance/node_modules/type-check","../publish-workspace-acceptance/node_modules/tunnel","../publish-workspace-accep...
gateway · 2026-09-04 00:00:59
result
82ef18f1862e3250b311cf9158358824e0d7c8eb4ebfe91efbe389ac30a3e44d  -
 M action.yml
 M docs/src/content/docs/targets/github.md
 M src/__tests__/action.test.ts
 M src/__tests__/config.test.ts
 M src/config.ts
gateway · 2026-09-04 00:01:05
result
454f87793403a76bb0607a9b24002ccb96886d4c0c5bafd298be7ed603125041  -
 M .github/workflows/publish.yml
 M docs/publish-issue-format.md
 M src/modules/__tests__/ci-poller-input.js
 M src/modules/__tests__/details-from-context.js
 M src/modules/__tests__/publish-location.js
 M src/modules/__tests__/publish-workflow.js
 M src/modules/details-from-context.js
 M src/modules/post-workflow-details.js
 M src/modules/publish-issue-title.js
 M src/modules/publish-issue-title.peggy
 M src/modules/publish-location.js
 M src/publish/inputs.js
 M src/publish/resolve-location.js
gateway · 2026-09-04 00:02:54

Findings

  1. MAJOR — Glob keys can escape the config root and include node_modules.
    src/config.ts:218-229 passes raw patterns to globSync; ../ patterns escape configDirectory, while path.resolve() accepts those results. The ignore: ['**/node_modules/**'] rule does not exclude ../…/node_modules/... matches. This violates the directory-relative and node_modules exclusion contract. Reject traversal patterns/results and enforce containment before listing or resolving workspaces.

  2. MAJOR — Glob workspaces break --config-from.
    prepare.ts:796-810 loads remote .craft.yml with loadConfigurationFromString(), but glob resolution calls getConfigFilePath() at config.ts:218-220. It therefore fails when no local .craft.yml exists, or expands relative to an unrelated local config. Carry the remote config’s base directory explicitly. No regression test covers glob resolution through loadConfigurationFromString().

  3. MAJOR — Publish accepts forbidden workspace components from issue titles.
    details-from-context.js:37-40 rejects only ..; publish-location.js:20-28 accepts packages/__proto__/cli, packages/./cli, and packages/-cli. Craft’s schema also only rejects whole-key __proto__ / . / .. values (project_config.ts:218-227). A hand-created accepted issue plus a matching literal config key reaches CRAFT_WORKSPACE (publish.yml:253), bypassing the action’s component validation. Apply one shared segment validator before checkout and before state/publish side effects.

  4. MODERATE — Updated documentation contradicts the workflow and validation contract.

    • docs/publish-issue-format.md:79 says accepted “starts publishing,” but publishing requires a fresh ci-ready label event and no pending/failed labels (.github/workflows/publish.yml:102-114).
    • docs/publish-issue-format.md:30 claims [A-Za-z0-9_.-]+ segments are valid, while the action rejects ., .., __proto__, and ...
gpt-5.6-terra · 2026-09-04 00:02:54 · ~906 tokens

Distillations (1)

Gen 0 2026-09-03 23:54:10 · 513 tokens

Date: Sep 3, 2026 * 🔴 [requested-read-only-review] (23:52) User requested a strict read-only adversarial code review of full uncommitted diffs plus relevant source, tests, docs, and workflow in BOTH `/home/byk/Code/getsentry/craft-workspace-action-propagation` and `/home/byk/Code/getsentry/publish-workspace-acceptance`. User directed: do not edit, format, commit, or change VCS state. * 🔴 (23:52…