Dashboard › cli › Session 1QLo0UWM3EmN
1QLo0UWM3EmN2YDvFREAD-ONLY final security/supply-chain gate getsentry/cli PR #1569 exact base ec83887a16f780f32fba4b7d710bad262dba3a22 head a84012184c79c2566c7466aa6beda5f767199218. Immutable Git objects only; never worktree. Repo /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade. MUST return substantive non-empty report. Inspect every changed source/test. Verify response body cancellation identity and transport-vs-malformed classification across GitHub/npm/GHCR token/manifest/tags; strict token and OCI layer/manifest runtime validation; endpoint shapes/exact tags; fail-closed caches; stable SemVer; provenance/channels; 404/source affinity/trusted pagination/scope/redirect/SHA/delta/cache/offline/installer/CodeQL. Findings-first PASS/CONCERN/MUST-FIX/BLOCKED exact head file:line and regressions; residual gaps. Final exactly MERGE or DO-NOT-MERGE. Never modify; empty forbidden.
true commit commit 110d6530e57ca4fbfb2a3f98e52013d2668b5b5e 5f180e7390a7d23a8aad31d24d768d442ffaacbc 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 5b5d53ac76ab3543a9a2f193f3f81d8589c97cc1a5000200e9237a212fdac6a6 -
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_089b806530013WpsFoymMDCKsj
<!-- lore:019f0508-af6e-7c75-8c48-773a426c895a -->
* **symbolic-il2cpp integration tests: use symbolic-testutils dev-dependency with Object::parse pattern**: Integration tests for \`symbolic-il2cpp\` live in \`symbolic-il2cpp/tests/\` (separate from unit tests in \`src/\`). Add \`symbolic-testutils = { path = "../symbolic-testutils" }\` as dev-dependency (path-only, safe for publishing — matches \`symbolic-debuginfo\` pattern). Use \`ByteView::open(fixture("..."))\` → \`Object::parse(\&view)?\` to get a real \`ObjectLike\`. Fixture files live in \`symbolic-testutils/fixtures/\`. Native unit test with mock \`ObjectLike\` rejected as too heavyweight (many methods to implement). PR #1005 added \`from\_object\_with\_provider\_empty\_without\_sources\` and \`from\_object\_with\_provider\_parses\_source\_info\` tests.
<!-- lore:019cbd5f-ec35-7e2d-8386-6d3a67adf0cf ...
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_089b827fe001PU1XpzjhZ9PMVq
*/
async function fetchTagPage(
token: string,
lastTag?: string,
- signal?: AbortSignal
+ signal?: AbortSignal,
+ source: UpgradeSource = PRIMARY_UPGRADE_SOURCE
): Promise<string[]> {
- let url = `${GHCR_REGISTRY}/v2/${GHCR_REPO}/tags/list?n=${TAGS_PAGE_SIZE}`;
+ let url = `${GHCR_REGISTRY}/v2/${source.ghcrRepo}/tags/list?n=${TAGS_PAGE_SIZE}`;
if (lastTag) {
url += `&last=${encodeURIComponent(lastTag)}`;
}
const response = await fetchWithRetry(
url,
{
headers: {
Authorization: `Bearer ${token}`,
"User-Agent": getUserAgent(),
},
},
"Failed to list GHCR tags",
{ signal }
);
if (!response.ok) {
throw new UpgradeError(
"network_error",
`Failed to list GHCR tags: HTTP ${response.status}`
);
}
- const data = (await response.json()) as { tags?: ...
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_089b85061001DdCIYH6ZilrZj9
374 if (!version) {
375 throw new UpgradeError(
376 "network_error",
377 "Nightly manifest has no version annotation"
378 );
379 }
380 if (semverValid(version) === null || !NIGHTLY_VERSION_REGEX.test(version)) {
381 throw new UpgradeError(
382 "network_error",
383 "Nightly manifest has invalid version annotation"
384 );
385 }
386 return version;
387 }
388
389 /**
390 * Find the layer matching a given filename in an OCI manifest.
391 *
392 * ORAS sets `org.opencontainers.image.title` to the filename for each pushed
393 * file. This function searches layers for the matching title annotation.
394 *
395 * @param manifest - OCI manifest containing layers
396 * @param filename - Filename to find (e.g., "sentry-linux-x64.gz")
397 * @returns ...
<path>/home/byk/.local/share/opencode/tool-output/tool_089b85061001DdCIYH6ZilrZj9</path>
<type>file</type>
<content>
1: 1 /**
2: 2 * Binary Management
3: 3 *
4: 4 * Shared utilities for installing, replacing, and managing the CLI binary.
5: 5 * Used by both `setup --install` (fresh installs) and `upgrade` (self-updates).
6: 6 */
7: 7
8: 8 import { spawnSync } from "node:child_process";
9: 9 import {
10: 10 existsSync,
11: 11 mkdirSync,
12: 12 readFileSync,
13: 13 renameSync,
14: 14 unlinkSync,
15: 15 writeFileSync,
16: 16 } from "node:fs";
17: 17 import { chmod, copyFile, mkdir, realpath, unlink } from "node:fs/promises";
18: 18 import { delimiter, dirname, isAbsolute, join, resolve, sep } from "node:path";
19: 19 import { compare as semverCompare } from "semver";
20: 20 import { getUserAgent } from "./constants.js";
21: 21 import {
22: 22 buildTlsErrorDetail,
23: ...
<path>/home/byk/.local/share/opencode/tool-output/tool_089b85061001DdCIYH6ZilrZj9</path>
<type>file</type>
<content>
1090: 322 },
1091: 323 },
1092: 324 `Failed to fetch manifest for tag "${tag}"`,
1093: 325 { signal }
1094: 326 );
1095: 327
1096: 328 if (!response.ok) {
1097: 329 throw new GhcrManifestHttpError(tag, response.status);
1098: 330 }
1099: 331
1100: 332 const data = await parseUpgradeJson(
1101: 333 response,
1102: 334 signal,
1103: 335 `Manifest for tag "${tag}" returned invalid metadata`
1104: 336 );
1105: 337 if (!isOciManifest(data)) {
1106: 338 throw new UpgradeError(
1107: 339 "network_error",
1108: 340 `Manifest for tag "${tag}" returned invalid metadata`
1109: 341 );
1110: 342 }
1111: 343 return data;
1112: 344 }
1113: 345
1114: 346 /**
1115: 347 * Fetch the OCI manifest for the `:nightly` tag. ...
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_089b8aaa700109kjCf1m5KPLhU
targetVersion: target,
channel,
method,
forced: flags.force,
offline: offline ? true : undefined,
warnings,
changelog,
} satisfies UpgradeResult);
return;
},
});
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. ...
<path>/home/byk/.local/share/opencode/tool-output/tool_089b8aaa700109kjCf1m5KPLhU</path> <type>file</type> <content> 1: diff --git a/packages/cli/src/commands/cli/upgrade.ts b/packages/cli/src/commands/cli/upgrade.ts 2: index 8d8282368..b3039d86b 100644 3: --- a/packages/cli/src/commands/cli/upgrade.ts 4: +++ b/packages/cli/src/commands/cli/upgrade.ts 5: @@ -1,394 +1,445 @@ 6: /** 7: * sentry cli upgrade 8: * 9: * Self-update the Sentry CLI to the latest or a specific version. 10: * After upgrading, spawns the NEW binary with `cli setup` to update 11: * completions, agent skills, and record installation metadata. 12: * 13: * Supports two release channels: 14: * - stable (default): tracks the latest GitHub release 15: * - nightly: tracks the rolling nightly prerelease built from main 16: * 17: * The channel can be set via --channel or by passing "nightly"/"stable" 18: * as the version argument. ...
<path>/home/byk/.local/share/opencode/tool-output/tool_089b8aaa700109kjCf1m5KPLhU</path>
<type>file</type>
<content>
690: releaseLock(downloadResult.lockPath);
691: }
692:
693: // Build warnings about the potentially shadowing old installation.
694: // Note: install info is already recorded by the child `setup --install`
695: // process, so no redundant setInstallInfo call is needed here.
696: const uninstallHints: Record<string, string> = {
697: npm: "npm uninstall -g sentry",
698: pnpm: "pnpm remove -g sentry",
699: bun: "bun remove -g sentry",
700: yarn: "yarn global remove sentry",
701: brew: "brew uninstall getsentry/tools/sentry",
702: };
703: const warnings: string[] = [];
704: warnings.push(
705: `Your ${method}-installed sentry may still appear earlier in PATH.`
706: );
707: const hint = uninstallHints[method];
708: if (hint) {
709: warnings.push(`Consider removing it: ${hint}`);
710: }
711: ...
<path>/home/byk/.local/share/opencode/tool-output/tool_089b8aaa700109kjCf1m5KPLhU</path>
<type>file</type>
<content>
940: return yield new CommandOutput(
941: await buildCheckResultWithChangelog({
942: target,
943: versionArg,
944: method,
945: channel,
946: flags,
947: offline,
948: changelogPromise,
949: })
950: );
951: }
952:
953: // Skip if already on target — unless forced or switching channels
954: if (CLI_VERSION === target && !flags.force && !channelChanged) {
955: return yield new CommandOutput({
956: action: "up-to-date",
957: currentVersion: CLI_VERSION,
958: targetVersion: target,
959: channel,
960: method,
961: forced: false,
962: offline: offline ? ...
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_089b92c80001pxuxNj7eBqLJc8
mockFetch(async () => {
throw new TypeError("fetch failed");
});
await expect(getAnonymousToken()).rejects.toThrow(UpgradeError);
await expect(getAnonymousToken()).rejects.toThrow(
"Failed to connect to GHCR: fetch failed"
);
});
+ test("propagates caller cancellation without retrying", async () => {
+ const controller = new AbortController();
+ let requests = 0;
+ mockFetch(async (_url, init) => {
+ requests += 1;
+ return new Promise((_resolve, reject) => {
+ init?.signal?.addEventListener(
+ "abort",
+ () => reject(new DOMException("aborted", "AbortError")),
+ { once: true }
+ );
+ });
+ });
+
+ const request = getAnonymousToken(undefined, controller.signal);
+ controller.abort();
+
+ await expect(request).rejects.toMatchObject({ name: ...
a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/binary.ts:29: UpgradeTransportError, a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/binary.ts:306: /** Optional cancellation signal shared by every source probe. */ a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/binary.ts:329: throw new UpgradeTransportError(buildTlsErrorDetail(error)); a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/binary.ts:331: throw new UpgradeTransportError( a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/binary.ts:512: throw new UpgradeTransportError(buildTlsErrorDetail(error)); a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/binary.ts:515: throw new UpgradeTransportError( a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/binary.ts:521:/** Parse an upgrade response while preserving cancellation and transport failures. */ a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/binary.ts:536: ...
400 expect(result).toBe(customDir);
401 });
402
403 test("SENTRY_INSTALL_DIR takes priority over ~/.local/bin", () => {
404 const localBin = join(testDir, ".local", "bin");
405 mkdirSync(localBin, { recursive: true });
406 const customDir = join(testDir, "custom");
407 mkdirSync(customDir, { recursive: true });
408
409 const result = determineInstallDir(testDir, {
410 SENTRY_INSTALL_DIR: customDir,
411 PATH: `/usr/bin:${localBin}`,
412 });
413
414 expect(result).toBe(customDir);
415 });
416 });
417
418 describe("fetchWithUpgradeError", () => {
419 let originalFetch: typeof globalThis.fetch;
420
421 beforeEach(() => {
422 originalFetch = globalThis.fetch;
423 });
424
425 afterEach(() => {
426 globalThis.fetch = originalFetch;
427 });
428
429 test("returns response on success", async () => {
430 globalThis.fetch = ...
a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/delta-upgrade.ts:237:export async function fetchRecentReleases(
a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/delta-upgrade.ts:369:export async function resolveNightlyChain(opts: {
a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/delta-upgrade.ts:387: (await client.listTags(opts.token, PATCH_TAG_PREFIX, opts.signal));
a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/ghcr.ts:597:export async function listTags(
a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/ghcr.ts:640:export async function downloadLayerBlob(
a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/release-notes.ts:588: * Uses a higher `per_page` than `fetchRecentReleases()` in delta-upgrade
a84012184c79c2566c7466aa6beda5f767199218:packages/cli/test/lib/delta-upgrade.test.ts:844: const result = await ...
a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/delta-upgrade.ts:1:/** Delta upgrade discovery and application backed by binpatch. */ a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/delta-upgrade.ts:8: extractStableChain as binpatchExtractStableChain, a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/delta-upgrade.ts:9: filterAndSortChainTags as binpatchFilterAndSortChainTags, a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/delta-upgrade.ts:10: validateChainStep as binpatchValidateChainStep, a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/delta-upgrade.ts:32:} from "binpatch"; a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/delta-upgrade.ts:57:} from "binpatch"; a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/delta-upgrade.ts:65:} from "binpatch"; a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/delta-upgrade.ts:134: return ...
a84012184c79c2566c7466aa6beda5f767199218:.github/workflows/ci.yml:471: repo: getsentry/cli
a84012184c79c2566c7466aa6beda5f767199218:.github/workflows/ci.yml:554: repo: getsentry/cli
a84012184c79c2566c7466aa6beda5f767199218:.github/workflows/cleanup-nightlies.yml:34: REPO="ghcr.io/getsentry/cli"
560
561 /**
562 * Fetch the latest version from npm registry.
563 *
564 * @returns Latest version string
565 * @throws {UpgradeError} When fetch fails or response is invalid
566 */
567 export async function fetchLatestFromNpm(): Promise<string> {
568 const response = await fetchWithUpgradeError(
569 `${NPM_REGISTRY_URL}/latest`,
570 { headers: { Accept: "application/json" } },
571 "npm registry"
572 );
573
574 if (!response.ok) {
575 throw new UpgradeError(
576 "network_error",
577 `Failed to fetch from npm: ${response.status}`
578 );
579 }
580
581 const ...
.github/workflows/ci.yml .github/workflows/cleanup-nightlies.yml .github/workflows/eval-skill-fork.yml .github/workflows/publish-codemod.yml .github/workflows/release.yml .github/workflows/sentry-release.yml apps/cli-docs/public/install apps/cli-docs/src/components/InstallSelector.astro codemods/sentry-v3-to-v4/workflow.yaml packages/cli/install packages/cli/src/commands/cli/uninstall.ts packages/cli/src/lib/db/install-info.ts packages/cli/src/lib/init/workflow-inputs.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
1 #!/bin/bash
2 set -euo pipefail
3
4 RED='\033[0;31m'
5 MUTED='\033[0;2m'
6 NC='\033[0m'
7
8 # Sentry error telemetry — fire-and-forget error reporting via envelope API.
9 # Uses the CLI's public write-only DSN. No PII collected.
10 # Opt-out: SENTRY_CLI_NO_TELEMETRY=1
11 SENTRY_DSN_KEY="1188a86f3f8168f089450587b00bca66"
12 SENTRY_INGEST="https://o1.ingest.us.sentry.io"
13 SENTRY_PROJECT_ID="4510776311808000"
14
15 # Generate a UUID for the event. Tries /proc, uuidgen, then awk fallback.
16 gen_uuid() {
17 if [[ -r /proc/sys/kernel/random/uuid ]]; then
18 cat /proc/sys/kernel/random/uuid
19 elif command -v uuidgen >/dev/null 2>&1; then
20 uuidgen | tr '[:upper:]' '[:lower:]'
21 else
22 awk 'BEGIN{srand();for(i=1;i<=32;i++)printf "%c",substr("0123456789abcdef",int(rand()*16)+1,1);print ""}'
23 fi
24 }
25
26 # Send an error event to Sentry. ...
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
@@ -115,122 +115,116 @@ describe("upgradeCommand.func", () => {
restoreStderr?.();
restoreStderr = undefined;
globalThis.fetch = originalFetch;
});
// Note: We skip testing "unknown installation method" case because
// detectInstallationMethod() runs actual shell commands (npm list, etc.)
// which can be slow/flaky in CI. The unknown method handling is tested
// indirectly through the upgrade.ts unit tests in lib/upgrade.test.ts.
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": ...
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
@@ -90,49 +90,49 @@ function tempFile(name: string): string {
// ============================================================================
describe("resolveStableDelta", () => {
test("resolves and applies a stable delta patch", async () => {
// Create a "current binary" to patch from
const oldBinaryPath = tempFile("old-binary.bin");
const destPath = tempFile("patched-binary.bin");
writeFileSync(oldBinaryPath, Buffer.from("old binary content for testing"));
// 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
- ...
a84012184c79c2566c7466aa6beda5f767199218:packages/cli/test/commands/cli/upgrade.test.ts:413: test("uses the cached target only after a transport failure", async () => {
a84012184c79c2566c7466aa6beda5f767199218:packages/cli/test/commands/cli/upgrade.test.ts:429: expect(getOutput()).toContain("Using cached target: 88.88.88");
a84012184c79c2566c7466aa6beda5f767199218:packages/cli/test/commands/cli/upgrade.test.ts:432: test("uses the cached target after response body transport failure", async () => {
a84012184c79c2566c7466aa6beda5f767199218:packages/cli/test/commands/cli/upgrade.test.ts:452: expect(getOutput()).toContain("Using cached target: 88.88.88");
a84012184c79c2566c7466aa6beda5f767199218:packages/cli/test/commands/cli/upgrade.test.ts:461: ])("never uses the cached target after %s", async (_name, response) => {
a84012184c79c2566c7466aa6beda5f767199218:packages/cli/test/commands/cli/upgrade.test.ts:479: expect(getOutput()).not.toContain("Using cached ...
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
@@ -18,17 +18,21 @@ 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 {
+ 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 */
export const KNOWN_CURL_DIRS = [".local/bin", "bin", ".sentry/bin"];
/**
* Whether the current platform's ...
a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/upgrade.ts:102:/** GitHub pagination link for the next page. */ a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/upgrade.ts:108:/** Positive GitHub pagination page number. */ a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/upgrade.ts:161: "GitHub returned an invalid release pagination URL" a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/upgrade.ts:173: url.hostname !== "api.github.com" || a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/upgrade.ts:180: "GitHub returned an invalid release pagination URL" a84012184c79c2566c7466aa6beda5f767199218:packages/cli/src/lib/upgrade.ts:530: "GitHub returned cyclic release pagination" a84012184c79c2566c7466aa6beda5f767199218:packages/cli/test/lib/delta-upgrade.test.ts:52:// Test helpers (file-scoped) a84012184c79c2566c7466aa6beda5f767199218:packages/cli/test/lib/delta-upgrade.test.ts:60: browser_download_url: ...
commit commit 110d6530e57ca4fbfb2a3f98e52013d2668b5b5e 5f180e7390a7d23a8aad31d24d768d442ffaacbc 5b5d53ac76ab3543a9a2f193f3f81d8589c97cc1a5000200e9237a212fdac6a6 - 17
versionExists() returns response.ok for npm without distinguishing 404 from 401, 403, 429, or 5xx:
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade/packages/cli/src/lib/upgrade.ts:864-869false into UpgradeError("version_not_found") at packages/cli/src/commands/cli/upgrade.ts:300-305.A registry outage, rate limit, or authorization failure therefore becomes a false claim that the requested version does not exist. This violates the required transport-versus-absence classification.
Fix: return false only for HTTP 404. Return true for 2xx. Throw UpgradeError("network_error") for every other status.
Regression: use npm/pnpm/Bun/Yarn pinned-version command entry points and assert:
version_not_found;network_error;Pinned curl/Homebrew lookup chooses the nightly path only when the string contains -dev.. Every other value, including 1.2.3-beta.1, not-semver, or mcp@1.0.0, reaches GitHub tag resolution:
packages/cli/src/lib/upgrade.ts:748-769packages/cli/src/commands/cli/upgrade.ts:328-336validatePinnedGitHubRelease() checks only exact tag_name equality:
packages/cli/src/lib/upgrade.ts:723-745It never applies validateStableVersion() and never rejects release metadata marked draft or prerelease. This lets the stable artifact path accept a non-stable release whenever the trusted repository exposes the matching tag. Package-manager pins correctly reject prereleases, so behavior also differs by installation method. ...
<observations> * 🟡 (05:17) Code review verdict: `DO-NOT-MERGE`, based on 2 MUST-FIX findings and 3 CONCERN findings. * 🟡 (05:17) MUST-FIX in `packages/cli/src/lib/upgrade.ts:864-869`: npm `versionExists()` returns `response.ok`, conflating HTTP `404` absence with `401`, `403`, `429`, and `5xx` transport/authorization failures; `packages/cli/src/commands/cli/upgrade.ts:300-305` then converts eve…
* 🔴 (05:14) `packages/cli/src/lib/binary.ts` now imports `UpgradeTransportError` alongside `stringifyUnknown` and `UpgradeError` from `./errors.js`. * 🔴 (05:14) Added exported `UpgradeSource` with readonly fields `githubRepo: string`, `ghcrRepo: string`, and `tagPrefix: string`; it represents a repository pair hosting CLI stable GitHub releases and nightly GHCR images. * 🔴 (05:14) Added ordere…
* 🔴 (05:14) `packages/cli/test/lib/delta-upgrade.mocked.test.ts` updated stable delta fixtures from legacy `getsentry/cli` release tags such as `"0.14.0"` and `"0.13.0"` to Toolkit product-prefixed tags `"cli@0.14.0"` and `"cli@0.13.0"`; the patch URL changed from `https://github.com/getsentry/cli/releases/download/0.14.0/${BINARY_NAME}.patch` to `https://github.com/getsentry/toolkit/releases/do…
Date: Sep 10, 2026 * 🔴 (05:13) `packages/cli/test/commands/cli.test.ts` changed the curl upgrade mocks from a single GitHub release object such as `{ tag_name: "v99.0.0" }` to the Toolkit GitHub release-list format `[{ tag_name: "cli@99.0.0" }]`; the associated comment now says curl uses the Toolkit GitHub release list with product-prefixed tags. * 🔴 (05:13) The `"shows installation info with s…
* 🔴 (05:12) `packages/cli/src/lib/delta-upgrade.ts` imports `extractStableChain`, `filterAndSortChainTags`, and `validateChainStep` from `binpatch` under aliases `binpatchExtractStableChain`, `binpatchFilterAndSortChainTags`, and `binpatchValidateChainStep`; `pnpm-lock.yaml` pins `binpatch@0.4.2`. * 🔴 (05:12) `packages/cli/src/lib/delta-upgrade.ts` defines patch cache keys as `patch-chain:${fro…
* 🔴 (05:11) In `packages/cli/test/lib/binary.test.ts`, `determineInstallDir()` tests establish that `SENTRY_INSTALL_DIR` takes priority over `~/.local/bin`. * 🔴 (05:11) In `packages/cli/test/lib/binary.test.ts`, `fetchWithUpgradeError()` tests assert: successful fetch returns HTTP 200; an `AbortError` is re-thrown unchanged and is not an `UpgradeError`; `Error("ECONNREFUSED")` is wrapped as an …
* 🔴 (05:10) User-provided search results are from commit `a84012184c79c2566c7466aa6beda5f767199218`. * 🔴 (05:10) In `packages/cli/src/lib/errors.ts`, `UpgradeTransportError` is exported at line 622 as a subclass of `UpgradeError`, and its constructor sets `this.name = "UpgradeTransportError"` at line 625. * 🔴 (05:10) In `packages/cli/src/lib/binary.ts`, source-probe options document an optiona…
* 🔴 (05:10) User-provided truncated tool output was saved in full at `/home/byk/.local/share/opencode/tool-output/tool_089b92c80001pxuxNj7eBqLJc8`. * 🔴 (05:10) User-provided tests add caller-cancellation coverage for `getAnonymousToken(undefined, controller.signal)`: an `AbortController` abort resulting in `DOMException("aborted", "AbortError")` must propagate with `name: "AbortError"` and perf…
* 🔴 (05:09) User stated package-manager upgrade methods always need network access to fetch and install packages; offline upgrades are therefore limited to cached standalone-binary/delta paths. * 🔴 (05:09) User stated nightly builds are GitHub-only and always use curl (GitHub) lookup for nightly resolution. * 🔴 (05:09) User-provided upgrade documentation defines `sentry cli upgrade nightly` as…
* 🔴 (05:09) User-provided code in `packages/cli/src/lib/delta-upgrade.ts` changes `resolveStableChain(currentVersion, targetVersion, signal?, source)` to accept `source: UpgradeSource = getPrimaryUpgradeSource()` and call `stableSource(source).resolveChain(...)`, making stable delta-chain resolution upgrade-source-specific. * 🔴 (05:09) User-provided `packages/cli/src/lib/delta-upgrade.ts` impor…
* 🔴 (05:09) User-provided code specifies stripping a trailing path separator, but never from a bare root like `/`. * 🔴 (05:09) User-provided upgrade-source resolver returns the successful probe response so the caller never repeats the request. * 🔴 (05:09) User-provided code defines ordered CLI release sources; the resolver falls through to the next source only on HTTP 404. * 🔴 (05:09) User-pr…
Date: Sep 10, 2026 * 🔴 (05:09) User requires always passing `field` when constructing the referenced error; unfielded errors violate its predictable shape. * 🔴 (05:09) User requires `AuthError` to always be re-thrown so the auto-login flow can trigger. * 🔴 (05:09) User requires `HostScopeError` to always be re-thrown so host-scoping rejections surface to the user. * 🔴 (05:09) User stated the …
* 🔴 (05:09) User requires installation-method detection to always check Homebrew first because stored install information may be stale; the cheap, authoritative realpath check must override stale records, such as when a user switched from a previously recorded curl install to Homebrew. * 🔴 (05:09) User requires binary visibility verification to fail when the downloaded file never becomes visibl…
Date: Sep 10, 2026 * 🔴 [requested-review] (05:08) User requested a READ-ONLY final security/supply-chain gate for getsentry/cli PR #1569 at exact base `ec83887a16f780f32fba4b7d710bad262dba3a22` and head `a84012184c79c2566c7466aa6beda5f767199218`, using immutable Git objects only and never the worktree. Repository path: `/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f3…