DashboardcraftSession 1abfTSgx5xe4

Session 1abfTSgx5xe4

Full ID: 1abfTSgx5xe4ySSnv
Messages: 41
Distillations: 0
Time range: 2026-09-04 03:27:54 — 2026-09-04 03:37:40

Conversation (41 messages)

Perform a strict read-only adversarial audit of the exact uncommitted changes in both /home/byk/Code/getsentry/craft-workspace-action-propagation and /home/byk/Code/getsentry/publish-workspace-acceptance. Do not edit any files or run formatters. Inspect the complete diffs plus relevant source, tests, action/workflow behavior, generated parser, and docs. Check: full concrete path identity without normalization; glob expansion correct relative to config/root including --config-from; traversal/absolute/backslash/prototype safety before filesystem or checkout side effects; legacy compatibility required by current code; CI-approved revision ordering; state filename isolation; CI labels contract; title grammar/parser sync; workflow injection risk; and only intended files changed. Run only read-only checks if useful. Return severity-ordered findings with exact file:line citations, explicit PASS/FAIL for each listed check, and end exactly MERGE or DO-NOT-MERGE. If blocked, begin BLOCKED and name the exact issue; never return empty.

gateway · 2026-09-04 03:27:54
result
<skill_content name="jj-guide">
# Skill: jj-guide

# jj Guide for AI Agents

Jujutsu (jj) is a Git-compatible VCS with mutable commits, automatic snapshotting, no staging area, and first-class conflicts. This skill teaches you how to use it safely from a non-interactive agent environment.

If `.jj/` exists in the repo root, this is a jj repo. **Use `jj` commands, not `git`.** In a colocated repo (`.jj/` *and* `.git/`), git tools can read the state, but mutations should go through `jj` so the operation log stays consistent.

## Critical Rules

- **NEVER** use interactive flags (`-i`, `--interactive`). TUI prompts hang in agent environments. This applies to `jj split -i`, `jj squash -i`, `jj commit -i`, `jj resolve`, `jj diffedit`, etc.
- **ALWAYS** pass `-m "msg"` when describing/committing. Without `-m`, an editor opens and hangs.
- **VERIFY** mutations with `jj st` and `jj log` after `squash`, `abandon`, `rebase`, `restore`, `commit`. ...
gateway · 2026-09-04 03:28:01
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-04 03:29:20
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-04 03:29:27
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/commands/prepare.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            |  71 ++++++++++++++-
 src/commands/prepare.ts                 |   3 +-
 src/config.ts                           | 149 +++++++++++++++++++++++++++++---
 6 files changed, 260 insertions(+), 41 deletions(-)
diff --git a/action.yml b/action.yml
index 94f3158cc43d5167de21fe400b0162b0cb62d44d..84377a84ad432e202c0d739c502ded29bd102202 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 ...
gateway · 2026-09-04 03:29:32
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 | 191 ++--------------------
 src/modules/__tests__/publish-location.js     |  85 ++++------
 src/modules/__tests__/publish-workflow.js     |   8 +-
 src/modules/details-from-context.js           |  29 +---
 src/modules/post-workflow-details.js          |   4 +-
 ...
gateway · 2026-09-04 03:29:53
result
Found 14 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 172:  * independently-versioned release unit). A workspace inherits the top-level

  Line 175:  * NOTE: this "workspace" (a named, independently-versioned release unit) is a

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

  Line 200:  * Configuration for a single named workspace (release unit).

  Line 202:  * A workspace mirrors the release-relevant subset of the top-level config;

  Line 204:  * `github` block is *partial* (all fields optional) so a workspace can override

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

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

  Line 221:   // preserving an own workspace entry. ...
gateway · 2026-09-04 03:29:57
result
Found 83 matches
/home/byk/Code/getsentry/craft-workspace-action-propagation/src/utils/helpers.ts:
  Line 26:  * `CRAFT_WORKSPACE` env var) before yargs parsing.

  Line 37:  * back to `CRAFT_WORKSPACE`.

  Line 40:  * @param env The environment to read CRAFT_WORKSPACE from (defaults to process.env)

  Line 46:   const envWorkspace = env.CRAFT_WORKSPACE || undefined;

  Line 49:     options: { workspace: { type: 'string' } },

  Line 54:   let workspace: string | undefined;

  Line 60:     // an earlier value selected; it falls back to CRAFT_WORKSPACE instead.


/home/byk/Code/getsentry/craft-workspace-action-propagation/src/utils/__tests__/helpers.test.ts:
  Line 65:   test('CLI flag wins over CRAFT_WORKSPACE env', () => {

  Line 68:         CRAFT_WORKSPACE: 'mcp',

  Line 86:         { CRAFT_WORKSPACE: 'mcp' } as NodeJS.ProcessEnv,

  Line 91:   test('falls back to CRAFT_WORKSPACE when no flag', () => {

  Line 94:         CRAFT_WORKSPACE: 'mcp',

  Line 103:         ...
gateway · 2026-09-04 03:30:00
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/schemas/project_config.ts</path>
<type>file</type>
<content>
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-04 03:30:20
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-04 03:30:24
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/commands/prepare.ts</path>
<type>file</type>
<content>
740: 
741:     const calverFormat =
742:       config.versioning?.calver?.format ?? DEFAULT_CALVER_CONFIG.format;
743: 
744:     return calculateCalVer(git, {
745:       offset: calverOffset,
746:       format: calverFormat,
747:     });
748:   }
749: 
750:   // Handle automatic version detection or version bump types
751:   if (version === 'auto' || isBumpType(version)) {
752:     if (!requiresMinVersion(AUTO_VERSION_MIN_VERSION)) {
753:       const featureName = isBumpType(version)
754:         ? 'Version bump types'
755:         : 'Auto-versioning';
756:       throw new ConfigurationError(
757:         `${featureName} requires minVersion >= ${AUTO_VERSION_MIN_VERSION} in .craft.yml. ` +
758:           'Please update your configuration or specify the version explicitly.',
759:       );
760:     }
761: 
762:     const latestTag = await getLatestTag(git, ...
gateway · 2026-09-04 03:30:31
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-04 03:30:36
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/action.yml</path>
<type>file</type>
<content>
260:         # GitHub issue bodies are limited to ~65 536 characters.  Truncate
261:         # the changelog to stay under that limit with room for the rest of
262:         # the body template (~2 KB of surrounding markdown).
263:         MAX_CHANGELOG_CHARS=60000
264:         if [[ ${#CHANGELOG} -gt $MAX_CHANGELOG_CHARS ]]; then
265:           CHANGELOG="${CHANGELOG:0:$MAX_CHANGELOG_CHARS}"$'\n\n---\n*Changelog truncated for issue body.*'
266:         fi
267: 
268:         title="publish: ${GITHUB_REPOSITORY}${SUBDIRECTORY}${WORKSPACE:+/$WORKSPACE}@${RESOLVED_VERSION}"
269: 
270:         # Check if issue already exists by listing all open issues and filtering by exact title match.
271:         # We avoid GitHub search API to bypass indexing delays and query syntax edge cases. ...
gateway · 2026-09-04 03:30:59
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-04 03:31:05
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-04 03:31: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-04 03:31: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 (!isPublishPath(path)) {
3:     throw new Error("Invalid publish path.");
4:   }
5: 
6:   if (path === ".") {
7:     return { path };
8:   }
9: 
10:   if (!workspaceNames.every(isWorkspaceName)) {
11:     throw new Error(
12:       "Craft workspace discovery returned an invalid workspace list."
13:     );
14:   }
15: 
16:   const workspace = path.slice(2);
17:   if (workspaceNames.includes(workspace)) {
18:     return { path: ".", workspace };
19:   }
20: 
21:   return { path };
22: }
23: 
24: function isPublishPath(path) {
25:   return (
26:     typeof path === "string" &&
27:     (path === "." ||
28:       (/^\.\/[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*$/.test(path) &&
29:         !path.slice(2).split("/").some((segment) => segment === "." || segment === "..")))
30:   );
31: }
32: 
33: ...
gateway · 2026-09-04 03:31:38
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-04 03:31:44
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-04 03:32:07
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-04 03:32:11
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-04 03:32:18
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-04 03:32:22
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/__tests__/config.test.ts</path>
<type>file</type>
<content>
280:     loadWithTargets([
281:       { name: 'github', tagPrefix: 'cli@' },
282:       { name: 'github', id: 'second' },
283:     ]);
284:     // A mixed defined/undefined prefix is still ambiguous.
285:     expect(getGitTagPrefix()).toBe('cli@');
286:     expect(warnSpy).toHaveBeenCalledTimes(1);
287:   });
288: });
289: 
290: describe('workspaces', () => {
291:   let originalCwd: string;
292:   const temporaryDirectories: string[] = [];
293: 
294:   beforeEach(() => {
295:     originalCwd = process.cwd();
296:   });
297: 
298:   afterEach(() => {
299:     process.chdir(originalCwd);
300:     for (const directory of temporaryDirectories.splice(0)) {
301:       rmSync(directory, { recursive: true, force: true });
302:     }
303:     setActiveWorkspace(undefined);
304:     vi.restoreAllMocks();
305:   });
306: 
307:   const WS_CONFIG = [
308:     ...
gateway · 2026-09-04 03:32:28
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/__tests__/publish-location.js</path>
<type>file</type>
<content>
1: import { expect, test } from "vitest";
2: 
3: const { resolvePublishLocation } = require("../publish-location.js");
4: 
5: test("classifies an exact full workspace path without normalizing it", () => {
6:   expect(
7:     resolvePublishLocation({
8:       path: "./packages/CLI",
9:       workspaceNames: ["packages/cli", "packages/CLI"],
10:     })
11:   ).toStrictEqual({ path: ".", workspace: "packages/CLI" });
12: });
13: 
14: test("keeps a non-workspace suffix as a checkout path", () => {
15:   expect(
16:     resolvePublishLocation({
17:       path: "./packages",
18:       workspaceNames: ["cli"],
19:     })
20:   ).toStrictEqual({ path: "./packages" });
21: });
22: 
23: test("keeps a multi-segment suffix that is not an exact workspace path", () => {
24:   expect(
25:     resolvePublishLocation({
26:       path: "./packages/cli",
27:       ...
gateway · 2026-09-04 03:32:32
result
No files found
gateway · 2026-09-04 03:32:36
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-04 03:32:40
result
/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/resolve-ci-poller-input.js
/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/ci-poller-input.js
/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/__tests__/ci-poller-input.js
gateway · 2026-09-04 03:32:44
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/ci-poller-input.js</path>
<type>file</type>
<content>
1: const { parse: parsePublishIssueTitle } = require("./publish-issue-title");
2: const {
3:   getReleaseRevision,
4:   updateReleaseRevision,
5: } = require("./release-revision");
6: 
7: function getCiPollerInput({ title, issueBody, revision }) {
8:   let parsedTitle;
9:   try {
10:     parsedTitle = parsePublishIssueTitle(title);
11:   } catch {
12:     throw new Error(`Invalid publish issue title: '${title}'`);
13:   }
14:   const { repo, version } = parsedTitle;
15:   const currentRevision = getReleaseRevision({ issueBody, repo });
16: 
17:   return {
18:     ...(revision
19:       ? { issueBody: updateReleaseRevision({ issueBody, repo, revision }) }
20:       : {}),
21:     repo: `getsentry/${repo}`,
22:     revision: currentRevision,
23:     version,
24:   };
25: }
26: 
27: module.exports = { getCiPollerInput };

(End of file - total 27 lines)
</content>
gateway · 2026-09-04 03:32:51
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/.github/workflows</path>
<type>directory</type>
<entries>
auto-approve.yml
ci-poller.yml
cocoapods-keepalive.yml
publish.yml
test.yml

(5 entries)
</entries>
gateway · 2026-09-04 03:32:58
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-04 03:33:04
result
<path>/home/byk/Code/getsentry/craft-workspace-action-propagation/src/commands/publish.ts</path>
<type>file</type>
<content>
670:       if (exactMatches.length === 0 && fuzzyMatches.length === 0) {
671:         message += `\n\nNo release branches found on the remote.`;
672:       }
673: 
674:       message += `\n\nOriginal error: ${err instanceof Error ? err.message : String(err)}`;
675: 
676:       throw new ConfigurationError(message);
677:     }
678:   }
679: 
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, ...
gateway · 2026-09-04 03:33:09
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-04 03:33:13
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-04 03:33:32
result
(no output)
gateway · 2026-09-04 03:34:45
result
Found 1 matches
/home/byk/Code/getsentry/craft-workspace-action-propagation/package.json:
  Line 49:     "glob": "^11.0.0",
gateway · 2026-09-04 03:34:54
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-04 03:35:32
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/publish-issue-title.js</path>
<type>file</type>
<content>
430:     if (input.substr(peg$currPos, 9) === peg$c0) {
431:       s1 = peg$c0;
432:       peg$currPos += 9;
433:     } else {
434:       s1 = peg$FAILED;
435:       if (peg$silentFails === 0) {
436:         peg$fail(peg$e0);
437:       }
438:     }
439:     if (s1 !== peg$FAILED) {
440:       if (input.substr(peg$currPos, 10) === peg$c1) {
441:         s2 = peg$c1;
442:         peg$currPos += 10;
443:       } else {
444:         s2 = peg$FAILED;
445:         if (peg$silentFails === 0) {
446:           peg$fail(peg$e1);
447:         }
448:       }
449:       if (s2 === peg$FAILED) {
450:         s2 = null;
451:       }
452:       s3 = peg$parseRepository();
453:       if (s3 !== peg$FAILED) {
454:         s4 = peg$parsePath();
455:         if (s4 === peg$FAILED) {
456:           s4 = null;
457:         }
458:         if (input.charCodeAt(peg$currPos) === ...
gateway · 2026-09-04 03:35:36
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/publish-issue-title.js</path>
<type>file</type>
<content>
190: 
191: function peg$parse(input, options) {
192:   options = options !== undefined ? options : {};
193: 
194:   const peg$FAILED = {};
195:   const peg$source = options.grammarSource;
196: 
197:   const peg$startRuleFunctions = {
198:     PublishIssueTitle: peg$parsePublishIssueTitle,
199:   };
200:   let peg$startRuleFunction = peg$parsePublishIssueTitle;
201: 
202:   const peg$c0 = "publish: ";
203:   const peg$c1 = "getsentry/";
204:   const peg$c2 = "@";
205:   const peg$c3 = "/";
206: 
207:   const peg$r0 = /^[A-Za-z0-9_.\-]/;
208:   const peg$r1 = /^[A-Za-z0-9_.+\-]/;
209: 
210:   const peg$e0 = peg$literalExpectation("publish: ", false);
211:   const peg$e1 = peg$literalExpectation("getsentry/", false);
212:   const peg$e2 = peg$literalExpectation("@", false);
213:   const peg$e3 = peg$anyExpectation();
214:   const peg$e4 = ...
gateway · 2026-09-04 03:35:41
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-04 03:36:11
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-04 03:37:40

Findings

MAJOR — glob validation can escape the repository before filtering

isSafeWorkspacePath() validates the unexpanded pattern, then globSync() evaluates it. Brace alternatives can hide absolute or traversal segments, such as {../outside/*,packages/*} or {/etc/*,packages/*}.

  • src/config.ts:209-210 classifies brace patterns as globs.
  • src/config.ts:235-250 calls globSync() before filtering matches.
  • src/config.ts:257-270 only checks raw slash-separated text.

This violates the required pre-filesystem containment check. Validate every expanded alternative or reject unsafe glob syntax before calling globSync().

MAJOR — removes required legacy publish-title compatibility

Existing JSON-qualified workspace titles now fail parsing, leaving accepted/CI-ready requests unable to publish.

  • src/modules/publish-issue-title.peggy:10-16 removes LegacyWorkspace.
  • Generated parser also has no legacy branch: src/modules/publish-issue-title.js:228-234,452-490.
  • Input parsing fails before checkout: src/modules/details-from-context.js:26-33.
  • Documentation removes the compatibility contract: docs/publish-issue-format.md:11-20.

Keep parsing legacy titles while emitting only the new concrete-path form.

MAJOR — discovered workspace validation does not enforce segment safety

Craft permits unsafe literal workspace keys because schema validation only rejects whole-string special names; discovery preserves literal keys unchanged. Publish then accepts nested __proto__, . segments, and leading-dash segments that the action rejects.

  • Craft schema only rejects bare special values: src/schemas/project_config.ts:218-227.
  • Literal keys bypass path validation: src/config.ts:208-215.
  • Publish accepts packages/__proto__/cli, packages/./cli, and packages/-cli: src/modules/publish-location.js:33-41.
  • The action correctly rejects these per segment: action.yml:84-86. ...
gpt-5.6-terra · 2026-09-04 03:37:40 · ~995 tokens