DashboardcliSession 14GgsRiWwjbI

Session 14GgsRiWwjbI

Full ID: 14GgsRiWwjbIayfJt
Messages: 63
Distillations: 1
Time range: 2026-09-10 10:51:05 — 2026-09-10 11:22:35

Conversation (63 messages)

You are the replacement READ-ONLY security and supply-chain merge-gate reviewer for getsentry/cli PR #1569. The prior reviewer returned empty twice. A substantive evidence-based response is mandatory. Repository: /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade. Review exact immutable base ec83887a16f780f32fba4b7d710bad262dba3a22 and exact immutable head 9525f72c70fabbb23ec21d13cf86add58f6287cc using Git objects only. Never inspect or modify the worktree, mutate VCS, or write files. Verify merge-base/base ancestry, base/head trees, changed paths, full diff, and git diff --check. Inspect every changed source and test object. Adversarially verify: Toolkit-first fallback advances only on genuine typed HTTP 404; non-404 HTTP, transport, TLS, malformed success never fall through; successful source sticks through stable/nightly exact/latest lookup, token scope, manifests, blobs, delta, prefetch, changelog and setup; GitHub pagination reconstructs trusted selected-repo URLs and detects cycles; response-body cancellation preserves exact arbitrary signal.reason, body termination remains UpgradeTransportError, malformed complete JSON is terminal metadata; npm latest JSON has strict runtime shape and stable canonical SemVer; pinned npm/pnpm/bun/yarn 404 vs 401/403/429/500 classification uses one request and never launches subprocess on failure; Toolkit accepts only cli@VERSION while legacy supports vVERSION; malformed/prerelease standalone pins and draft/prerelease exact release metadata fail before download/setup; GHCR tokens are trimmed non-empty strings; OCI manifests/layers/digests/sizes/annotations and tag-list bodies validate at runtime; annotation arrays fail; tag pagination detects repeated cursor; bearer auth never reaches redirect; nightly annotation binds exact target; artifact channel and tracking channel remain separate; offline behavior and transport-only cache fallback; delta application final SHA ...

gateway · 2026-09-10 10:51:05
result
/home/byk/Code/getsentry/cli/.git/worktrees/toolkit-bridge-upgrade
commit
commit
BASE commit=ec83887a16f780f32fba4b7d710bad262dba3a22 tree=110d6530e57ca4fbfb2a3f98e52013d2668b5b5e parents=e7beceb6ba09b64dc277ae1732df04e65500062b
BASE subject=fix(event): Prevent immediate retry of rate-limited org in event view fallback (#1566)
HEAD commit=9525f72c70fabbb23ec21d13cf86add58f6287cc tree=22f49363a13e32c36dcd9e1ca95916809c2dc271 parents=a84012184c79c2566c7466aa6beda5f767199218
HEAD subject=fix(cli): harden upgrade metadata validation
merge-base=ec83887a16f780f32fba4b7d710bad262dba3a22
base-is-ancestor=yes
gateway · 2026-09-10 10:51:40
result
CHANGED PATHS
M	packages/cli/src/commands/cli/upgrade.ts
M	packages/cli/src/lib/binary.ts
M	packages/cli/src/lib/delta-upgrade.ts
M	packages/cli/src/lib/errors.ts
M	packages/cli/src/lib/ghcr.ts
M	packages/cli/src/lib/release-notes.ts
M	packages/cli/src/lib/upgrade.ts
M	packages/cli/src/lib/version-check.ts
M	packages/cli/test/commands/cli.test.ts
M	packages/cli/test/commands/cli/upgrade.test.ts
M	packages/cli/test/lib/binary.test.ts
M	packages/cli/test/lib/delta-upgrade.mocked.test.ts
M	packages/cli/test/lib/delta-upgrade.test.ts
M	packages/cli/test/lib/ghcr.test.ts
M	packages/cli/test/lib/release-notes.test.ts
M	packages/cli/test/lib/upgrade.test.ts
M	packages/cli/test/lib/version-check.test.ts
DIFF STAT
 packages/cli/src/commands/cli/upgrade.ts           | 180 +++--
 packages/cli/src/lib/binary.ts                     | 191 +++++-
 packages/cli/src/lib/delta-upgrade.ts              | 170 ++++-
 packages/cli/src/lib/errors.ts                     |   8 +
 packages/cli/src/lib/ghcr.ts   ...
gateway · 2026-09-10 10:52:11
result
diff --git a/packages/cli/src/commands/cli/upgrade.ts b/packages/cli/src/commands/cli/upgrade.ts
index 8d8282368..b3039d86b 100644
--- a/packages/cli/src/commands/cli/upgrade.ts
+++ b/packages/cli/src/commands/cli/upgrade.ts
@@ -22,9 +22,11 @@ import type { SentryContext } from "../../context.js";
 import {
   determineInstallDir,
   isDowngrade,
+  isNightlyVersion,
   LEGACY_INSTALL_SUBDIR,
   releaseLock,
   samePath,
+  type UpgradeSource,
 } from "../../lib/binary.js";
 import { buildCommand } from "../../lib/command.js";
 import { CLI_VERSION } from "../../lib/constants.js";
@@ -34,7 +36,7 @@ import {
   setReleaseChannel,
 } from "../../lib/db/release-channel.js";
 import { getVersionCheckInfo } from "../../lib/db/version-check.js";
-import { UpgradeError } from "../../lib/errors.js";
+import { UpgradeError, UpgradeTransportError } from "../../lib/errors.js";
 import { formatUpgradeResult } from "../../lib/formatters/human.js";
 import { formatBytes } from ...
gateway · 2026-09-10 10:52:23
result
diff --git a/packages/cli/src/lib/binary.ts b/packages/cli/src/lib/binary.ts
index 75582612a..e64f5059e 100644
--- a/packages/cli/src/lib/binary.ts
+++ b/packages/cli/src/lib/binary.ts
@@ -23,7 +23,11 @@ import {
   customFetch,
   isTlsCertError,
 } from "./custom-ca.js";
-import { stringifyUnknown, UpgradeError } from "./errors.js";
+import {
+  stringifyUnknown,
+  UpgradeError,
+  UpgradeTransportError,
+} from "./errors.js";
 import { logger } from "./logger.js";
 import { isProcessRunning } from "./process-utils.js";
 /** Known directories where the curl installer may place the binary */
@@ -102,6 +106,33 @@ export type InstallationMethod =
   | "yarn"
   | "unknown";
 
+/** A repository pair that hosts CLI stable releases and nightly OCI images. */
+export type UpgradeSource = {
+  /** GitHub `owner/repository` containing CLI release assets. */
+  readonly githubRepo: string;
+  /** GHCR `owner/package` containing CLI nightly images and delta patches. ...
gateway · 2026-09-10 10:52:32
result
diff --git a/packages/cli/src/lib/delta-upgrade.ts b/packages/cli/src/lib/delta-upgrade.ts
index ec709b8c8..204875dce 100644
--- a/packages/cli/src/lib/delta-upgrade.ts
+++ b/packages/cli/src/lib/delta-upgrade.ts
@@ -30,18 +30,20 @@ import {
   type SourceStrategy,
   type StableChainInfo,
 } from "binpatch";
+import { prerelease as semverPrerelease, valid as semverValid } from "semver";
 import {
   compareVersions,
-  GITHUB_RELEASES_URL,
+  getGitHubReleasesUrl,
   getPlatformBinaryName,
   isDowngrade,
   isNightlyVersion,
+  PRIMARY_UPGRADE_SOURCE,
+  type UpgradeSource,
 } from "./binary.js";
 import { CLI_VERSION } from "./constants.js";
 import { customFetch } from "./custom-ca.js";
 import { getConfigDir } from "./db/index.js";
 import { formatBytes } from "./formatters/numbers.js";
-import { GHCR_REPO } from "./ghcr.js";
 import { logger } from "./logger.js";
 import { makeByteProgress, type SetMessage } from "./progress.js";
 import { withTracing, withTracingSpan } from ...
gateway · 2026-09-10 10:52:40
result
diff --git a/packages/cli/src/lib/errors.ts b/packages/cli/src/lib/errors.ts
index f81b8c70c..b21505cd1 100644
--- a/packages/cli/src/lib/errors.ts
+++ b/packages/cli/src/lib/errors.ts
@@ -618,6 +618,14 @@ export class UpgradeError extends CliError {
   }
 }
 
+/** Upgrade failure caused by transport rather than an HTTP or metadata error. */
+export class UpgradeTransportError extends UpgradeError {
+  constructor(message: string) {
+    super("network_error", message);
+    this.name = "UpgradeTransportError";
+  }
+}
+
 // Seer Errors
 
 export type SeerErrorReason = "not_enabled" | "no_budget" | "ai_disabled";
gateway · 2026-09-10 10:52:52
result
diff --git a/packages/cli/src/lib/ghcr.ts b/packages/cli/src/lib/ghcr.ts
index 43d6deb7e..5bca7c061 100644
--- a/packages/cli/src/lib/ghcr.ts
+++ b/packages/cli/src/lib/ghcr.ts
@@ -17,9 +17,15 @@
  *   without the auth header.
  */
 
+import { valid as semverValid } from "semver";
+import {
+  PRIMARY_UPGRADE_SOURCE,
+  parseUpgradeJson,
+  type UpgradeSource,
+} from "./binary.js";
 import { getUserAgent } from "./constants.js";
 import { customFetch } from "./custom-ca.js";
-import { UpgradeError } from "./errors.js";
+import { UpgradeError, UpgradeTransportError } from "./errors.js";
 
 /** Default timeout for GHCR HTTP requests (10 seconds) */
 const GHCR_REQUEST_TIMEOUT = 10_000;
@@ -27,6 +33,9 @@ const GHCR_REQUEST_TIMEOUT = 10_000;
 /** Maximum number of retry attempts for transient failures */
 const GHCR_MAX_RETRIES = 1;
 
+/** Nightly versions use a numeric build timestamp as the prerelease value. */
+const NIGHTLY_VERSION_REGEX = /^\d+\.\d+\.\d+-dev\.\d+$/;
+
 /** Timeout ...
gateway · 2026-09-10 10:53:00
result
diff --git a/packages/cli/src/lib/release-notes.ts b/packages/cli/src/lib/release-notes.ts
index ddf617535..903ca51f6 100644
--- a/packages/cli/src/lib/release-notes.ts
+++ b/packages/cli/src/lib/release-notes.ts
@@ -15,11 +15,18 @@
 import { marked, type Token, type Tokens } from "marked";
 import {
   compareVersions,
-  GITHUB_RELEASES_URL,
   getGitHubHeaders,
+  getGitHubReleasesUrl,
+  PRIMARY_UPGRADE_SOURCE,
+  type UpgradeSource,
 } from "./binary.js";
 import { customFetch } from "./custom-ca.js";
-import type { GitHubRelease } from "./delta-upgrade.js";
+import {
+  type GitHubRelease,
+  isNormalizedForSource,
+  type NormalizedGitHubReleases,
+  normalizeStableReleases,
+} from "./delta-upgrade.js";
 import { logger } from "./logger.js";
 
 const log = logger.withTag("release-notes");
@@ -413,27 +420,34 @@ function mergeSectionsByCategory(releases: GitHubRelease[]): ChangeSection[] {
   return merged;
 }
 
-/**
- * Build a changelog summary from a list of GitHub releases. ...
gateway · 2026-09-10 10:53:08
result
diff --git a/packages/cli/src/lib/upgrade.ts b/packages/cli/src/lib/upgrade.ts
index 945adc48d..b29d67ff2 100644
--- a/packages/cli/src/lib/upgrade.ts
+++ b/packages/cli/src/lib/upgrade.ts
@@ -21,35 +21,47 @@ import { writeFile } from "node:fs/promises";
 import { homedir } from "node:os";
 import { dirname, isAbsolute, join, sep } from "node:path";
 import { setTimeout } from "node:timers/promises";
+import { prerelease as semverPrerelease, valid as semverValid } from "semver";
 import {
   acquireLock,
   cleanupOldBinary,
+  compareVersions,
   determineInstallDir,
   fetchWithUpgradeError,
-  GITHUB_RELEASES_URL,
   getBinaryDownloadUrl,
   getBinaryFilename,
   getBinaryPaths,
   getGitHubHeaders,
+  getGitHubLatestReleaseUrl,
+  getGitHubReleaseByTagUrl,
+  getGitHubRepositoryUrl,
   getPlatformBinaryName,
   type InstallationMethod,
   isNightlyVersion,
   KNOWN_CURL_DIRS,
+  PRIMARY_UPGRADE_SOURCE,
+  parseUpgradeJson,
   releaseLock,
+  resolveUpgradeSource,
+  ...
gateway · 2026-09-10 10:53:16
result
diff --git a/packages/cli/src/lib/version-check.ts b/packages/cli/src/lib/version-check.ts
index c69ff0466..34393b1c7 100644
--- a/packages/cli/src/lib/version-check.ts
+++ b/packages/cli/src/lib/version-check.ts
@@ -10,6 +10,7 @@
 // biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import
 import * as Sentry from "@sentry/node-core/light";
 import { compare as semverCompare } from "semver";
+import type { UpgradeSource } from "./binary.js";
 import { CLI_VERSION } from "./constants.js";
 import { getReleaseChannel } from "./db/release-channel.js";
 import {
@@ -27,7 +28,10 @@ import { cyan, muted } from "./formatters/colors.js";
 import { GLOBAL_FLAGS } from "./global-flags.js";
 import { logger } from "./logger.js";
 import { cleanupPatchCache } from "./patch-cache.js";
-import { fetchLatestFromGitHub, fetchLatestNightlyVersion } from "./upgrade.js";
+import {
+  fetchLatestFromGitHubWithSource,
+  fetchLatestNightlyVersionWithSource,
+} from ...
gateway · 2026-09-10 10:53:27
result
diff --git a/packages/cli/test/commands/cli.test.ts b/packages/cli/test/commands/cli.test.ts
index cd1514eb0..4eec11e9d 100644
--- a/packages/cli/test/commands/cli.test.ts
+++ b/packages/cli/test/commands/cli.test.ts
@@ -124,7 +124,7 @@ describe("upgradeCommand.func", () => {
 
   test("shows installation info with specified method", async () => {
     globalThis.fetch = (async () =>
-      new Response(JSON.stringify({ tag_name: "v0.0.0-dev" }), {
+      new Response(JSON.stringify([{ tag_name: "cli@1.0.0" }]), {
         status: 200,
         headers: { "Content-Type": "application/json" },
       })) as typeof fetch;
@@ -135,18 +135,18 @@ describe("upgradeCommand.func", () => {
 
     // Use method flag to bypass detection (curl uses GitHub).
     // Pass json: true so the output config renders structured JSON to stdout.
-    await func.call(context, { check: false, method: "curl", json: true });
+    await func.call(context, { check: true, method: "curl", json: true });
 
     // ...
gateway · 2026-09-10 10:53:38
result
diff --git a/packages/cli/test/commands/cli/upgrade.test.ts b/packages/cli/test/commands/cli/upgrade.test.ts
index ca9688e46..b7fead58f 100644
--- a/packages/cli/test/commands/cli/upgrade.test.ts
+++ b/packages/cli/test/commands/cli/upgrade.test.ts
@@ -40,6 +40,7 @@ import {
   getReleaseChannel,
   setReleaseChannel,
 } from "../../../src/lib/db/release-channel.js";
+import { setVersionCheckInfo } from "../../../src/lib/db/version-check.js";
 import { TEST_TMP_DIR, useTestConfigDir } from "../../helpers.js";
 
 /** Store original fetch for restoration */
@@ -172,6 +173,10 @@ function mockGhcrNightlyVersion(version: string): void {
   mockFetch(async (url) => {
     const urlStr = String(url);
 
+    if (urlStr === "https://api.github.com/repos/getsentry/toolkit") {
+      return new Response(null, { status: 200 });
+    }
+
     // GHCR anonymous token exchange
     if (urlStr.includes("ghcr.io/token")) {
       return new Response(JSON.stringify({ token: "test-token" }), {
@@ ...
gateway · 2026-09-10 10:53:48
result
diff --git a/packages/cli/test/lib/binary.test.ts b/packages/cli/test/lib/binary.test.ts
index 6fcfb3b1a..29ead3828 100644
--- a/packages/cli/test/lib/binary.test.ts
+++ b/packages/cli/test/lib/binary.test.ts
@@ -25,14 +25,19 @@ import {
   getBinaryDownloadUrl,
   getBinaryFilename,
   getBinaryPaths,
+  getGitHubReleaseByTagUrl,
   getLegacyInstallDirs,
   getPlatformBinaryName,
   installBinary,
   isDowngrade,
   isMusl,
+  parseUpgradeJson,
   releaseLock,
   replaceBinarySync,
+  resolveUpgradeSource,
   samePath,
+  UPGRADE_SOURCES,
+  UpgradeSourceNotFoundError,
 } from "../../src/lib/binary.js";
 import { UpgradeError } from "../../src/lib/errors.js";
 
@@ -40,9 +45,9 @@ describe("getBinaryDownloadUrl", () => {
   test("builds correct URL for current platform", () => {
     const url = getBinaryDownloadUrl("1.0.0");
 
-    expect(url).toContain("/1.0.0/");
+    expect(url).toContain("/cli@1.0.0/");
     expect(url).toStartWith(
-      ...
gateway · 2026-09-10 10:53:58
result
diff --git a/packages/cli/test/lib/delta-upgrade.mocked.test.ts b/packages/cli/test/lib/delta-upgrade.mocked.test.ts
index cd0a2fddc..f6186ab59 100644
--- a/packages/cli/test/lib/delta-upgrade.mocked.test.ts
+++ b/packages/cli/test/lib/delta-upgrade.mocked.test.ts
@@ -99,10 +99,10 @@ describe("resolveStableDelta", () => {
     // Set up fetch mocks — releases API + patch download
     // Since applyPatch will fail (we don't have a real TRDIFF10 matching this binary),
     // we expect resolveStableDelta to throw, but the chain resolution should succeed
-    const patchUrl = `https://github.com/getsentry/cli/releases/download/0.14.0/${BINARY_NAME}.patch`;
+    const patchUrl = `https://github.com/getsentry/toolkit/releases/download/cli@0.14.0/${BINARY_NAME}.patch`;
     const releases = [
       {
-        tag_name: "0.14.0",
+        tag_name: "cli@0.14.0",
         assets: [
           {
             name: BINARY_NAME,
@@ -123,7 +123,7 @@ describe("resolveStableDelta", () => {
       ...
gateway · 2026-09-10 10:54:13
result
diff --git a/packages/cli/test/lib/delta-upgrade.test.ts b/packages/cli/test/lib/delta-upgrade.test.ts
index 3752d4ae6..68d1cfbdf 100644
--- a/packages/cli/test/lib/delta-upgrade.test.ts
+++ b/packages/cli/test/lib/delta-upgrade.test.ts
@@ -11,8 +11,11 @@ import { existsSync, unlinkSync } from "node:fs";
 import { access, readFile } from "node:fs/promises";
 import { tmpdir } from "node:os";
 import { join } from "node:path";
-import { afterEach, beforeEach, describe, expect, test } from "vitest";
-import { getPlatformBinaryName } from "../../src/lib/binary.js";
+import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
+import {
+  getPlatformBinaryName,
+  UPGRADE_SOURCES,
+} from "../../src/lib/binary.js";
 import {
   applyPatchChain,
   attemptDeltaUpgrade,
@@ -38,6 +41,12 @@ import {
   validateChainStep,
 } from "../../src/lib/delta-upgrade.js";
 import type { OciManifest } from "../../src/lib/ghcr.js";
+import { useTestConfigDir } from ...
gateway · 2026-09-10 10:54:30
result
diff --git a/packages/cli/test/lib/ghcr.test.ts b/packages/cli/test/lib/ghcr.test.ts
index e5c81d532..a55612b5a 100644
--- a/packages/cli/test/lib/ghcr.test.ts
+++ b/packages/cli/test/lib/ghcr.test.ts
@@ -6,6 +6,7 @@
  */
 
 import { afterEach, beforeEach, describe, expect, test } from "vitest";
+import { UPGRADE_SOURCES } from "../../src/lib/binary.js";
 import { UpgradeError } from "../../src/lib/errors.js";
 import {
   downloadLayerBlob,
@@ -15,6 +16,7 @@ import {
   findLayerByFilename,
   GHCR_REPO,
   GHCR_TAG,
+  GhcrManifestHttpError,
   getAnonymousToken,
   getNightlyVersion,
   listTags,
@@ -37,13 +39,13 @@ function makeManifest(overrides: Partial<OciManifest> = {}): OciManifest {
     schemaVersion: 2,
     mediaType: "application/vnd.oci.image.manifest.v1+json",
     config: {
-      digest: "sha256:config",
+      digest: `sha256:${"0".repeat(64)}`,
       mediaType: "application/vnd.oci.empty.v1+json",
       size: 2,
     },
     layers: [
       {
-        digest: ...
gateway · 2026-09-10 10:54:40
result
diff --git a/packages/cli/test/lib/release-notes.test.ts b/packages/cli/test/lib/release-notes.test.ts
index 78963b471..ffccf4901 100644
--- a/packages/cli/test/lib/release-notes.test.ts
+++ b/packages/cli/test/lib/release-notes.test.ts
@@ -9,16 +9,22 @@
  */
 
 import { marked } from "marked";
-import { describe, expect, test } from "vitest";
-import type { GitHubRelease } from "../../src/lib/delta-upgrade.js";
+import { afterEach, beforeEach, describe, expect, test } from "vitest";
+import { UPGRADE_SOURCES } from "../../src/lib/binary.js";
+import {
+  fetchRecentReleases,
+  type GitHubRelease,
+} from "../../src/lib/delta-upgrade.js";
 import {
   buildChangelogSummary,
   type ChangeCategory,
   countListItems,
   extractNightlyTimestamp,
   extractSections,
+  fetchChangelog,
   parseCommitMessages,
 } from "../../src/lib/release-notes.js";
+import { mockFetch } from "../helpers.js";
 
 // ─────────────────────────── Fixtures ──────────────────────────────────────
 
@@ -293,3 ...
gateway · 2026-09-10 10:54:53
result
diff --git a/packages/cli/test/lib/upgrade.test.ts b/packages/cli/test/lib/upgrade.test.ts
index 7c6f63a4b..d10f21dd6 100644
--- a/packages/cli/test/lib/upgrade.test.ts
+++ b/packages/cli/test/lib/upgrade.test.ts
@@ -120,6 +120,7 @@ import {
   getBinaryDownloadUrl,
   isNightlyVersion,
   releaseLock,
+  UPGRADE_SOURCES,
 } from "../../src/lib/binary.js";
 import {
   clearInstallInfo,
@@ -140,6 +141,7 @@ const {
   fetchLatestVersion,
   getCurlInstallPaths,
   parseInstallationMethod,
+  resolveExistingUpgradeVersion,
   startCleanupOldBinary,
   versionExists,
 } = await import("../../src/lib/upgrade.js");
@@ -188,37 +190,234 @@ describe("parseInstallationMethod", () => {
 });
 
 describe("fetchLatestFromGitHub", () => {
-  test("returns version from GitHub API", async () => {
-    mockFetch(
-      async () =>
-        new Response(
-          JSON.stringify({
-            tag_name: "v1.2.3",
-          }),
+  test("selects the latest CLI-prefixed Toolkit release", async () => ...
gateway · 2026-09-10 10:55:08
result
diff --git a/packages/cli/test/lib/version-check.test.ts b/packages/cli/test/lib/version-check.test.ts
index 20172cd83..dd047a5cb 100644
--- a/packages/cli/test/lib/version-check.test.ts
+++ b/packages/cli/test/lib/version-check.test.ts
@@ -3,12 +3,15 @@
  */
 
 import { setTimeout as sleep } from "node:timers/promises";
-import { afterEach, beforeEach, describe, expect, test } from "vitest";
+import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
+import { UPGRADE_SOURCES } from "../../src/lib/binary.js";
 import { setReleaseChannel } from "../../src/lib/db/release-channel.js";
 import {
   getVersionCheckInfo,
   setVersionCheckInfo,
 } from "../../src/lib/db/version-check.js";
+// biome-ignore lint/performance/noNamespaceImport: Vitest requires the module namespace to spy on an ESM export
+import * as deltaUpgrade from "../../src/lib/delta-upgrade.js";
 import {
   ApiError,
   ContextError,
@@ -440,6 +443,7 @@ describe("maybeCheckForUpdateInBackground", () => ...
gateway · 2026-09-10 10:55:38
result
COMMITS BASE..HEAD
04867eaf9b412a62d4b1ea1c59c2b382c557bd0b cc356b77799633918710e0c8d358034894f86147 ec83887a16f780f32fba4b7d710bad262dba3a22 feat(cli): add toolkit upgrade bridge
9c874298e3b57515ba36d35140627a668a46dc7b 4dd2c6153592d15804b1aaef8da9015e0df0345b 04867eaf9b412a62d4b1ea1c59c2b382c557bd0b fix(cli): address toolkit bridge review
723375b6ce4e37884e760fa0f848e7e7e3effcc9 c0c0ace97578535769b33929fb2494113eec3e5d 9c874298e3b57515ba36d35140627a668a46dc7b fix(cli): address upgrade review findings
dbee7e9f2a4555e044f74dfebad002cccfa43a1f 024dbc7afa15d0632eb4c15ed6c808dab974a9c5 723375b6ce4e37884e760fa0f848e7e7e3effcc9 test(cli): update toolkit upgrade fixtures
5ffa5cd0fe02c0a5dc5bec9a8a099f4135c0b447 a1cbad382fe2fec2c40f31bd64eaf1898b779428 dbee7e9f2a4555e044f74dfebad002cccfa43a1f fix(cli): harden upgrade source resolution
c8c815bbecf472061ec02ab96d1a31f23f078d01 ecd342742030111dd72ce73739016ac3b803d521 5ffa5cd0fe02c0a5dc5bec9a8a099f4135c0b447 fix(cli): validate GitHub release ...
gateway · 2026-09-10 10:56:00
result
  1114 lines   37246 bytes b3039d86b5935a806f265c906460686d83ee2c64 packages/cli/src/commands/cli/upgrade.ts
   768 lines   25572 bytes e64f5059e53d1321c3910ba46ead18391343348f packages/cli/src/lib/binary.ts
   725 lines   21916 bytes 204875dce4325434ff6c4b7b0fcc4d2abb9bbd50 packages/cli/src/lib/delta-upgrade.ts
  1012 lines   34040 bytes b21505cd11bdfddf7c51f0b89d942cc850ea29fe packages/cli/src/lib/errors.ts
   659 lines   20203 bytes 5bca7c0615a2cdd1ee956e88259d4d046e3bc97b packages/cli/src/lib/ghcr.ts
   793 lines   25097 bytes 903ca51f67706a30a1e203ff1df3b288018ed3d5 packages/cli/src/lib/release-notes.ts
  1502 lines   48462 bytes b29d67ff2da3f1ba4940ec971aa9b11f41ed390f packages/cli/src/lib/upgrade.ts
   501 lines   16463 bytes 34393b1c777e09c0a1ab683a493a6f15d91811c3 packages/cli/src/lib/version-check.ts
   230 lines    7592 bytes 4eec11e9d8fa20ef1c55738a82df0a7f3bbad6bd packages/cli/test/commands/cli.test.ts
  1584 lines   50894 bytes b7fead58fbef43936ca22d8e4901000961ce740d ...
gateway · 2026-09-10 10:56:17
result
     1	/**
     2	 * sentry cli upgrade
     3	 *
     4	 * Self-update the Sentry CLI to the latest or a specific version.
     5	 * After upgrading, spawns the NEW binary with `cli setup` to update
     6	 * completions, agent skills, and record installation metadata.
     7	 *
     8	 * Supports two release channels:
     9	 * - stable (default): tracks the latest GitHub release
    10	 * - nightly: tracks the rolling nightly prerelease built from main
    11	 *
    12	 * The channel can be set via --channel or by passing "nightly"/"stable"
    13	 * as the version argument. The choice is persisted in the local database
    14	 * so that subsequent bare `sentry cli upgrade` calls use the same channel.
    15	 */
    16	
    17	import { spawn } from "node:child_process";
    18	import { homedir } from "node:os";
    19	import { dirname, join } from "node:path";
    20	import { setTimeout } from "node:timers/promises";
    21	import type { SentryContext } from "../../context.js";
    ...
gateway · 2026-09-10 10:56:27
result
   401	    currentVersion: CLI_VERSION,
   402	    targetVersion: target,
   403	    channel,
   404	    method,
   405	    forced: flags.force,
   406	  };
   407	
   408	  // When already on target, no update hint needed
   409	  if (CLI_VERSION !== target) {
   410	    const cmd =
   411	      versionArg && !CHANNEL_VERSIONS.has(versionArg)
   412	        ? `sentry cli upgrade ${target}`
   413	        : "sentry cli upgrade";
   414	    result.warnings = [`Run '${cmd}' to update.`];
   415	  }
   416	
   417	  return result;
   418	}
   419	
   420	/**
   421	 * Maximum number of spawn attempts for the new binary.
   422	 *
   423	 * On Windows, Defender/SmartScreen may lock a newly-written executable for
   424	 * antivirus scanning after the file handle is closed. This causes EBUSY from
   425	 * uv_spawn. Retrying with backoff lets the scan complete without a fixed sleep. ...
gateway · 2026-09-10 10:56:40
result
   801	  try {
   802	    await runSetupOnNewBinary({
   803	      binaryPath: downloadResult.tempBinaryPath,
   804	      method: "curl",
   805	      channel,
   806	      install: true,
   807	      installDir,
   808	      ensureAuthScopes: !json,
   809	      noAgentSkills,
   810	    });
   811	  } finally {
   812	    releaseLock(downloadResult.lockPath);
   813	  }
   814	
   815	  // Build warnings about the potentially shadowing old installation.
   816	  // Note: install info is already recorded by the child `setup --install`
   817	  // process, so no redundant setInstallInfo call is needed here.
   818	  const uninstallHints: Record<string, string> = {
   819	    npm: "npm uninstall -g sentry",
   820	    pnpm: "pnpm remove -g sentry",
   821	    bun: "bun remove -g sentry",
   822	    yarn: "yarn global remove sentry",
   823	    brew: "brew uninstall getsentry/tools/sentry",
   824	  };
   825	  const warnings: string[] = [];
   826	  warnings.push(
   827	    `Your ...
gateway · 2026-09-10 10:56:54
result
     1	/**
     2	 * Binary Management
     3	 *
     4	 * Shared utilities for installing, replacing, and managing the CLI binary.
     5	 * Used by both `setup --install` (fresh installs) and `upgrade` (self-updates).
     6	 */
     7	
     8	import { spawnSync } from "node:child_process";
     9	import {
    10	  existsSync,
    11	  mkdirSync,
    12	  readFileSync,
    13	  renameSync,
    14	  unlinkSync,
    15	  writeFileSync,
    16	} from "node:fs";
    17	import { chmod, copyFile, mkdir, realpath, unlink } from "node:fs/promises";
    18	import { delimiter, dirname, isAbsolute, join, resolve, sep } from "node:path";
    19	import { compare as semverCompare } from "semver";
    20	import { getUserAgent } from "./constants.js";
    21	import {
    22	  buildTlsErrorDetail,
    23	  customFetch,
    24	  isTlsCertError,
    25	} from "./custom-ca.js";
    26	import {
    27	  stringifyUnknown,
    28	  UpgradeError,
    29	  UpgradeTransportError,
    30	} from ...
gateway · 2026-09-10 10:57:03
result
   401	 * @returns "sentry.exe" on Windows, "sentry" elsewhere
   402	 */
   403	export function getBinaryFilename(): string {
   404	  return process.platform === "win32" ? "sentry.exe" : "sentry";
   405	}
   406	
   407	/**
   408	 * Build paths object from an install path.
   409	 * Returns the install path and derived sibling paths used during
   410	 * download, replacement, and locking.
   411	 *
   412	 * @param installPath - Absolute path to the binary
   413	 * @returns Object with install, temp (.download), old (.old), and lock (.lock) paths
   414	 */
   415	export function getBinaryPaths(installPath: string): {
   416	  installPath: string;
   417	  tempPath: string;
   418	  oldPath: string;
   419	  lockPath: string;
   420	} {
   421	  return {
   422	    installPath,
   423	    tempPath: `${installPath}.download`,
   424	    oldPath: `${installPath}.old`,
   425	    lockPath: `${installPath}.lock`,
   426	  };
   427	}
   428	
   429	/**
   430	 * Determine the ...
gateway · 2026-09-10 10:57:20
result
     1	/** Delta upgrade discovery and application backed by binpatch. */
     2	
     3	import { join } from "node:path";
     4	// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import
     5	import * as Sentry from "@sentry/node-core/light";
     6	import {
     7	  applyPatchChainInMemory,
     8	  extractStableChain as binpatchExtractStableChain,
     9	  filterAndSortChainTags as binpatchFilterAndSortChainTags,
    10	  validateChainStep as binpatchValidateChainStep,
    11	  type DeltaTelemetry,
    12	  type DeltaUnavailableReason,
    13	  type ExtractStableChainOpts,
    14	  type GitHubRelease,
    15	  getPatchFromVersion,
    16	  getPatchTargetSha256,
    17	  ghcrSource,
    18	  githubReleaseSource,
    19	  type InstrumentHook,
    20	  MAX_NIGHTLY_CHAIN_DEPTH,
    21	  makeCache,
    22	  OciClient,
    23	  type OciManifest,
    24	  PATCH_TAG_PREFIX,
    25	  type PatchCache,
    26	  type PatchChain,
    27	  type ...
gateway · 2026-09-10 10:57:32
result
   381	    repo: source.ghcrRepo,
   382	    userAgent: `sentry-cli/${CLI_VERSION}`,
   383	    fetch: customFetch,
   384	  });
   385	  const tags =
   386	    opts.preloadedTags ??
   387	    (await client.listTags(opts.token, PATCH_TAG_PREFIX, opts.signal));
   388	  const chainTags = filterAndSortChainTags(
   389	    tags,
   390	    opts.currentVersion,
   391	    opts.targetVersion
   392	  );
   393	  if (chainTags.length === 0 || chainTags.length > MAX_NIGHTLY_CHAIN_DEPTH) {
   394	    return null;
   395	  }
   396	
   397	  let manifests: OciManifest[];
   398	  // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing.
   399	  try {
   400	    manifests = await Promise.all(
   401	      chainTags.map((tag) => client.fetchManifest(opts.token, tag, opts.signal))
   402	    );
   403	  } catch {
   404	    return null;
   405	  }
   406	  const binaryName = getPlatformBinaryName();
   407	  const ...
gateway · 2026-09-10 10:57:52
result
     1	/**
     2	 * CLI Error Hierarchy
     3	 *
     4	 * Unified error classes for consistent error handling across the CLI.
     5	 *
     6	 * ## Exit Code Ranges
     7	 *
     8	 * Each error class maps to a semantic exit code so scripts and agents can
     9	 * react to failure categories without parsing stderr. Codes are grouped
    10	 * into decades inspired by HTTP status semantics:
    11	 *
    12	 * | Range | Category          | HTTP Analogy         |
    13	 * |-------|-------------------|----------------------|
    14	 * | 0     | Success           | 200 OK               |
    15	 * | 1     | General error     | 500 Internal         |
    16	 * | 10–19 | Auth & identity   | 401/403              |
    17	 * | 20–29 | Input & config    | 400/404/422          |
    18	 * | 30–39 | API & network     | 502/503/504          |
    19	 * | 40–49 | Feature/billing   | 402/451              |
    20	 * | 50–59 | Operations        | —                    |
    21	 * | 60–69 | ...
gateway · 2026-09-10 10:58:03
result
     1	/**
     2	 * GHCR (GitHub Container Registry) Client
     3	 *
     4	 * Encapsulates the OCI download protocol for fetching nightly CLI binaries
     5	 * from ghcr.io/getsentry/cli. Nightly builds are pushed as OCI artifacts
     6	 * via ORAS with the version baked into the manifest annotation.
     7	 *
     8	 * Key design decisions:
     9	 * - Anonymous access: nightly package is public; no token needed beyond the
    10	 *   standard ghcr.io anonymous token exchange.
    11	 * - Version discovery from manifest annotation: `annotations.version` in the
    12	 *   OCI manifest holds the nightly version. Checking the latest version only
    13	 *   requires a token exchange + manifest fetch (2 HTTP requests total).
    14	 * - Redirect quirk: ghcr.io blob downloads return 307 to Azure Blob Storage.
    15	 *   Using `fetch` with `redirect: "follow"` would forward the Authorization
    16	 *   header to Azure, which returns 404. ...
gateway · 2026-09-10 10:58:23
result
   351	 * Convenience wrapper around {@link fetchManifest} for the rolling nightly tag.
   352	 *
   353	 * @param token - Anonymous bearer token from {@link getAnonymousToken}
   354	 * @returns Parsed OCI manifest
   355	 * @throws {UpgradeError} On network failure or non-200 response
   356	 */
   357	export async function fetchNightlyManifest(
   358	  token: string,
   359	  signal?: AbortSignal,
   360	  source: UpgradeSource = PRIMARY_UPGRADE_SOURCE
   361	): Promise<OciManifest> {
   362	  return await fetchManifest(token, GHCR_TAG, signal, source);
   363	}
   364	
   365	/**
   366	 * Extract the nightly version string from a manifest's annotations.
   367	 *
   368	 * The version is set via `--annotation "version=<ver>"` during `oras push`.
   369	 *
   370	 * @param manifest - OCI manifest from {@link fetchNightlyManifest}
   371	 * @returns Version string (e.g., "0.13.0-dev.1740000000")
   372	 * @throws {UpgradeError} When the version annotation is missing
   373	 */
   ...
gateway · 2026-09-10 10:59:06
result
     1	/**
     2	 * Release Notes Parser & Aggregation
     3	 *
     4	 * Extracts user-facing changelog entries from GitHub Release bodies (stable)
     5	 * or conventional commit messages (nightly). Uses `marked.lexer()` for
     6	 * AST-based section extraction and produces structured data that can be
     7	 * re-serialized as filtered markdown for rendering via `renderMarkdown()`.
     8	 *
     9	 * Only three categories are kept — everything else is filtered out:
    10	 * - **New Features** (✨) — from `### New Features` sections or `feat:` commits
    11	 * - **Bug Fixes** (🐛) — from `### Bug Fixes` sections or `fix:` commits
    12	 * - **Performance** (⚡) — from `### Performance` sections or `perf:` commits
    13	 */
    14	
    15	import { marked, type Token, type Tokens } from "marked";
    16	import {
    17	  compareVersions,
    18	  getGitHubHeaders,
    19	  getGitHubReleasesUrl,
    20	  PRIMARY_UPGRADE_SOURCE,
    21	  type UpgradeSource,
    22	} from ...
gateway · 2026-09-10 10:59:18
result
   421	}
   422	
   423	/** Options for source-aware changelog summary construction. */
   424	type ChangelogBuildOptions = {
   425	  /** Maximum total list items across all sections, or unlimited when omitted. */
   426	  maxItems?: number;
   427	  /** Selected release source whose tag prefix filters the release list. */
   428	  source?: UpgradeSource;
   429	};
   430	
   431	function normalizeChangelogReleases(
   432	  releases: GitHubRelease[],
   433	  source: UpgradeSource
   434	): GitHubRelease[] {
   435	  if (isNormalizedForSource(releases, source)) {
   436	    return releases;
   437	  }
   438	  return [];
   439	}
   440	
   441	/** Build a changelog summary while filtering source-specific release tags. ...
gateway · 2026-09-10 10:59:32
result
     1	/**
     2	 * Upgrade Module
     3	 *
     4	 * Detects how the CLI was installed and provides self-upgrade functionality.
     5	 * Binary management helpers (download URLs, locking, replacement) live in
     6	 * binary.ts and are shared with the setup --install flow.
     7	 */
     8	
     9	import { spawn } from "node:child_process";
    10	import {
    11	  chmodSync,
    12	  closeSync,
    13	  existsSync,
    14	  openSync,
    15	  realpathSync,
    16	  statSync,
    17	  unlinkSync,
    18	  writeSync,
    19	} from "node:fs";
    20	import { writeFile } from "node:fs/promises";
    21	import { homedir } from "node:os";
    22	import { dirname, isAbsolute, join, sep } from "node:path";
    23	import { setTimeout } from "node:timers/promises";
    24	import { prerelease as semverPrerelease, valid as semverValid } from "semver";
    25	import {
    26	  acquireLock,
    27	  cleanupOldBinary,
    28	  compareVersions,
    29	  determineInstallDir,
    30	  ...
gateway · 2026-09-10 10:59:50
result
   401	  }
   402	
   403	  // Default to npm for other node_modules installations (npm, yarn classic)
   404	  return "npm";
   405	}
   406	
   407	/**
   408	 * Legacy detection for existing installs that don't have stored install info.
   409	 * Checks known curl install paths and package managers.
   410	 *
   411	 * @returns Detected installation method, or "unknown" if unable to determine
   412	 */
   413	async function detectLegacyInstallationMethod(): Promise<InstallationMethod> {
   414	  // Check known curl install paths
   415	  for (const dir of getKnownCurlPaths()) {
   416	    if (process.execPath.startsWith(dir)) {
   417	      return "curl";
   418	    }
   419	  }
   420	
   421	  // Check package managers in order of popularity
   422	  const packageManagers: PackageManager[] = ["npm", "pnpm", "bun", "yarn"];
   423	
   424	  for (const pm of packageManagers) {
   425	    if (await isInstalledWith(pm)) {
   426	      return pm;
   427	    }
   428	  }
   429	
   ...
gateway · 2026-09-10 11:00:26
result
   801	 * @param version - Nightly version string (e.g., "0.14.0-dev.1772661724")
   802	 * @returns true if the nightly tag exists in GHCR, false if not found
   803	 * @throws {UpgradeError} On network failure or GHCR unavailability
   804	 */
   805	async function nightlyVersionExists(
   806	  version: string,
   807	  source: UpgradeSource
   808	): Promise<boolean> {
   809	  const token = await getAnonymousToken(source);
   810	  try {
   811	    const manifest = await fetchManifest(
   812	      token,
   813	      `nightly-${version}`,
   814	      undefined,
   815	      source
   816	    );
   817	    validateNightlyManifestVersion(manifest, version);
   818	    return true;
   819	  } catch (error) {
   820	    if (error instanceof GhcrManifestHttpError && error.status === 404) {
   821	      return false;
   822	    }
   823	    throw error;
   824	  }
   825	}
   826	
   827	async function standaloneVersionExists(
   828	  version: string,
   829	  source?: ...
gateway · 2026-09-10 11:00:42
result
  1201	  throw new UpgradeError(
  1202	    "execution_failed",
  1203	    `Downloaded binary is missing or empty at ${path}. ` +
  1204	      "This is usually transient — rerun `sentry cli upgrade` to retry."
  1205	  );
  1206	}
  1207	
  1208	/**
  1209	 * Download the new binary to a temporary path and return its location.
  1210	 * Used by the upgrade command to download before spawning setup --install.
  1211	 *
  1212	 * For **nightly** versions (detected via {@link isNightlyVersion}), downloads
  1213	 * from GHCR using the OCI blob download protocol via {@link downloadNightlyToPath}.
  1214	 *
  1215	 * For **stable** versions, downloads from GitHub Releases via
  1216	 * {@link downloadStableToPath}.
  1217	 *
  1218	 * The lock is held on success so concurrent upgrades are blocked during the
  1219	 * download→spawn→install pipeline. ...
gateway · 2026-09-10 11:01:35
result
     1	/**
     2	 * Background version check for "new version available" notifications.
     3	 *
     4	 * For nightly builds (CLI_VERSION contains "-dev.<timestamp>"), checks GHCR for the
     5	 * latest nightly version via the OCI manifest annotation. For stable builds,
     6	 * checks GitHub Releases. Results are cached in the database and shown on
     7	 * subsequent runs.
     8	 */
     9	
    10	// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import
    11	import * as Sentry from "@sentry/node-core/light";
    12	import { compare as semverCompare } from "semver";
    13	import type { UpgradeSource } from "./binary.js";
    14	import { CLI_VERSION } from "./constants.js";
    15	import { getReleaseChannel } from "./db/release-channel.js";
    16	import {
    17	  getVersionCheckInfo,
    18	  markUpdateNotified,
    19	  setVersionCheckInfo,
    20	} from "./db/version-check.js";
    21	import {
    22	  prefetchNightlyPatches,
    23	  ...
gateway · 2026-09-10 11:01:56
result
packages/cli/package.json
packages/cli/src/lib/custom-ca.ts
pnpm-lock.yaml
gateway · 2026-09-10 11:02:08
result
     1	/**
     2	 * Custom CA certificate loading for corporate TLS proxies.
     3	 *
     4	 * Reads CA bundles from (in priority order):
     5	 * 1. `sentry cli defaults ca-cert` (stored path in SQLite)
     6	 * 2. `NODE_EXTRA_CA_CERTS` env var
     7	 *
     8	 * Returns a `tls` options object for Bun's `fetch()`. On the Node.js npm
     9	 * distribution, Node natively honors `NODE_EXTRA_CA_CERTS` so the extra
    10	 * `tls.ca` option is harmless (ignored by Node's fetch).
    11	 *
    12	 * Security model: When the CA source is an env var (not a stored default)
    13	 * AND the target is SaaS (`*.sentry.io`), a one-time warning is logged.
    14	 * `sentry cli defaults ca-cert` silences the warning — the user has
    15	 * explicitly acknowledged the custom CA. See CLI-1K6 plan for the full
    16	 * threat model discussion. ...
gateway · 2026-09-10 11:03:00
result
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:53:  fetchLatestVersion,
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:59:  resolveExistingUpgradeVersion,
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:60:  resolveLatestUpgradeVersion,
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:62:  versionExists,
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:140: * fallback when `fetchLatestVersion()` hits a network error.
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:301:    if (!(await versionExists(lookupMethod, target))) {
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:309:  const resolved = await resolveExistingUpgradeVersion(target);
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:341:      ? ...
gateway · 2026-09-10 11:03:15
result
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:47:  fetchChangelog,
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:52:  executeUpgrade,
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:514:      // CLI-1D3). `downloadBinaryToTemp`'s visibility-race retry loop
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:695:      executeUpgrade(method, target, downloadTag, offline, setMessage, source)
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:783:      executeUpgrade("curl", target, downloadTag, undefined, setMessage, source)
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:892:  return fetchChangelog({
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:531:  // Expose `current` so attemptDeltaUpgrade's catch path can ...
gateway · 2026-09-10 11:03:28
result
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:387:    (await client.listTags(opts.token, PATCH_TAG_PREFIX, opts.signal));
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:401:      chainTags.map((tag) => client.fetchManifest(opts.token, tag, opts.signal))
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/ghcr.ts:258:export async function getAnonymousToken(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/ghcr.ts:305: * @param token - Anonymous bearer token from {@link getAnonymousToken}
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/ghcr.ts:310:export async function fetchManifest(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/ghcr.ts:351: * Convenience wrapper around {@link fetchManifest} for the rolling nightly tag.
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/ghcr.ts:353: * @param token - Anonymous bearer token from {@link ...
gateway · 2026-09-10 11:03:47
result
     1	{
     2	  "name": "sentry",
     3	  "version": "0.45.0-dev.0",
     4	  "description": "Sentry CLI - A command-line interface for using Sentry built by robots and humans for robots and humans",
     5	  "repository": {
     6	    "type": "git",
     7	    "url": "git+https://github.com/getsentry/cli.git"
     8	  },
     9	  "main": "./dist/index.cjs",
    10	  "type": "module",
    11	  "types": "./dist/index.d.cts",
    12	  "exports": {
    13	    ".": {
    14	      "import": {
    15	        "types": "./dist/index.d.mts",
    16	        "default": "./dist/index.mjs"
    17	      },
    18	      "require": {
    19	        "types": "./dist/index.d.cts",
    20	        "default": "./dist/index.cjs"
    21	      }
    22	    }
    23	  },
    24	  "bin": {
    25	    "sentry": "./dist/bin.cjs"
    26	  },
    27	  "files": [
    28	    "dist/bin.cjs",
    29	    "dist/index.cjs",
    30	    "dist/index.mjs",
    31	    "dist/index.d.cts",
    32	    "dist/index.d.mts",
    ...
gateway · 2026-09-10 11:05:19
result
/usr/bin/bash: -c: line 1: unexpected EOF while looking for matching `"'
gateway · 2026-09-10 11:05:47
result
TAG PREFIX REFERENCES
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/issue/resolve.ts:113:      "  sentry issue resolve CLI-XX -i @commit:getsentry/cli@abc123\n" +
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/release/parse.ts:26: * @param arg - The raw positional argument (e.g., "my-org/1.0.0" or "sentry-cli@1.0.0")
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/release/view.ts:168:      '  sentry release view "sentry-cli@0.24.0"\n' +
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/api/releases.ts:146: * @param version - Release version string (e.g., "1.0.0", "sentry-cli@0.24.0")
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/binary.ts:116:  readonly tagPrefix: string;
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/binary.ts:124:    tagPrefix: "cli@",
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/binary.ts:129:    tagPrefix: ...
gateway · 2026-09-10 11:06:20
result
GHCR INVALID METADATA TESTS
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/delta-upgrade.test.ts:80:    annotations[`sha256-${key}`] = value;
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/delta-upgrade.test.ts:1248:      return new Response(JSON.stringify({ tags: [] }), { status: 200 });
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/ghcr.test.ts:535:    { schemaVersion: 2, layers: [], annotations: ["value"] },
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/ghcr.test.ts:543:          annotations: ["value"],
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/ghcr.test.ts:547:  ])("rejects invalid OCI manifest %#", async (manifest) => {
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/ghcr.test.ts:551:      'Manifest for tag "nightly" returned invalid metadata'
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/ghcr.test.ts:617:  test("rejects a repeated pagination cursor", async () => ...
gateway · 2026-09-10 11:06:35
result
9525f72c7 fix(cli): harden upgrade metadata validation
 packages/cli/src/lib/ghcr.ts                   |  15 +++-
 packages/cli/src/lib/upgrade.ts                |  43 ++++++++--
 packages/cli/test/commands/cli/upgrade.test.ts | 101 ++++++++++++++++++++++-
 packages/cli/test/lib/ghcr.test.ts             |  33 ++++++++
 packages/cli/test/lib/upgrade.test.ts          | 106 +++++++++++++++++++++----
 5 files changed, 270 insertions(+), 28 deletions(-)
diff --git a/packages/cli/src/lib/ghcr.ts b/packages/cli/src/lib/ghcr.ts
index c7b8c37b2..5bca7c061 100644
--- a/packages/cli/src/lib/ghcr.ts
+++ b/packages/cli/src/lib/ghcr.ts
@@ -209,6 +209,7 @@ function isStringRecord(value: unknown): value is Record<string, string> {
   return (
     typeof value === "object" &&
     value !== null &&
+    !Array.isArray(value) &&
     Object.values(value).every((item) => typeof item === "string")
   );
 }
@@ -286,7 +287,8 @@ export async function getAnonymousToken(
     data === null ||
     !("token" ...
gateway · 2026-09-10 11:07:46
result
9525f72c70fabbb23ec21d13cf86add58f6287cc:.github/workflows/sentry-release.yml:38:      VERSION: ${{ github.event.release.tag_name || inputs.version }}
9525f72c70fabbb23ec21d13cf86add58f6287cc:.github/workflows/sentry-release.yml:54:        run: npm install -g "sentry@${VERSION}"
9525f72c70fabbb23ec21d13cf86add58f6287cc:.github/workflows/sentry-release.yml:74:          --url "https://github.com/${{ github.repository }}/releases/tag/${VERSION}"
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/script/bundle.ts:384:// with the npm package so `npx sentry@latest init` can load the
gateway · 2026-09-10 11:08:25
result
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts:413:    test("uses the cached target only after a transport failure", async () => {
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts:429:      expect(getOutput()).toContain("Using cached target: 88.88.88");
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts:432:    test("uses the cached target after response body transport failure", async () => {
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts:452:      expect(getOutput()).toContain("Using cached target: 88.88.88");
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts:461:    ])("never uses the cached target after %s", async (_name, response) => {
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts:479:      expect(getOutput()).not.toContain("Using cached ...
gateway · 2026-09-10 11:08:44

Your replacement security review returned EMPTY. Resume the SAME exact-head immutable review now. A non-empty response is mandatory. If any repository/tool access fails, report BLOCKED first with the exact command/error and end DO-NOT-MERGE. Otherwise provide substantive findings-first evidence for exact base ec83887a16f780f32fba4b7d710bad262dba3a22 and head 9525f72c70fabbb23ec21d13cf86add58f6287cc. State immutable base/head trees, merge base, changed paths inspected, patch hash or equivalent immutable verification, and git diff --check result. Re-evaluate every security contract from the prior prompt with exact HEAD file:line citations: typed 404-only fallback; terminal non-404/transport/malformed responses; source affinity; trusted pagination; body cancellation and transport classification; npm shape/status and no-spawn package-manager failures; canonical Toolkit/legacy tags; pinned metadata; strict GHCR token/OCI/tag validation and cursor cycle; auth-free redirects; channel/offline/cache/final-SHA behavior. Source-neutral patch caching remains settled under byte-identical dual publication and final SHA verification; raise it only with a demonstrated surviving defect. Report PASS/CONCERN/MUST-FIX/BLOCKED and residual risks even with no defects. Never inspect or modify the worktree or mutate VCS. End exactly MERGE or DO-NOT-MERGE. Empty output is forbidden.

gateway · 2026-09-10 11:18:30
result
DELTA FAILURE/CLEANUP ASSERTIONS
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/delta-upgrade.mocked.test.ts:14:import { copyFileSync, existsSync, unlinkSync, writeFileSync } from "node:fs";
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/delta-upgrade.mocked.test.ts:167:      ).rejects.toThrow("SHA-256 mismatch");
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/delta-upgrade.mocked.test.ts:169:      if (existsSync(oldBinaryPath)) unlinkSync(oldBinaryPath);
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/delta-upgrade.mocked.test.ts:170:      if (existsSync(destPath)) unlinkSync(destPath);
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/delta-upgrade.mocked.test.ts:189:      if (existsSync(oldBinaryPath)) unlinkSync(oldBinaryPath);
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/delta-upgrade.mocked.test.ts:251:      if (existsSync(oldBinaryPath)) ...
gateway · 2026-09-10 11:18:41
result
DIFF_CHECK_EXIT=0
gateway · 2026-09-10 11:19:20
result
OBJECTS
ec83887a16f780f32fba4b7d710bad262dba3a22
110d6530e57ca4fbfb2a3f98e52013d2668b5b5e
9525f72c70fabbb23ec21d13cf86add58f6287cc
22f49363a13e32c36dcd9e1ca95916809c2dc271
MERGE_BASE
ec83887a16f780f32fba4b7d710bad262dba3a22
CHANGED_PATHS
M	packages/cli/src/commands/cli/upgrade.ts
M	packages/cli/src/lib/binary.ts
M	packages/cli/src/lib/delta-upgrade.ts
M	packages/cli/src/lib/errors.ts
M	packages/cli/src/lib/ghcr.ts
M	packages/cli/src/lib/release-notes.ts
M	packages/cli/src/lib/upgrade.ts
M	packages/cli/src/lib/version-check.ts
M	packages/cli/test/commands/cli.test.ts
M	packages/cli/test/commands/cli/upgrade.test.ts
M	packages/cli/test/lib/binary.test.ts
M	packages/cli/test/lib/delta-upgrade.mocked.test.ts
M	packages/cli/test/lib/delta-upgrade.test.ts
M	packages/cli/test/lib/ghcr.test.ts
M	packages/cli/test/lib/release-notes.test.ts
M	packages/cli/test/lib/upgrade.test.ts
M	packages/cli/test/lib/version-check.test.ts
PATCH_SHA256
4950719273dc17d5a9e061b8cfe70c62131fe3299dc1799d9c09bb522b...
gateway · 2026-09-10 11:19:34
result
INSPECTED_HEAD_BLOBS_OK=17
gateway · 2026-09-10 11:20:03
result
     1	/**
     2	 * Binary Management
     3	 *
     4	 * Shared utilities for installing, replacing, and managing the CLI binary.
     5	 * Used by both `setup --install` (fresh installs) and `upgrade` (self-updates).
     6	 */
     7	
     8	import { spawnSync } from "node:child_process";
     9	import {
    10	  existsSync,
    11	  mkdirSync,
    12	  readFileSync,
    13	  renameSync,
    14	  unlinkSync,
    15	  writeFileSync,
    16	} from "node:fs";
    17	import { chmod, copyFile, mkdir, realpath, unlink } from "node:fs/promises";
    18	import { delimiter, dirname, isAbsolute, join, resolve, sep } from "node:path";
    19	import { compare as semverCompare } from "semver";
    20	import { getUserAgent } from "./constants.js";
    21	import {
    22	  buildTlsErrorDetail,
    23	  customFetch,
    24	  isTlsCertError,
    25	} from "./custom-ca.js";
    26	import {
    27	  stringifyUnknown,
    28	  UpgradeError,
    29	  UpgradeTransportError,
    30	} from ...
gateway · 2026-09-10 11:20:40
result
diff --git a/packages/cli/src/lib/binary.ts b/packages/cli/src/lib/binary.ts
index 75582612a..e64f5059e 100644
--- a/packages/cli/src/lib/binary.ts
+++ b/packages/cli/src/lib/binary.ts
@@ -1,470 +1,641 @@
 /**
  * Binary Management
  *
  * Shared utilities for installing, replacing, and managing the CLI binary.
  * Used by both `setup --install` (fresh installs) and `upgrade` (self-updates).
  */
 
 import { spawnSync } from "node:child_process";
 import {
   existsSync,
   mkdirSync,
   readFileSync,
   renameSync,
   unlinkSync,
   writeFileSync,
 } from "node:fs";
 import { chmod, copyFile, mkdir, realpath, unlink } from "node:fs/promises";
 import { delimiter, dirname, isAbsolute, join, resolve, sep } from "node:path";
 import { compare as semverCompare } from "semver";
 import { getUserAgent } from "./constants.js";
 import {
   buildTlsErrorDetail,
   customFetch,
   isTlsCertError,
 } from "./custom-ca.js";
-import { stringifyUnknown, UpgradeError } from "./errors.js";
+import ...
gateway · 2026-09-10 11:21:21
result
   250	    throw new UpgradeError("unknown_method");
   251	  }
   252	  // Homebrew manages versioning through the formula — pinning a specific
   253	  // stable version is not supported via this command.
   254	  const pinnedVersion = CHANNEL_VERSIONS.has(versionArg ?? "")
   255	    ? undefined
   256	    : versionArg?.replace(VERSION_PREFIX_REGEX, "");
   257	  if (method === "brew" && pinnedVersion && !isNightlyVersion(pinnedVersion)) {
   258	    throw new UpgradeError(
   259	      "unsupported_operation",
   260	      "Homebrew does not support installing a specific version. Run 'brew upgrade getsentry/tools/sentry' to upgrade to the latest formula version."
   261	    );
   262	  }
   263	  // Offline mode is only supported for curl-installed binaries — package
   264	  // managers always need network to fetch and install packages.
   265	  if (offline && method !== "curl") {
   266	    throw new UpgradeError(
   267	      "unsupported_operation",
   268	      "Offline ...
gateway · 2026-09-10 11:21:36
result
   920	}
   921	
   922	export const upgradeCommand = buildCommand({
   923	  auth: false,
   924	  docs: {
   925	    brief: "Update the Sentry CLI to the latest version",
   926	    fullDescription:
   927	      "Check for updates and upgrade the Sentry CLI to the latest or a specific version.\n\n" +
   928	      "By default, detects how the CLI was installed (npm, curl, etc.) and uses the same method to upgrade.\n\n" +
   929	      "Two release channels are supported:\n" +
   930	      "  stable  (default) Latest stable release\n" +
   931	      "  nightly           Built from main, updated on every commit\n\n" +
   932	      "The channel is persisted so that subsequent bare `sentry cli upgrade` calls\n" +
   933	      "use the same channel.\n\n" +
   934	      "Examples:\n" +
   935	      "  sentry cli upgrade              # Update to latest (using persisted channel)\n" +
   936	      "  sentry cli upgrade nightly      # Switch to nightly channel and update\n" +
   937	      "  ...
gateway · 2026-09-10 11:22:00
result
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:39:import { UpgradeError, UpgradeTransportError } from "../../lib/errors.js";
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:146:function resolveOfflineTarget(versionArg: string | undefined): string {
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:169:async function resolveTargetWithFallback(opts: {
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:201:    const target = resolveOfflineTarget(versionArg);
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:225:    if (method !== "curl" || !(error instanceof UpgradeTransportError)) {
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:229:      const target = resolveOfflineTarget(versionArg);
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:862: * cache on channel ...
gateway · 2026-09-10 11:22:11
result
   120	} {
   121	  // "nightly" and "stable" as positional args select the channel rather than
   122	  // installing a specific version. Match case-insensitively for convenience.
   123	  const lower = positional?.toLowerCase();
   124	  if (lower === "nightly" || lower === "stable") {
   125	    return {
   126	      channel: lower,
   127	      versionArg: undefined,
   128	    };
   129	  }
   130	
   131	  return {
   132	    channel: getReleaseChannel(),
   133	    versionArg: positional,
   134	  };
   135	}
   136	
   137	/**
   138	 * Resolve the target version from the local cache (SQLite) instead of
   139	 * fetching from the network. Used by `--offline` and as automatic
   140	 * fallback when `fetchLatestVersion()` hits a network error. ...
gateway · 2026-09-10 11:22:25
result
     1	/**
     2	 * GHCR (GitHub Container Registry) Client
     3	 *
     4	 * Encapsulates the OCI download protocol for fetching nightly CLI binaries
     5	 * from ghcr.io/getsentry/cli. Nightly builds are pushed as OCI artifacts
     6	 * via ORAS with the version baked into the manifest annotation.
     7	 *
     8	 * Key design decisions:
     9	 * - Anonymous access: nightly package is public; no token needed beyond the
    10	 *   standard ghcr.io anonymous token exchange.
    11	 * - Version discovery from manifest annotation: `annotations.version` in the
    12	 *   OCI manifest holds the nightly version. Checking the latest version only
    13	 *   requires a token exchange + manifest fetch (2 HTTP requests total).
    14	 * - Redirect quirk: ghcr.io blob downloads return 307 to Azure Blob Storage.
    15	 *   Using `fetch` with `redirect: "follow"` would forward the Authorization
    16	 *   header to Azure, which returns 404. ...
gateway · 2026-09-10 11:22:35

Distillations (1)

Gen 0 2026-09-10 11:27:59 · 3315 tokens

Date: Sep 10, 2026 * 🔴 (10:51) User assigned the assistant as the replacement READ-ONLY security and supply-chain merge-gate reviewer for getsentry/cli PR #1569; prior reviewer returned empty twice, and a substantive evidence-based response is mandatory. * 🔴 (10:51) User specified repository `/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrad…