DashboardcliSession 0GF9HJQkQP5C

Session 0GF9HJQkQP5C

Full ID: 0GF9HJQkQP5CCU5DL
Messages: 56
Distillations: 5
Time range: 2026-09-10 12:58:25 — 2026-09-10 13:31:33

Conversation (56 messages)

Independent READ-ONLY correctness merge gate. You MUST use these literal revisions and NO OTHERS: BASE: ec83887a16f780f32fba4b7d710bad262dba3a22 HEAD: 9525f72c70fabbb23ec21d13cf86add58f6287cc REPO: /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade

First run exactly: git -C /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade cat-file -e 'ec83887a16f780f32fba4b7d710bad262dba3a22^{commit}' && git -C /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade cat-file -e '9525f72c70fabbb23ec21d13cf86add58f6287cc^{commit}' If your command contains any different SHA, stop and correct it before execution.

Then use immutable Git objects only. Never edit files or mutate VCS. Inspect complete BASE..HEAD diff and every changed source/test object. Verify merge base, tree SHAs, changed paths, diff SHA-256, and diff --check. Review correctness of dual-source fallback, source affinity, pagination, response-body/cancellation typing, npm/GHCR runtime validation, pins/tags, package-manager no-spawn failures, tracking versus artifact channels, offline behavior, redirect auth isolation, and delta integrity. Source-neutral patch caching is accepted under mandatory byte-identical dual publishing and final SHA verification.

Return a NON-EMPTY final report: findings first with MUST-FIX/CONCERN/PASS and exact HEAD file:line citations; deterministic regression/fix for defects; immutable evidence; residual risks. If the literal first command fails, quote its exact output under BLOCKED. End exactly MERGE or DO-NOT-MERGE.

gateway · 2026-09-10 12:58:25
result
(no output)
gateway · 2026-09-10 12:58:54
result
ec83887a16f780f32fba4b7d710bad262dba3a22
ancestor_exit=0
gateway · 2026-09-10 12:59:05
result
BASE tree: 110d6530e57ca4fbfb2a3f98e52013d2668b5b5e
HEAD tree: 22f49363a13e32c36dcd9e1ca95916809c2dc271
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
gateway · 2026-09-10 12:59:16
result
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

--- numstat ...
gateway · 2026-09-10 12:59:33
result
4950719273dc17d5a9e061b8cfe70c62131fe3299dc1799d9c09bb522b112193  -

--- diff check ---
diff_check_exit=0

--- commits ---
04867eaf9b412a62d4b1ea1c59c2b382c557bd0b feat(cli): add toolkit upgrade bridge
9c874298e3b57515ba36d35140627a668a46dc7b fix(cli): address toolkit bridge review
723375b6ce4e37884e760fa0f848e7e7e3effcc9 fix(cli): address upgrade review findings
dbee7e9f2a4555e044f74dfebad002cccfa43a1f test(cli): update toolkit upgrade fixtures
5ffa5cd0fe02c0a5dc5bec9a8a099f4135c0b447 fix(cli): harden upgrade source resolution
c8c815bbecf472061ec02ab96d1a31f23f078d01 fix(cli): validate GitHub release pagination
b6484e0fb1eb0f8bce68272d538f5c3b26cd5bd5 fix(cli): validate resolved upgrade metadata
c6030fbf4d0c190f037c86d832edb6680d3e54ab fix(cli): bind nightly manifests to versions
2f9b5d0d26124d445a916b8bfc584ad518720584 fix(cli): isolate pinned upgrade resolution
4a1d5bad105c006193c68459175f5eb3c7f78e08 test(cli): update pinned version fixture
e565b1afb39a341605adc883de8812851de7d581 ...
gateway · 2026-09-10 13:00:00
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
@@ -1,414 +1,465 @@
 /**
  * sentry cli upgrade
  *
  * Self-update the Sentry CLI to the latest or a specific version.
  * After upgrading, spawns the NEW binary with `cli setup` to update
  * completions, agent skills, and record installation metadata.
  *
  * Supports two release channels:
  * - stable (default): tracks the latest GitHub release
  * - nightly: tracks the rolling nightly prerelease built from main
  *
  * The channel can be set via --channel or by passing "nightly"/"stable"
  * as the version argument. The choice is persisted in the local database
  * so that subsequent bare `sentry cli upgrade` calls use the same channel. ...
gateway · 2026-09-10 13:00:13
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,450 +1,621 @@
 /**
  * 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 13:00:26
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
@@ -1,368 +1,461 @@
 /** Delta upgrade discovery and application backed by binpatch. */
 
 import { join } from "node:path";
 // biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import
 import * as Sentry from "@sentry/node-core/light";
 import {
   applyPatchChainInMemory,
   extractStableChain as binpatchExtractStableChain,
   filterAndSortChainTags as binpatchFilterAndSortChainTags,
   validateChainStep as binpatchValidateChainStep,
   type DeltaTelemetry,
   type DeltaUnavailableReason,
   type ExtractStableChainOpts,
   type GitHubRelease,
   getPatchFromVersion,
   getPatchTargetSha256,
   ghcrSource,
   githubReleaseSource,
   type InstrumentHook,
   MAX_NIGHTLY_CHAIN_DEPTH,
   makeCache,
   OciClient,
   type OciManifest,
   ...
gateway · 2026-09-10 13:00:34
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
@@ -541,160 +541,168 @@ export function validationError(
   headline: string,
   examples: string[],
   field?: string,
   note?: string
 ): ValidationError {
   return new ValidationError(
     buildValidationMessage(headline, examples, note),
     field
   );
 }
 
 /**
  * Input validation errors.
  *
  * @param message - Validation failure description
  * @param field - Name of the invalid field
  */
 export class ValidationError extends CliError {
   readonly field?: string;
 
   constructor(message: string, field?: string) {
     super(message, EXIT.VALIDATION);
     this.name = "ValidationError";
     this.field = field;
   }
 }
 
 /**
  * OAuth device flow errors (RFC 8628).
  *
  * @param code - OAuth error code (e.g., "authorization_pending", "slow_down")
  * @param description - Human-readable ...
gateway · 2026-09-10 13:00:42
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
@@ -1,520 +1,659 @@
 /**
  * GHCR (GitHub Container Registry) Client
  *
  * Encapsulates the OCI download protocol for fetching nightly CLI binaries
  * from ghcr.io/getsentry/cli. Nightly builds are pushed as OCI artifacts
  * via ORAS with the version baked into the manifest annotation.
  *
  * Key design decisions:
  * - Anonymous access: nightly package is public; no token needed beyond the
  *   standard ghcr.io anonymous token exchange.
  * - Version discovery from manifest annotation: `annotations.version` in the
  *   OCI manifest holds the nightly version. Checking the latest version only
  *   requires a token exchange + manifest fetch (2 HTTP requests total).
  * - Redirect quirk: ghcr.io blob downloads return 307 to Azure Blob Storage. ...
gateway · 2026-09-10 13:00:52
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
@@ -1,102 +1,109 @@
 /**
  * Release Notes Parser & Aggregation
  *
  * Extracts user-facing changelog entries from GitHub Release bodies (stable)
  * or conventional commit messages (nightly). Uses `marked.lexer()` for
  * AST-based section extraction and produces structured data that can be
  * re-serialized as filtered markdown for rendering via `renderMarkdown()`. ...
gateway · 2026-09-10 13:01:01
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
@@ -1,169 +1,267 @@
 /**
  * Upgrade Module
  *
  * Detects how the CLI was installed and provides self-upgrade functionality.
  * Binary management helpers (download URLs, locking, replacement) live in
  * binary.ts and are shared with the setup --install flow.
  */
 
 import { spawn } from "node:child_process";
 import {
   chmodSync,
   closeSync,
   existsSync,
   openSync,
   realpathSync,
   statSync,
   unlinkSync,
   writeSync,
 } from "node:fs";
 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,
   ...
gateway · 2026-09-10 13:01:12
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
@@ -1,110 +1,114 @@
 /**
  * Background version check for "new version available" notifications.
  *
  * For nightly builds (CLI_VERSION contains "-dev.<timestamp>"), checks GHCR for the
  * latest nightly version via the OCI manifest annotation. For stable builds,
  * checks GitHub Releases. Results are cached in the database and shown on
  * subsequent runs.
  */
 
 // 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 {
   getVersionCheckInfo,
   markUpdateNotified,
   ...
gateway · 2026-09-10 13:01:24
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
@@ -77,160 +77,154 @@ describe("feedbackCommand.func", () => {
   });
 
   test("throws ValidationError for whitespace-only message", async () => {
     const func = await feedbackCommand.loader();
     const mockContext = {
       stdout: { write: vi.fn(() => true) },
       stderr: { write: vi.fn(() => true) },
     };
 
     await expect(func.call(mockContext, {}, "   ")).rejects.toThrow(
       "Please provide a feedback message."
     );
   });
 
   test("throws ConfigError when Sentry is disabled", async () => {
     const func = await feedbackCommand.loader();
     const mockContext = {
       stdout: { write: vi.fn(() => true) },
       stderr: { write: vi.fn(() => true) },
     };
 
     // Sentry is disabled in test environment (no DSN)
     await expect(
       ...
gateway · 2026-09-10 13:01:40
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
@@ -8,70 +8,71 @@
  * Status messages go through consola (→ process.stderr). Tests capture stderr
  * via a spy on process.stderr.write and assert on the collected output.
  */
 
 // biome-ignore lint/performance/noNamespaceImport: needed for spyOn mocking
 import * as child_process from "node:child_process";
 import { chmodSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
 import { unlink } from "node:fs/promises";
 import { homedir } from "node:os";
 import { delimiter, join } from "node:path";
 import { gzipSync } from "node:zlib";
 import { run } from "@stricli/core";
 import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
 
 // Make child_process namespace mutable so vi.spyOn works on ESM exports
 ...
gateway · 2026-09-10 13:01:57
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
@@ -1,98 +1,225 @@
 /**
  * Binary Management Tests
  *
  * Tests for shared binary helpers: install directory selection, paths,
  * download URLs, locking, and binary installation.
  */
 
 import {
   chmodSync,
   mkdirSync,
   readFileSync,
   realpathSync,
   rmSync,
   symlinkSync,
   writeFileSync,
 } from "node:fs";
 import { access, readFile, writeFile } from "node:fs/promises";
 import { join, sep } from "node:path";
 import { afterEach, beforeEach, describe, expect, test } from "vitest";
 import {
   acquireLock,
   compareVersions,
   determineInstallDir,
   fetchWithUpgradeError,
   getBinaryDownloadUrl,
   getBinaryFilename,
   getBinaryPaths,
+  getGitHubReleaseByTagUrl,
   getLegacyInstallDirs,
   getPlatformBinaryName,
   installBinary,
   isDowngrade,
   ...
gateway · 2026-09-10 13:02:08
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
@@ -52,125 +52,125 @@ import {
 
 let originalFetch: typeof globalThis.fetch;
 
 beforeEach(() => {
   originalFetch = globalThis.fetch;
 });
 
 afterEach(() => {
   globalThis.fetch = originalFetch;
 });
 
 function mockFetch(
   fn: (url: string | URL | Request, init?: RequestInit) => Promise<Response>
 ): void {
   globalThis.fetch = fn as typeof globalThis.fetch;
 }
 
 // ============================================================================
 // Helpers
 // ============================================================================
 
 const BINARY_NAME = getPlatformBinaryName();
 
 function versionHex(version: string): string {
   return Array.from(version)
     .map((c) => c.charCodeAt(0).toString(16).padStart(2, ...
gateway · 2026-09-10 13:02:17
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
@@ -1,75 +1,84 @@
 /**
  * Unit Tests for Delta Upgrade Module
  *
  * Tests the exported pure-computation functions that drive chain resolution
  * for both stable (GitHub Releases) and nightly (GHCR) channels, plus
  * async orchestration functions tested via fetch mocking.
  */
 
 import { createHash } from "node:crypto";
 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,
+  ...
gateway · 2026-09-10 13:02:27
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
@@ -1,242 +1,350 @@
 /**
  * GHCR Client Tests
  *
  * Unit tests for the GHCR/OCI download protocol helpers.
  * All HTTP calls are mocked via globalThis.fetch to avoid network access.
  */
 
 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,
   downloadNightlyBlob,
   fetchManifest,
   fetchNightlyManifest,
   findLayerByFilename,
   GHCR_REPO,
   GHCR_TAG,
+  GhcrManifestHttpError,
   getAnonymousToken,
   getNightlyVersion,
   listTags,
   type OciManifest,
 } from "../../src/lib/ghcr.js";
 
 /** Store original fetch for restoration */
 let originalFetch: typeof globalThis.fetch;
 
 /** Helper to mock fetch without ...
gateway · 2026-09-10 13:02:43
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
@@ -1,56 +1,62 @@
 /**
  * Unit Tests for Release Notes Parser & Aggregation
  *
  * Tests core invariants (section extraction, version filtering, truncation)
  * that are hard to express as property-based tests due to format specifics.
  *
  * Core random-input invariants (category validity, filtering, commit parsing)
  * are tested via property-based tests in release-notes.property.test.ts.
  */
 
 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 ...
gateway · 2026-09-10 13:02:58
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
@@ -103,60 +103,62 @@ const { spawnImpl } = vi.hoisted(() => ({
 // Initialize with the real default now that fakeProcess is defined
 spawnImpl.fn = () => fakeProcess(0);
 
 vi.mock("node:child_process", async (importOriginal) => {
   const orig = await importOriginal<typeof import("node:child_process")>();
   return {
     ...orig,
     spawn: (cmd: string, args: string[], opts: object) =>
       spawnImpl.fn(cmd, args, opts),
   };
 });
 
 // Dynamic imports: must run AFTER vi.mock() so upgrade.ts picks up the
 // mocked spawn.
 import { isEnoentSpawnError } from "../../src/commands/cli/upgrade.js";
 import {
   acquireLock,
   getBinaryDownloadUrl,
   isNightlyVersion,
   releaseLock,
+  UPGRADE_SOURCES,
 } from "../../src/lib/binary.js";
 import {
   clearInstallInfo,
   ...
gateway · 2026-09-10 13:03:12
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
@@ -1,46 +1,49 @@
 /**
  * Version Check Logic Tests
  */
 
 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,
   ValidationError,
 } from ...
gateway · 2026-09-10 13:07:55
result
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:341:      ? await resolveLatestUpgradeVersion(channel)
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:497:export async function fetchLatestFromGitHubWithSource(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:549:export async function fetchLatestFromGitHub(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:554:    await fetchLatestFromGitHubWithSource(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:629:export async function fetchLatestNightlyVersionWithSource(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:676:export async function fetchLatestNightlyVersion(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:681:    await fetchLatestNightlyVersionWithSource(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:705:    return ...
gateway · 2026-09-10 13:08:10
result
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/lib/delta-upgrade.ts:580:export function resolveStableDelta(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:599:export function resolveNightlyDelta(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:618:export function attemptDeltaUpgrade(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:711:export function prefetchNightlyPatches(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:719:export function ...
gateway · 2026-09-10 13:08:18
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:310:export async function fetchManifest(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/ghcr.ts:357:export async function fetchNightlyManifest(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/ghcr.ts:362:  return await fetchManifest(token, GHCR_TAG, signal, source);
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/ghcr.ts:432:export async function downloadNightlyBlob(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/ghcr.ts:599:export ...
gateway · 2026-09-10 13:08:32
result
/usr/bin/bash: line 1: python: command not found
gateway · 2026-09-10 13:08:42
result
  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 upgrade is only supported for curl-installed binaries."
  269     );
  270   }
  271 }
  272 
  273 function getArtifactChannel(target: string): ReleaseChannel {
  274   return isNightlyVersion(target) ? "nightly" : "stable";
  275 }
  276 
  277 type ResolveTargetOptions = {
  278   method: InstallationMethod;
  279   channel: ReleaseChannel;
  280   versionArg: string | undefined;
  281   channelChanged: boolean;
  282   flags: UpgradeFlags;
  283 };
  284 
  285 /**
  286  * Result of resolving the target version. ...
gateway · 2026-09-10 13:08:55
result
  150   const { latestVersion } = getVersionCheckInfo();
  151   if (!latestVersion) {
  152     throw new UpgradeError(
  153       "network_error",
  154       "No cached version available. Run any command to trigger a background version check, then retry."
  155     );
  156   }
  157   return latestVersion;
  158 }
  159 
  160 /**
  161  * Resolve the target version, trying the network first and falling back to
  162  * the local cache when offline or when the network is unavailable.
  163  *
  164  * @returns `{ target, offline }` — the resolved version and whether the
  165  *   resolution used the offline path (explicit or automatic fallback).
  166  *   Returns `null` when `resolveTargetVersion` returns a "done" result
  167  *   (check-only or already up-to-date); the caller should yield that result. ...
gateway · 2026-09-10 13:09:09
result
  650   ) {
  651     return xdgInstallDir;
  652   }
  653   return currentInstallDir;
  654 }
  655 
  656 /**
  657  * Execute the standard upgrade path: download via curl or package manager,
  658  * then run setup on the new binary.
  659  */
  660 async function executeStandardUpgrade(opts: {
  661   method: InstallationMethod;
  662   channel: ReleaseChannel;
  663   versionArg: string | undefined;
  664   target: string;
  665   execPath: string;
  666   entryPath?: string;
  667   pathEnv?: string;
  668   offline?: OfflineMode;
  669   json?: boolean;
  670   noAgentSkills: boolean;
  671   source?: UpgradeSource;
  672 }): Promise<void> {
  673   const {
  674     method,
  675     channel,
  676     versionArg,
  677     target,
  678     execPath,
  679     entryPath,
  680     pathEnv,
  681     offline,
  682     json,
  683     noAgentSkills,
  684     source,
  685   } = opts;
  686 
  687   // Use the rolling "nightly" tag only when upgrading to latest nightly
  688  ...
gateway · 2026-09-10 13:09:29
result
  835 
  836 /**
  837  * Resolve the channel, version arg, method, and channel-changed flag from
  838  * the positional version argument and flags. Extracted to keep `func()`
  839  * complexity under the biome limit.
  840  */
  841 async function resolveContext(
  842   version: string | undefined,
  843   flags: UpgradeFlags
  844 ): Promise<{
  845   channel: ReleaseChannel;
  846   versionArg: string | undefined;
  847   channelChanged: boolean;
  848   method: InstallationMethod;
  849 }> {
  850   const { channel, versionArg } = resolveChannelAndVersion(version);
  851   const currentChannel = getReleaseChannel();
  852   const channelChanged = channel !== currentChannel;
  853 
  854   const method = flags.method ?? (await detectInstallationMethod());
  855   validateMethod(method, versionArg, flags.offline);
  856   return { channel, versionArg, channelChanged, method };
  857 }
  858 
  859 /**
  860  * Persist the release channel preference. ...
gateway · 2026-09-10 13:09:38
result
 1038 
 1039     // --check with offline fallback: resolveTargetWithFallback returns
 1040     // kind: "target" for offline check, so guard against actual upgrade.
 1041     if (flags.check) {
 1042       return yield new CommandOutput(
 1043         await buildCheckResultWithChangelog({
 1044           target,
 1045           versionArg,
 1046           method,
 1047           channel,
 1048           flags,
 1049           offline,
 1050           changelogPromise,
 1051         })
 1052       );
 1053     }
 1054 
 1055     // Skip if already on target — unless forced or switching channels
 1056     if (CLI_VERSION === target && !flags.force && !channelChanged) {
 1057       return yield new CommandOutput({
 1058         action: "up-to-date",
 1059         currentVersion: CLI_VERSION,
 1060         targetVersion: target,
 1061         channel,
 1062         method,
 1063         forced: false,
 1064         offline: offline ? ...
gateway · 2026-09-10 13:09:49
result
  105   | "bun"
  106   | "yarn"
  107   | "unknown";
  108 
  109 /** A repository pair that hosts CLI stable releases and nightly OCI images. */
  110 export type UpgradeSource = {
  111   /** GitHub `owner/repository` containing CLI release assets. */
  112   readonly githubRepo: string;
  113   /** GHCR `owner/package` containing CLI nightly images and delta patches. */
  114   readonly ghcrRepo: string;
  115   /** Prefix attached to CLI release tags in this repository. */
  116   readonly tagPrefix: string;
  117 };
  118 
  119 /** Ordered CLI release sources. The resolver falls through only on HTTP 404. */
  120 export const UPGRADE_SOURCES = [
  121   {
  122     githubRepo: "getsentry/toolkit",
  123     ghcrRepo: "getsentry/toolkit",
  124     tagPrefix: "cli@",
  125   },
  126   {
  127     githubRepo: "getsentry/cli",
  128     ghcrRepo: "getsentry/cli",
  129     tagPrefix: "",
  130   },
  131 ] as const satisfies readonly [UpgradeSource, ...UpgradeSource[]];
  132 
  ...
gateway · 2026-09-10 13:10:01
result
  337 /**
  338  * Select the first available upgrade source.
  339  *
  340  * The caller receives the successful probe response so it never repeats the
  341  * request. Only HTTP 404 advances to the next source. Every other HTTP or
  342  * network failure aborts immediately.
  343  */
  344 export async function resolveUpgradeSource(
  345   options: ResolveUpgradeSourceOptions
  346 ): Promise<ResolvedUpgradeSource> {
  347   for (const source of options.sources ?? UPGRADE_SOURCES) {
  348     const response = await fetchUpgradeProbe(source, options);
  349     if (response.ok) {
  350       return { source, response };
  351     }
  352     if (response.status !== 404) {
  353       throw new UpgradeError(
  354         "network_error",
  355         `Failed to fetch from GitHub: HTTP ${response.status}`
  356       );
  357     }
  358   }
  359 
  360   throw new UpgradeSourceNotFoundError();
  361 }
  362 
  363 /**
  364  * Detect whether a version string identifies a ...
gateway · 2026-09-10 13:10:14
result
  490  * @param init - Fetch options
  491  * @param serviceName - Service name for error messages (e.g., "GitHub")
  492  * @returns Response object
  493  * @throws {UpgradeError} On network failure
  494  * @throws {Error} AbortError if signal is aborted (re-thrown as-is)
  495  */
  496 export async function fetchWithUpgradeError(
  497   url: string,
  498   init: RequestInit,
  499   serviceName: string
  500 ): Promise<Response> {
  501   try {
  502     return await customFetch(url, init);
  503   } catch (error) {
  504     if (init.signal?.aborted) {
  505       throw init.signal.reason;
  506     }
  507     // Re-throw AbortError as-is so callers can handle it specifically
  508     if (error instanceof Error && error.name === "AbortError") {
  509       throw error;
  510     }
  511     if (error instanceof Error && isTlsCertError(error)) {
  512       throw new UpgradeTransportError(buildTlsErrorDetail(error));
  513     }
  514     const msg = stringifyUnknown(error);
 ...
gateway · 2026-09-10 13:10:25
result
   75 export type { InstallationMethod } from "./binary.js";
   76 // biome-ignore lint/performance/noBarrelFile: backward-compat re-export, not a barrel
   77 export { parseInstallationMethod } from "./binary.js";
   78 
   79 /** Package managers that can be used for global installs */
   80 type PackageManager = "npm" | "pnpm" | "bun" | "yarn";
   81 
   82 /**
   83  * How the current upgrade reached the offline code path.
   84  *
   85  * - `false` — online upgrade (network available)
   86  * - `"explicit"` — user passed `--offline` flag
   87  * - `"network-fallback"` — network failed, auto-fell back to cache
   88  */
   89 export type OfflineMode = false | "explicit" | "network-fallback";
   90 
   91 // Constants
   92 
   93 /** The git tag used for the rolling nightly GitHub release (stable fallback only). */
   94 export const NIGHTLY_TAG = "nightly";
   95 
   96 /** npm registry base URL */
   97 const NPM_REGISTRY_URL = "https://registry.npmjs.org/sentry";
   98 
   ...
gateway · 2026-09-10 13:10:39
result
  490  * Fetch the latest version from GitHub releases.
  491  *
  492  * @param signal - Optional AbortSignal to cancel the request
  493  * @returns Latest version string (without 'v' prefix)
  494  * @throws {UpgradeError} When fetch fails or response is invalid
  495  * @throws {Error} AbortError if signal is aborted
  496  */
  497 export async function fetchLatestFromGitHubWithSource(
  498   signal?: AbortSignal,
  499   sources: readonly UpgradeSource[] = UPGRADE_SOURCES
  500 ): Promise<ResolvedUpgradeVersion> {
  501   const resolved = await resolveUpgradeSource({
  502     getProbeUrl: getGitHubLatestReleaseUrl,
  503     signal,
  504     sources,
  505   });
  506   let response = resolved.response;
  507   const visitedPages = new Set([getGitHubLatestReleaseUrl(resolved.source)]);
  508   const versions: string[] = [];
  509   while (true) {
  510     const data = await parseUpgradeJson(
  511       response,
  512       signal,
  513       "GitHub returned invalid ...
gateway · 2026-09-10 13:10:48
result
  827 async function standaloneVersionExists(
  828   version: string,
  829   source?: UpgradeSource
  830 ): Promise<boolean> {
  831   if (!isNightlyVersion(version)) {
  832     validateStableVersion(version, "Requested standalone version");
  833   }
  834   if (source) {
  835     if (isNightlyVersion(version)) {
  836       return nightlyVersionExists(version, source);
  837     }
  838     const response = await fetchWithUpgradeError(
  839       getGitHubReleaseByTagUrl(version, source),
  840       { headers: getGitHubHeaders() },
  841       "GitHub"
  842     );
  843     if (response.ok) {
  844       await validatePinnedGitHubRelease(response, version, source);
  845       return true;
  846     }
  847     if (response.status === 404) {
  848       return false;
  849     }
  850     throw new UpgradeError(
  851       "network_error",
  852       `Failed to fetch from GitHub: HTTP ${response.status}`
  853     );
  854   }
  855   const resolved = await ...
gateway · 2026-09-10 13:11:23
result
 1030  * @returns Filename of the gzip-compressed binary for this platform
 1031  */
 1032 function getNightlyGzFilename(): string {
 1033   return `${getPlatformBinaryName()}.gz`;
 1034 }
 1035 
 1036 /**
 1037  * Download a nightly binary from GHCR and decompress it to `destPath`.
 1038  *
 1039  * Fetches an anonymous token, retrieves the OCI manifest, finds the layer
 1040  * matching this platform's `.gz` filename, then downloads and decompresses
 1041  * the blob in-stream.
 1042  *
 1043  * When `version` is provided, fetches the pinned versioned tag
 1044  * (`nightly-{version}`). Otherwise fetches the rolling `:nightly` tag.
 1045  *
 1046  * @param destPath - File path to write the decompressed binary
 1047  * @param version - Specific nightly version to download (omit for latest)
 1048  * @throws {UpgradeError} When GHCR fetch or blob download fails
 1049  */
 1050 async function downloadNightlyToPath(
 1051   destPath: string,
 1052   version?: string,
 1053   setMessage?: ...
gateway · 2026-09-10 13:11:32
result
 1090  * @throws {UpgradeError} When both download attempts fail
 1091  */
 1092 async function downloadStableToPath(
 1093   version: string,
 1094   destPath: string,
 1095   setMessage?: SetMessage,
 1096   source: UpgradeSource = PRIMARY_UPGRADE_SOURCE
 1097 ): Promise<void> {
 1098   const url = getBinaryDownloadUrl(version, source);
 1099   const headers = getGitHubHeaders();
 1100 
 1101   // Try gzip-compressed download first (~60% smaller)
 1102   // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing.
 1103   try {
 1104     const gzResponse = await fetchWithUpgradeError(
 1105       `${url}.gz`,
 1106       { headers },
 1107       "GitHub"
 1108     );
 1109     if (gzResponse.ok && gzResponse.body) {
 1110       await streamDecompressToFile(gzResponse.body, destPath, setMessage);
 1111       return;
 1112     }
 1113   } catch {
 1114     // Fall through to raw download
 1115   }
 1116 
 1117   // Fall ...
gateway · 2026-09-10 13:11:46
result
   25 } from "./binary.js";
   26 import { getUserAgent } from "./constants.js";
   27 import { customFetch } from "./custom-ca.js";
   28 import { UpgradeError, UpgradeTransportError } from "./errors.js";
   29 
   30 /** Default timeout for GHCR HTTP requests (10 seconds) */
   31 const GHCR_REQUEST_TIMEOUT = 10_000;
   32 
   33 /** Maximum number of retry attempts for transient failures */
   34 const GHCR_MAX_RETRIES = 1;
   35 
   36 /** Nightly versions use a numeric build timestamp as the prerelease value. */
   37 const NIGHTLY_VERSION_REGEX = /^\d+\.\d+\.\d+-dev\.\d+$/;
   38 
   39 /** Timeout for large blob downloads (30 seconds) */
   40 const GHCR_BLOB_TIMEOUT = 30_000;
   41 
   42 /**
   43  * Check if an error is a transient network/timeout failure worth retrying.
   44  *
   45  * Matches timeout/abort errors from `AbortSignal.timeout()`, connection
   46  * resets, and generic network failures. ...
gateway · 2026-09-10 13:11:59
result
  230 }
  231 
  232 function isOciManifest(value: unknown): value is OciManifest {
  233   if (typeof value !== "object" || value === null) {
  234     return false;
  235   }
  236   const manifest = value as Partial<OciManifest>;
  237   return (
  238     manifest.schemaVersion === 2 &&
  239     Array.isArray(manifest.layers) &&
  240     manifest.layers.every(isOciLayer) &&
  241     (manifest.mediaType === undefined ||
  242       typeof manifest.mediaType === "string") &&
  243     (manifest.config === undefined || isOciLayer(manifest.config)) &&
  244     (manifest.annotations === undefined || isStringRecord(manifest.annotations))
  245   );
  246 }
  247 
  248 /**
  249  * Fetch a short-lived anonymous bearer token for read-only access to the
  250  * public `ghcr.io/getsentry/cli` package.
  251  *
  252  * The token exchange endpoint returns a JSON object with a `token` field.
  253  * No credentials are required for public packages. ...
gateway · 2026-09-10 13:12:14
result
  420  *
  421  * The blob endpoint returns a 307 redirect to a signed Azure Blob Storage URL.
  422  * `fetch` with `redirect: "follow"` would forward the Authorization header
  423  * to Azure, which returns 404. We must:
  424  * 1. Fetch the blob URL without following redirects to get the redirect URL.
  425  * 2. Follow the redirect URL without the Authorization header.
  426  *
  427  * @param token - Anonymous bearer token from {@link getAnonymousToken}
  428  * @param digest - Layer digest to download (e.g., "sha256:abc123...")
  429  * @returns Raw response body (gzip-compressed binary)
  430  * @throws {UpgradeError} On network failure or bad response
  431  */
  432 export async function downloadNightlyBlob(
  433   token: string,
  434   digest: string,
  435   signal?: AbortSignal,
  436   source: UpgradeSource = PRIMARY_UPGRADE_SOURCE
  437 ): Promise<Response> {
  438   const blobUrl = `${GHCR_REGISTRY}/v2/${source.ghcrRepo}/blobs/${digest}`;
  439 
  440   // Step 1: ...
gateway · 2026-09-10 13:12:25
result
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/custom-ca.ts:305:export function customFetch(
    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 13:12:34
result
  285  * Get the combined CA certificate PEM string for Node.js `http.request()`.
  286  * Returns undefined when no custom CAs are configured.
  287  *
  288  * Unlike {@link getCustomTlsOptions} (which returns Bun's `{ tls: { ca } }` shape),
  289  * this returns the raw PEM string suitable for Node's `https.RequestOptions.ca`
  290  * and the Sentry SDK's `NodeTransportOptions.caCerts`.
  291  */
  292 export function getCustomCaCerts(): string | undefined {
  293   resolve();
  294   return resolved?.tls.ca;
  295 }
  296 
  297 /**
  298  * Drop-in replacement for `fetch()` that injects custom CA certificates
  299  * when configured. All non-authenticated fetch call sites should use this
  300  * instead of bare `fetch()`.
  301  *
  302  * Authenticated API calls go through `fetchWithTimeout()` in sentry-client.ts
  303  * which already applies TLS options directly alongside the SaaS warning. ...
gateway · 2026-09-10 13:12:48
result
   55   PatchChain,
   56   StableChainInfo,
   57 } from "binpatch";
   58 // biome-ignore lint/performance/noBarrelFile: preserve the existing public API
   59 export {
   60   extractSha256,
   61   getPatchFromVersion,
   62   getPatchTargetSha256,
   63   getStableTargetSha256,
   64   PATCH_TAG_PREFIX,
   65 } from "binpatch";
   66 
   67 export type DeltaResult = {
   68   sha256: string;
   69   patchBytes: number;
   70   chainLength: number;
   71 };
   72 
   73 const log = logger.withTag("delta-upgrade");
   74 
   75 const NORMALIZED_RELEASE_SOURCE = Symbol("normalizedReleaseSource");
   76 
   77 /** Stable GitHub releases normalized for one explicit upgrade source. */
   78 export type NormalizedGitHubReleases = GitHubRelease[] & {
   79   /** Stable key for the source that produced these normalized tags. */
   80   readonly [NORMALIZED_RELEASE_SOURCE]: string;
   81 };
   82 
   83 function upgradeSourceKey(source: UpgradeSource): string {
   84   return ...
gateway · 2026-09-10 13:13:24
result
  485       );
  486       if (sha256 !== chain.expectedSha256) {
  487         throw new Error(
  488           `SHA-256 mismatch after patching: got ${sha256}, expected ${chain.expectedSha256}`
  489         );
  490       }
  491       return sha256;
  492     }
  493   );
  494 }
  495 
  496 function makeProgressHandler(setMessage?: SetMessage): ProgressHandler {
  497   let progress: ReturnType<typeof makeByteProgress> | undefined;
  498   let phase: string | undefined;
  499   let previousWritten = 0;
  500   return (event) => {
  501     if (
  502       event.type === "bytes" &&
  503       (progress === undefined || phase !== event.phase)
  504     ) {
  505       // New phase: spin up a fresh bar. The apply phase totals bytes across
  506       // every hop's `newSize`, which for multi-hop chains far exceeds the
  507       // final binary (e.g. 930 MB shown for a 310 MB install). ...
gateway · 2026-09-10 13:13:38
result
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/db/dsn-cache.model-based.test.ts:153:class SetCachedDsnCommand implements AsyncCommand<CacheModel, RealCache> {
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/db/dsn-cache.model-based.test.ts:187:class GetCachedDsnCommand implements AsyncCommand<CacheModel, RealCache> {
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/db/dsn-cache.model-based.test.ts:218:class UpdateCachedResolutionCommand
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/db/dsn-cache.model-based.test.ts:246:class ClearDsnCacheCommand implements AsyncCommand<CacheModel, RealCache> {
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/db/dsn-cache.model-based.test.ts:280:class SetCachedProjectCommand implements AsyncCommand<CacheModel, RealCache> {
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/db/dsn-cache.model-based.test.ts:307:class GetCachedProjectCommand implements ...
gateway · 2026-09-10 13:14:14
result
  217 packages/cli/src/commands/cli/upgrade.ts
  204 packages/cli/src/lib/binary.ts
  206 packages/cli/src/lib/delta-upgrade.ts
   13 packages/cli/src/lib/errors.ts
  252 packages/cli/src/lib/ghcr.ts
  160 packages/cli/src/lib/release-notes.ts
  576 packages/cli/src/lib/upgrade.ts
   33 packages/cli/src/lib/version-check.ts
   57 packages/cli/test/commands/cli.test.ts
  545 packages/cli/test/commands/cli/upgrade.test.ts
  201 packages/cli/test/lib/binary.test.ts
   25 packages/cli/test/lib/delta-upgrade.mocked.test.ts
  243 packages/cli/test/lib/delta-upgrade.test.ts
  256 packages/cli/test/lib/ghcr.test.ts
  229 packages/cli/test/lib/release-notes.test.ts
  804 packages/cli/test/lib/upgrade.test.ts
  133 packages/cli/test/lib/version-check.test.ts
gateway · 2026-09-10 13:14:26
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
@@ -122,0 +123 @@ import {
+  UPGRADE_SOURCES,
@@ -142,0 +144 @@ const {
+  resolveExistingUpgradeVersion,
@@ -191,7 +193,33 @@ 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 () => {
+    const requests: string[] = [];
+    mockFetch(async (url) => {
+      requests.push(String(url));
+      return new Response(
+        JSON.stringify([
+          { tag_name: "mcp@9.0.0" },
+          { tag_name: "cli@not-a-version" },
+          { tag_name: "cli@99.0.0-dev.1", prerelease: false },
+          { tag_name: "cli@1.2.3" },
+         ...
gateway · 2026-09-10 13:14:48
result
+    await expect(resolveExistingUpgradeVersion("1.0.0")).rejects.toThrow(
+      "Failed to connect to GitHub"
+    );
+  });
+
+  test.each([
+    ["empty body", ""],
+    ["invalid JSON", "{"],
+    ["missing tag", JSON.stringify({})],
+    ["mismatched tag", JSON.stringify({ tag_name: "mcp@1.0.0" })],
+  ])("rejects pinned Toolkit %s without legacy fallback", async (_name, body) => {
+    const requests: string[] = [];
+    mockFetch(async (url) => {
+      requests.push(String(url));
+      return new Response(body, { status: 200 });
+    });
+
+    await expect(resolveExistingUpgradeVersion("1.0.0")).rejects.toMatchObject({
+      reason: "network_error",
+    });
+    expect(requests).toEqual([
+      "https://api.github.com/repos/getsentry/toolkit/releases/tags/cli%401.0.0",
+    ]);
+  });
+
+  test.each([
+    undefined,
+    "not-semver",
+    "0.14.0-dev.124",
+  ])("rejects pinned nightly manifest annotation %s without legacy fallback", async (annotation) => {
+    const ...
gateway · 2026-09-10 13:15:00
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
@@ -42,0 +43 @@ import {
+import { setVersionCheckInfo } from "../../../src/lib/db/version-check.js";
@@ -174,0 +176,4 @@ function mockGhcrNightlyVersion(version: string): void {
+    if (urlStr === "https://api.github.com/repos/getsentry/toolkit") {
+      return new Response(null, { status: 200 });
+    }
+
@@ -212,3 +217,2 @@ function mockGitHubVersion(version: string): void {
-    // GitHub latest release endpoint — returns JSON with tag_name
-    if (urlStr.includes("releases/latest")) {
-      return new Response(JSON.stringify({ tag_name: version }), {
+    if (urlStr.includes("getsentry/toolkit/releases?per_page=100")) {
+      return new Response(JSON.stringify([{ tag_name: `cli@${version}` }]), {
@@ -220 +223,0 @@ function ...
gateway · 2026-09-10 13:15:11
result
+    await run(app, ["cli", "upgrade", "--method", method, "1.2.3"], context);
+
+    expect(errors.join("\n")).toContain("Version 1.2.3 not found");
+    expect(requests).toEqual(["https://registry.npmjs.org/sentry/1.2.3"]);
+    expect(spawnedArgs).toEqual([]);
+  });
+
+  test.each([
+    "npm",
+    "pnpm",
+    "bun",
+    "yarn",
+  ] as const)("preserves non-404 HTTP failures for a pinned %s version", async (method) => {
+    for (const status of [401, 403, 429, 500]) {
+      const requests: string[] = [];
+      mockFetch(async (url) => {
+        requests.push(String(url));
+        return new Response(null, { status });
+      });
+      const { context, errors, restore } = createMockContext({
+        homeDir: testDir,
+      });
+
+      await run(app, ["cli", "upgrade", "--method", method, "1.2.3"], context);
+
+      restore();
+      expect(errors.join("\n")).toContain(
+        `Failed to fetch from npm: ${status}`
+      );
+      ...
gateway · 2026-09-10 13:15:21
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
@@ -8,0 +9 @@ import { afterEach, beforeEach, describe, expect, test } from "vitest";
+import { UPGRADE_SOURCES } from "../../src/lib/binary.js";
@@ -17,0 +19 @@ import {
+  GhcrManifestHttpError,
@@ -40 +42 @@ function makeManifest(overrides: Partial<OciManifest> = {}): OciManifest {
-      digest: "sha256:config",
+      digest: `sha256:${"0".repeat(64)}`,
@@ -46 +48 @@ function makeManifest(overrides: Partial<OciManifest> = {}): OciManifest {
-        digest: "sha256:abc123",
+        digest: `sha256:${"a".repeat(64)}`,
@@ -54 +56 @@ function makeManifest(overrides: Partial<OciManifest> = {}): OciManifest {
-        digest: "sha256:def456",
+        digest: `sha256:${"d".repeat(64)}`,
@@ -93,0 +96,13 @@ describe("getAnonymousToken", () => {
+  test("uses the selected source's GHCR ...
gateway · 2026-09-10 13:15:32

Return your FINAL non-empty correctness report now for literal BASE ec83887a16f780f32fba4b7d710bad262dba3a22 and HEAD 9525f72c70fabbb23ec21d13cf86add58f6287cc. Do not return progress or defer work. Include immutable merge base, base/head trees, changed-file scope, diff hash, git diff --check, findings with exact HEAD file:line evidence, and residual risks. If no defect exists, state PASS — no correctness defect found and end exactly MERGE. If blocked or defective, quote evidence and end exactly DO-NOT-MERGE. Never edit files or VCS. Empty output is forbidden.

gateway · 2026-09-10 13:31:33

Findings

PASS — no correctness defect found.

  • PASS — fallback and source affinity: Toolkit precedes legacy, and only HTTP 404 permits fallback. Other HTTP, transport, parsing, and metadata failures stop resolution. The selected source flows through validation, download, delta, changelog, and prefetch paths:

    • /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade/packages/cli/src/lib/binary.ts:119-134
    • /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade/packages/cli/src/lib/binary.ts:344-360
    • /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade/packages/cli/src/lib/upgrade.ts:497-545
    • /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade/packages/cli/src/lib/upgrade.ts:643-672
  • PASS — pagination and response validation: GitHub pagination validates origin, path, positive page numbers, and cycles. GHCR pagination rejects repeated cursors. JSON body failures preserve caller cancellation and distinguish malformed metadata from transport failure:

    • /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade/packages/cli/src/lib/upgrade.ts:149-185
    • /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade/packages/cli/src/lib/upgrade.ts:497-545
    • /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade/packages/cli/src/lib/ghcr.ts:599-636
    • /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade/packages/cli/src/lib/binary.ts:496-539
  • PASS — pins, npm/GHCR validation, and no-spawn failures: Stable npm versions require canonical non-prerelease SemVer. ...

gpt-5.6-sol · 2026-09-10 13:31:33 · ~2.7K tokens

Distillations (5)

Gen 0 2026-09-10 14:36:57 · 737 tokens

* 🟡 (13:01) `packages/cli/test/commands/cli.test.ts` updated upgrade-command GitHub mocks from single release objects such as `{ tag_name: "v99.0.0" }` to Toolkit release-list responses using product-prefixed tags such as `[{ tag_name: "cli@99.0.0" }]`. * 🟡 (13:01) Test `"shows installation info with specified method"` now invokes `func.call(context, { check: true, method: "curl", json: true })…

Gen 0 2026-09-10 14:31:26 · 1276 tokens

Date: Sep 10, 2026 * 🔴 (13:01) User specified installation detection must always check for Homebrew first because stored install information may be stale; the cheap, authoritative realpath check must override previously recorded methods, such as a former curl installation after switching to Homebrew. * 🟡 (13:01) `packages/cli/src/lib/upgrade.ts` expanded from 169 to 267+ lines and added source-…

Gen 0 2026-09-10 14:21:32 · 442 tokens

* 🟡 (13:01) `packages/cli/src/lib/release-notes.ts` replaced `GITHUB_RELEASES_URL` with `getGitHubReleasesUrl` and added `PRIMARY_UPGRADE_SOURCE` plus `UpgradeSource`, making release-note retrieval source-aware. * 🟡 (13:01) `packages/cli/src/lib/release-notes.ts` now imports `isNormalizedForSource`, `NormalizedGitHubReleases`, and `normalizeStableReleases` from `./delta-upgrade.js` to normalize…

Gen 0 2026-09-10 14:08:37 · 1139 tokens

* 🟡 (13:00) `packages/cli/src/lib/delta-upgrade.ts` added `semverPrerelease`/`semverValid`, replaced `GITHUB_RELEASES_URL` with `getGitHubReleasesUrl`, and added `PRIMARY_UPGRADE_SOURCE` plus `UpgradeSource` to support source-specific delta discovery. * 🟡 (13:00) `packages/cli/src/lib/delta-upgrade.ts` added the private symbol `NORMALIZED_RELEASE_SOURCE` and logic described as filtering and nor…

Gen 0 2026-09-10 13:46:37 · 2728 tokens

Date: Sep 10, 2026 * 🔴 [enforced-workflow] (12:58) User required an independent READ-ONLY correctness merge gate for repository `/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade`, using literal revisions BASE `ec83887a16f780f32fba4b7d710bad262dba3a22` and HEAD `9525f72c70fabbb23ec21d13cf86add58f6287cc`, with no other revisions. * 🔴 [enforc…