DashboardcliSession 1SkBNt97Ze2f

Session 1SkBNt97Ze2f

Full ID: 1SkBNt97Ze2fsD8d9
Messages: 31
Distillations: 0
Time range: 2026-09-10 03:46:03 — 2026-09-10 03:54:28

Conversation (31 messages)

READ-ONLY security/supply-chain merge gate getsentry/cli PR #1569 exact base ec83887a16f780f32fba4b7d710bad262dba3a22 head f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6. Inspect immutable Git objects only (git show head:path, git diff base..head), never worktree contents/state. Repo /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade. Inspect all changed files/tests. Verify prior blockers: exact pins bypass latest; target-driven Homebrew/npm routing; one-off pinned nightly preserves channel while artifact/changelog nightly; stable semantic prerelease exclusion latest/delta/changelog both sources; branded prefetch provenance stable source key/raw rejection; exact stable/nightly metadata; typed 404-only/fail closed; source affinity/canonical pagination/GHCR arbitrary abort reason/scope/redirect/SHA/delta/cache/offline/installer unchanged. Return non-empty findings-first PASS/CONCERN/MUST-FIX/BLOCKED exact head file:line, deterministic regression per defect, residual gaps. Final exactly MERGE or DO-NOT-MERGE. Never modify.

gateway · 2026-09-10 03:46:03
result
<skill_content name="jj-guide">
# Skill: jj-guide

# jj Guide for AI Agents

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

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

## Critical Rules

- **NEVER** use interactive flags (`-i`, `--interactive`). TUI prompts hang in agent environments. This applies to `jj split -i`, `jj squash -i`, `jj commit -i`, `jj resolve`, `jj diffedit`, etc.
- **ALWAYS** pass `-m "msg"` when describing/committing. Without `-m`, an editor opens and hangs.
- **VERIFY** mutations with `jj st` and `jj log` after `squash`, `abandon`, `rebase`, `restore`, `commit`. ...
gateway · 2026-09-10 03:46:27
result
ec83887a16f780f32fba4b7d710bad262dba3a22
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6
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/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
 packages/cli/src/commands/cli/upgrade.ts           | 176 +++++--
 packages/cli/src/lib/binary.ts                     | 154 +++++-
 packages/cli/src/lib/delta-upgrade.ts              | 170 ++++--
 packages/cli/src/lib/ghcr.ts                       | 102 +++-
 ...
gateway · 2026-09-10 03:46:38
result
...output truncated...

Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_0896cb6280019J4hVNXE0yTxr4


<!-- lore:019e6874-7d6f-7fb9-8085-ac2f3472e22f -->
* **sensitive argv flags must never reach telemetry — redactArgv() in cli.ts**: \`SENSITIVE\_ARGV\_FLAGS = new Set(\['token', 'auth-token'])\` in \`src/cli.ts\`. \`redactArgv()\` replaces values of these flags with \`\[REDACTED]\` before any telemetry call. This is an absolute invariant — never pass raw \`process.argv\` to telemetry without running through \`redactArgv()\` first.

<!-- lore:019e610e-c11c-7b34-ae65-02bf3183ae62 -->
* **setup.ts bestEffort() wrapper: post-install steps must never crash setup**: \`src/commands/cli/setup.ts\` \`bestEffort(stepName, fn)\` wraps non-essential post-install steps (recording install info, shell completions, agent skills) in try/catch. On failure: calls \`warn(stepName, error)\` + \`captureException(error, { level: 'warning', tags: { 'setup.step': stepName } })\`. ...
gateway · 2026-09-10 03:46:47
result
diff --git a/packages/cli/src/commands/cli/upgrade.ts b/packages/cli/src/commands/cli/upgrade.ts
index 8d8282368..4e855a1f0 100644
--- a/packages/cli/src/commands/cli/upgrade.ts
+++ b/packages/cli/src/commands/cli/upgrade.ts
@@ -1,414 +1,471 @@
 /**
  * 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 03:46:54
result
diff --git a/packages/cli/src/lib/binary.ts b/packages/cli/src/lib/binary.ts
index 75582612a..61e10568d 100644
--- a/packages/cli/src/lib/binary.ts
+++ b/packages/cli/src/lib/binary.ts
@@ -25,287 +25,429 @@ import {
 } from "./custom-ca.js";
 import { stringifyUnknown, UpgradeError } from "./errors.js";
 import { logger } from "./logger.js";
 import { isProcessRunning } from "./process-utils.js";
 /** Known directories where the curl installer may place the binary */
 export const KNOWN_CURL_DIRS = [".local/bin", "bin", ".sentry/bin"];
 
 /**
  * Whether the current platform's filesystem is case-insensitive by default
  * (Windows, macOS). Resolved once at module load — `process.platform` never
  * changes at runtime.
  */
 const IS_CASE_INSENSITIVE_FS =
   process.platform === "win32" || process.platform === "darwin";
 
 /**
  * Legacy install directory (relative to home) that predates the XDG layout.
  * The curl installer used to drop the binary here; migration moves it out. ...
gateway · 2026-09-10 03:47:03
result
diff --git a/packages/cli/src/lib/upgrade.ts b/packages/cli/src/lib/upgrade.ts
index 945adc48d..4a23b4b25 100644
--- a/packages/cli/src/lib/upgrade.ts
+++ b/packages/cli/src/lib/upgrade.ts
@@ -1,149 +1,238 @@
 /**
  * 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 03:47:17
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,348 +1,441 @@
 /** 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 03:47:29
result
...output truncated...

Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_0896d7cd1001dA5oYTRtJFWAu4

+        ) {
+          return new Response("Not Found", { status: 404 });
+        }
+        if (request.includes("getsentry/cli/releases/tags/88.88.88")) {
+          return new Response(JSON.stringify({ tag_name: "88.88.88" }), {
+            status: 200,
+          });
+        }
+        if (request.includes("getsentry/cli/releases?per_page=30")) {
+          return new Response(JSON.stringify([]), { status: 200 });
+        }
+        return new Response("Unexpected", { status: 500 });
+      });
+
+      const { context, restore } = createMockContext({ homeDir: testDir });
+      restoreStderr = restore;
+
+      await run(
+        app,
+        ["cli", "upgrade", "--check", "--method", "curl", "88.88.88"],
+        context
+      );
+
+      expect(requests).toContain(
+        ...
gateway · 2026-09-10 03:47:40
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
@@ -72,85 +72,85 @@ function mockFetch(
 
 const BINARY_NAME = getPlatformBinaryName();
 
 function versionHex(version: string): string {
   return Array.from(version)
     .map((c) => c.charCodeAt(0).toString(16).padStart(2, "0"))
     .join("");
 }
 
 function tempFile(name: string): string {
   return join(
     tmpdir(),
     `delta-iso-${Date.now()}-${Math.random().toString(36).slice(2)}-${name}`
   );
 }
 
 // ============================================================================
 // resolveStableDelta
 // ============================================================================
 
 describe("resolveStableDelta", () => {
   test("resolves and applies a stable delta patch", async () => {
     // Create a ...
gateway · 2026-09-10 03:47:49
result
diff --git a/packages/cli/test/lib/release-notes.test.ts b/packages/cli/test/lib/release-notes.test.ts
index 78963b471..fb44bc47e 100644
--- a/packages/cli/test/lib/release-notes.test.ts
+++ b/packages/cli/test/lib/release-notes.test.ts
@@ -1,51 +1,57 @@
 /**
  * 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 03:48:06
result
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/lib/delta-upgrade.ts:88:export function isNormalizedForSource(
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/lib/delta-upgrade.ts:100:export function normalizeStableReleases(
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/lib/delta-upgrade.ts:187:    const releases = normalizeStableReleases(data, source);
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/lib/delta-upgrade.ts:237:export async function fetchRecentReleases(
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/lib/delta-upgrade.ts:253:      return normalizeStableReleases([], source);
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/lib/delta-upgrade.ts:258:      return normalizeStableReleases([], source);
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/lib/delta-upgrade.ts:260:    return normalizeStableReleases(data, source);
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/lib/delta-upgrade.ts:263:    ...
gateway · 2026-09-10 03:48:16
result
diff --git a/packages/cli/test/lib/binary.test.ts b/packages/cli/test/lib/binary.test.ts
index 6fcfb3b1a..5eaa53474 100644
--- a/packages/cli/test/lib/binary.test.ts
+++ b/packages/cli/test/lib/binary.test.ts
@@ -1,93 +1,219 @@
 /**
  * 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,
   isMusl,
 ...
gateway · 2026-09-10 03:48:46
result
   144	  resolveExistingUpgradeVersion,
   201	          { tag_name: "cli@99.0.0-dev.1", prerelease: false },
   284	  test("classifies malformed GitHub release pagination as a network error", async () => {
   741	    await expect(resolveExistingUpgradeVersion("1.0.0")).rejects.toThrow(
   751	  ])("rejects pinned Toolkit %s without legacy fallback", async (_name, body) => {
   758	    await expect(resolveExistingUpgradeVersion("1.0.0")).rejects.toMatchObject({
   770	  ])("rejects pinned nightly manifest annotation %s without legacy fallback", async (annotation) => {
   794	      resolveExistingUpgradeVersion("0.14.0-dev.123")
   801	  test("does not fall back from an explicit selected source", async () => {
  1916	  test("does not fall back from a non-404 Toolkit nightly failure", async () => {
  1938	  test("does not fall back when nightly transport error text says HTTP 404", async () => {
gateway · 2026-09-10 03:48:57
result
/usr/bin/bash: line 1: python: command not found
gateway · 2026-09-10 03:49:05
result
   690       async () =>
   691         new Response(JSON.stringify([{ tag_name: "cli@v3.0.0" }]), {
   692           status: 200,
   693           headers: { "Content-Type": "application/json" },
   694         })
   695     );
   696 
   697     const version = await fetchLatestVersion("curl");
   698     expect(version).toBe("3.0.0");
   699   });
   700 });
   701 
   702 describe("versionExists", () => {
   703   test("probes prefixed Toolkit tags and retains the selected source", async () => {
   704     const requests: string[] = [];
   705     mockFetch(async (url) => {
   706       requests.push(String(url));
   707       return new Response(JSON.stringify({ tag_name: "cli@1.0.0" }), {
   708         status: 200,
   709       });
   710     });
   711 
   712     await expect(versionExists("curl", "1.0.0")).resolves.toBe(true);
   713     expect(requests).toEqual([
   714       "https://api.github.com/repos/getsentry/toolkit/releases/tags/cli%401.0.0",
   715     ]);
   716   ...
gateway · 2026-09-10 03:49:16
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
@@ -97,140 +97,134 @@ describe("feedbackCommand.func", () => {
 
     // Sentry is disabled in test environment (no DSN)
     await expect(
       func.call(mockContext, {}, "test", "feedback")
     ).rejects.toThrow("Feedback not sent: telemetry is disabled.");
   });
 });
 
 // Test the upgrade command func
 describe("upgradeCommand.func", () => {
   let originalFetch: typeof globalThis.fetch;
   let restoreStderr: (() => void) | undefined;
 
   beforeEach(() => {
     originalFetch = globalThis.fetch;
   });
 
   afterEach(() => {
     restoreStderr?.();
     restoreStderr = undefined;
     globalThis.fetch = originalFetch;
   });
 
   // Note: We skip testing "unknown installation method" case because
   // detectInstallationMethod() runs actual shell commands (npm ...
gateway · 2026-09-10 03:49:25
result
diff --git a/packages/cli/test/commands/cli/upgrade.test.ts b/packages/cli/test/commands/cli/upgrade.test.ts
index ca9688e46..b67847fb7 100644
--- a/packages/cli/test/commands/cli/upgrade.test.ts
+++ b/packages/cli/test/commands/cli/upgrade.test.ts
@@ -160,30 +160,34 @@ function createMockContext(
       } else {
         process.env.SENTRY_PLAIN_OUTPUT = origPlain;
       }
     },
   };
 }
 
 /**
  * Mock fetch to simulate GHCR manifest returning a specific nightly version.
  * Handles token exchange and manifest fetch.
  */
 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" }), {
         status: 200,
         headers: { "content-type": ...
gateway · 2026-09-10 03:50:31
result
--- packages/cli/src/commands/cli/upgrade.ts ---
   220     };
   221   } catch (error) {
   222     // Automatic offline fallback: only for curl-installed binaries (package
   223     // managers need the network for the actual install, not just version
   224     // discovery), and only for network errors (not version_not_found etc.)
   225     if (
   226       method !== "curl" ||
   227       !(error instanceof UpgradeError && error.reason === "network_error")
   228     ) {
   229       throw error;
   230     }
   231     try {
   232       const target = resolveOfflineTarget(versionArg);
   233       log.warn("Network unavailable, falling back to cached upgrade target");
   234       log.info(`Using cached target: ${target}`);
   235       return { kind: "target", target, offline: "network-fallback" };
   236     } catch {
   237       // No cached version either — re-throw original network error
   238       throw error;
   239     }
   240   }
   241 }
   242 
   243 /**
   ...
gateway · 2026-09-10 03:50:45
result
   128   for (let attempt = 0; attempt <= GHCR_MAX_RETRIES; attempt++) {
   129     try {
   130       const response = await customFetch(url, {
   131         ...init,
   132         signal: buildSignal(timeout, externalSignal),
   133       });
   134       return response;
   135     } catch (error) {
   136       lastError = error instanceof Error ? error : new Error(String(error));
   137       // Propagate external abort immediately — don't retry caller cancellation
   138       if (isExternalAbort(lastError, externalSignal)) {
   139         throw lastError;
   140       }
   141       // Only retry on timeout or network errors — not HTTP errors
   142       if (attempt >= GHCR_MAX_RETRIES || !isRetryableError(lastError)) {
   143         break;
   144       }
   145     }
   146   }
   147 
   148   throw new UpgradeError(
   149     "network_error",
   150     `${context}: ${lastError?.message ?? ...
gateway · 2026-09-10 03:50:58
result
packages/cli/src/lib/db/install-info.ts
packages/cli/test/lib/db/install-info.test.ts
packages/cli/test/lib/install-script.test.ts
gateway · 2026-09-10 03:51:06
result
apps/cli-docs/public/install
apps/cli-docs/src/components/InstallSelector.astro
packages/cli/install
packages/cli/src/commands/cli/uninstall.ts
packages/cli/src/lib/db/install-info.ts
packages/cli/test/commands/cli/uninstall.test.ts
packages/cli/test/lib/db/install-info.test.ts
packages/cli/test/lib/install-script.test.ts
gateway · 2026-09-10 03:51:17
result
apps/cli-docs/public/install c6850ca5ea9254ff1b25a1850d31de63cec527f6
apps/cli-docs/public/install c6850ca5ea9254ff1b25a1850d31de63cec527f6
packages/cli/install e05cef2018d3fb6ddde9dcc1a4d7c47c0db3b3da
packages/cli/install e05cef2018d3fb6ddde9dcc1a4d7c47c0db3b3da
packages/cli/test/lib/install-script.test.ts b1f22bbab8c434e110b1fc59525dda160cccc074
packages/cli/test/lib/install-script.test.ts b1f22bbab8c434e110b1fc59525dda160cccc074
gateway · 2026-09-10 03:51:28
result
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/lib/delta-upgrade.ts:21:  makeCache,
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/lib/patch-cache.ts:2:import { makeCache, type PatchCache, type PatchChain } from "binpatch";
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/lib/patch-cache.ts:10:  return makeCache(join(getConfigDir(), "patch-cache"));
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/lib/upgrade.ts:66:import { clearPatchCache } from "./patch-cache.js";
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/lib/version-check.ts:30:import { cleanupPatchCache } from "./patch-cache.js";
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/test/lib/bspatch.test.ts:523:      `bspatch-cache-${Date.now()}-${Math.random().toString(36).slice(2)}-${name}`
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/test/lib/patch-cache.test.ts:20:} from ...
gateway · 2026-09-10 03:52:00
result
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/lib/binary.ts:490:export async function fetchWithUpgradeError(
   775 }
   776 
   777 async function standaloneVersionExists(
   778   version: string,
   779   source?: UpgradeSource
   780 ): Promise<boolean> {
   781   if (source) {
   782     if (isNightlyVersion(version)) {
   783       return nightlyVersionExists(version, source);
   784     }
   785     const response = await fetchWithUpgradeError(
   786       getGitHubReleaseByTagUrl(version, source),
   787       { headers: getGitHubHeaders() },
   788       "GitHub"
   789     );
   790     if (response.ok) {
   791       return true;
   792     }
   793     if (response.status === 404) {
   794       return false;
   795     }
   796     throw new UpgradeError(
   797       "network_error",
   798       `Failed to fetch from GitHub: HTTP ${response.status}`
   799     );
   800   }
   801   const resolved = await resolveExistingUpgradeVersion(version);
   802   ...
gateway · 2026-09-10 03:52:07
result
   460     if (existsSync(dir) && pathDirs.some((p) => samePath(p, dir))) {
   461       return dir;
   462     }
   463   }
   464 
   465   // 5. XDG-aligned fallback — setup will handle adding this to PATH
   466   return join(homeDir, ".local", "bin");
   467 }
   468 
   469 /**
   470  * Build headers for GitHub API requests.
   471  */
   472 export function getGitHubHeaders(): Record<string, string> {
   473   return {
   474     Accept: "application/vnd.github.v3+json",
   475     "User-Agent": getUserAgent(),
   476   };
   477 }
   478 
   479 /**
   480  * Fetch wrapper that converts network errors to UpgradeError.
   481  * Handles DNS failures, timeouts, and other connection issues.
   482  *
   483  * @param url - URL to fetch
   484  * @param init - Fetch options
   485  * @param serviceName - Service name for error messages (e.g., "GitHub")
   486  * @returns Response object
   487  * @throws {UpgradeError} On network failure
   488  * @throws {Error} AbortError if ...
gateway · 2026-09-10 03:52:29
result
ec83887a16f780f32fba4b7d710bad262dba3a22
f04818a2473a139b9ba1a0a0c94bb9fb80e9cc23788d06cf666c1deabd365f6e  -
gateway · 2026-09-10 03:52:42
result
diff --git a/packages/cli/test/lib/upgrade.test.ts b/packages/cli/test/lib/upgrade.test.ts
index 7c6f63a4b..6cfc7fae9 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,18 +190,175 @@ 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 03:53:10
result
   381	      prefetchedReleases: releases,
   390	  test("normalizes raw prefetched Toolkit releases without fetching", async () => {
   402	      prefetchedReleases: [
   421	  test("rejects unprefixed raw prefetched releases for Toolkit", async () => {
   427	      prefetchedReleases: [
   441	      prefetchedReleases: [
   441	  test("preserves an arbitrary external cancellation reason", async () => {
   447	      controller.abort(reason);
   636	      setReleaseChannel("nightly");
   654	      setReleaseChannel("nightly");
   692	      setReleaseChannel("nightly");
  1259	    setReleaseChannel("nightly");
  1275	  test("validates an npm stable pin through npm while tracking nightly", async () => {
  1281	    setReleaseChannel("nightly");
  1292	    expect(requests).toContain("https://registry.npmjs.org/sentry/1.2.3");
gateway · 2026-09-10 03:53:21
result
  1000     log.debug(`Installation method: ${method}`);
  1001     log.debug(`Current version: ${CLI_VERSION}`);
  1002 
  1003     const resolved = await withProgress(
  1004       { message: "Checking for updates...", json: flags.json },
  1005       async () =>
  1006         resolveTargetWithFallback({
  1007           resolveOpts: { method, channel, versionArg, channelChanged, flags },
  1008           versionArg,
  1009           offline: flags.offline,
  1010           method,
  1011           persistChannelFn: () =>
  1012             persistChannel(channel, channelChanged, version),
  1013         })
  1014     );
  1015     // Early exit for check-only (online) and up-to-date results.
  1016     if (resolved.kind === "done") {
  1017       const result = resolved.result;
  1018       // For --check with a version diff, fetch changelog before returning.
  1019       if (
  1020         result.action === "checked" &&
  1021         result.currentVersion !== ...
gateway · 2026-09-10 03:54:28

MUST-FIX

  1. Raw prefetched releases bypass provenance enforcement
    /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade/packages/cli/src/lib/release-notes.ts:430-438,640-642,746

    prefetchedReleases remains an unbranded GitHubRelease[]. When the source brand is absent or mismatched, normalizeChangelogReleases() reinterprets the raw array under the caller-provided source instead of rejecting it. This defeats the source key added in delta-upgrade.ts:75-120 and leaves release provenance ambiguous. The test at packages/cli/test/lib/release-notes.test.ts:390-419 explicitly preserves the unwanted raw-acceptance behavior.

    Fix: accept NormalizedGitHubReleases and require isNormalizedForSource() before reuse. Return null or fetch from the selected source when the brand is absent or mismatched; never normalize caller-supplied raw prefetched data.

    Regression: pass (a) raw valid Toolkit releases, (b) Toolkit-normalized releases with the legacy source, and (c) legacy-normalized releases with Toolkit. All three must be rejected without fetching or producing a changelog. Same-source branded releases must succeed.

  2. A pinned stable target inherits the persisted nightly changelog channel
    /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade/packages/cli/src/commands/cli/upgrade.ts:276-280,1023-1029,1037-1043

    getArtifactChannel() returns trackingChannel for every non-nightly target. Therefore, while tracking nightly, sentry cli upgrade 1.2.3 correctly routes installation through npm or stable GitHub assets but incorrectly requests a nightly changelog. With a stable target and nightly current version, timestamp parsing normally suppresses the changelog entirely. Artifact behavior must always follow the target; only persisted channel state follows the tracking channel. ...

gpt-5.6-sol · 2026-09-10 03:54:28 · ~1.0K tokens