Dashboard › cli › Session 0F4SZd0XgbTz
0F4SZd0XgbTzj1F9CREAD-ONLY final security/supply-chain gate getsentry/cli PR #1569 exact base ec83887a16f780f32fba4b7d710bad262dba3a22 head 7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7. Immutable Git objects only; never worktree. Repo /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade. MUST return substantive non-empty report. Inspect all changed files/tests. Verify all prior blockers: pagination stage exact abort reason; transport-only cache fallback and fail-closed HTTP/metadata; stable package manager SemVer; arbitrary abort all paths; strict branded prefetch provenance; target-driven channels; semantic stable filtering; exact metadata; 404/source affinity/trusted pagination/GHCR scope+redirect/SHA/delta/cache/offline/installer; exact-origin CodeQL assertions. Findings-first PASS/CONCERN/MUST-FIX/BLOCKED with exact head file:line, deterministic regression per defect, residual gaps. Final exactly MERGE or DO-NOT-MERGE. Never modify; empty forbidden.
commit commit ec83887a16f780f32fba4b7d710bad262dba3a22 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 packages/cli/src/commands/cli/upgrade.ts | 180 ++++-- packages/cli/src/lib/binary.ts | 170 +++++- packages/cli/src/lib/delta-upgrade.ts | 170 ++++-- packages/cli/src/lib/errors.ts | 8 ...
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_089959c31001Bnv7zDW2Wr5IKI
<!-- 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 } })\`. ...
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_08995d09f0010n07ucNjnbusHV
/** Changelog summary for the version range. Absent for offline or on fetch failure. */
changelog?: ChangelogSummary;
};
type UpgradeFlags = {
readonly check: boolean;
readonly force: boolean;
readonly offline: boolean;
readonly "no-agent-skills": boolean;
readonly method?: InstallationMethod;
/** Injected by buildCommand output wrapper — suppresses spinners */
readonly json?: boolean;
};
/**
* Resolve effective channel and version arg from the positional `version`
* parameter. "nightly" and "stable" are treated as channel selectors, not
* literal version strings.
*
* @returns `{ channel, versionArg }` where versionArg is undefined when the
* positional was a channel name (so we resolve to latest) or was omitted. ...
1 /**
2 * Binary Management
3 *
4 * Shared utilities for installing, replacing, and managing the CLI binary.
5 * Used by both `setup --install` (fresh installs) and `upgrade` (self-updates).
6 */
7
8 import { spawnSync } from "node:child_process";
9 import {
10 existsSync,
11 mkdirSync,
12 readFileSync,
13 renameSync,
14 unlinkSync,
15 writeFileSync,
16 } from "node:fs";
17 import { chmod, copyFile, mkdir, realpath, unlink } from "node:fs/promises";
18 import { delimiter, dirname, isAbsolute, join, resolve, sep } from "node:path";
19 import { compare as semverCompare } from "semver";
20 import { getUserAgent } from "./constants.js";
21 import {
22 buildTlsErrorDetail,
23 customFetch,
24 isTlsCertError,
25 } from "./custom-ca.js";
26 import {
27 stringifyUnknown,
28 UpgradeError,
29 UpgradeTransportError,
30 } from ...
1 /**
2 * GHCR (GitHub Container Registry) Client
3 *
4 * Encapsulates the OCI download protocol for fetching nightly CLI binaries
5 * from ghcr.io/getsentry/cli. Nightly builds are pushed as OCI artifacts
6 * via ORAS with the version baked into the manifest annotation.
7 *
8 * Key design decisions:
9 * - Anonymous access: nightly package is public; no token needed beyond the
10 * standard ghcr.io anonymous token exchange.
11 * - Version discovery from manifest annotation: `annotations.version` in the
12 * OCI manifest holds the nightly version. Checking the latest version only
13 * requires a token exchange + manifest fetch (2 HTTP requests total).
14 * - Redirect quirk: ghcr.io blob downloads return 307 to Azure Blob Storage.
15 * Using `fetch` with `redirect: "follow"` would forward the Authorization
16 * header to Azure, which returns 404. ...
1 /** Delta upgrade discovery and application backed by binpatch. */
2
3 import { join } from "node:path";
4 // biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import
5 import * as Sentry from "@sentry/node-core/light";
6 import {
7 applyPatchChainInMemory,
8 extractStableChain as binpatchExtractStableChain,
9 filterAndSortChainTags as binpatchFilterAndSortChainTags,
10 validateChainStep as binpatchValidateChainStep,
11 type DeltaTelemetry,
12 type DeltaUnavailableReason,
13 type ExtractStableChainOpts,
14 type GitHubRelease,
15 getPatchFromVersion,
16 getPatchTargetSha256,
17 ghcrSource,
18 githubReleaseSource,
19 type InstrumentHook,
20 MAX_NIGHTLY_CHAIN_DEPTH,
21 makeCache,
22 OciClient,
23 type OciManifest,
24 PATCH_TAG_PREFIX,
25 type PatchCache,
26 type PatchChain,
27 type ...
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_089968af9001x01OqC2nSN18J0
163 url.protocol !== "https:" ||
164 url.hostname !== "api.github.com" ||
165 !(isSelectedSourcePath || isCanonicalRepositoryPath) ||
166 page === null ||
167 !PAGE_NUMBER_REGEX.test(page)
168 ) {
169 throw new UpgradeError(
170 "network_error",
171 "GitHub returned an invalid release pagination URL"
172 );
173 }
174 const nextPage = new URL(getGitHubLatestReleaseUrl(source));
175 nextPage.searchParams.set("page", page);
176 return nextPage.href;
177 }
178
179 // Curl Binary Helpers
180
181 /**
182 * Build the list of known curl install directories the binary may live in,
183 * each with a trailing separator so `startsWith` matches a directory boundary
184 * (e.g. `~/.local/bin/` won't match `~/.local/binaries/`). ...
/usr/bin/bash: line 1: python: command not found
1 /**
2 * Upgrade Module
3 *
4 * Detects how the CLI was installed and provides self-upgrade functionality.
5 * Binary management helpers (download URLs, locking, replacement) live in
6 * binary.ts and are shared with the setup --install flow.
7 */
8
9 import { spawn } from "node:child_process";
10 import {
11 chmodSync,
12 closeSync,
13 existsSync,
14 openSync,
15 realpathSync,
16 statSync,
17 unlinkSync,
18 writeSync,
19 } from "node:fs";
20 import { writeFile } from "node:fs/promises";
21 import { homedir } from "node:os";
22 import { dirname, isAbsolute, join, sep } from "node:path";
23 import { setTimeout } from "node:timers/promises";
24 import { prerelease as semverPrerelease, valid as semverValid } from "semver";
25 import {
26 acquireLock,
27 cleanupOldBinary,
28 compareVersions,
29 determineInstallDir,
30 ...
1 /**
2 * Release Notes Parser & Aggregation
3 *
4 * Extracts user-facing changelog entries from GitHub Release bodies (stable)
5 * or conventional commit messages (nightly). Uses `marked.lexer()` for
6 * AST-based section extraction and produces structured data that can be
7 * re-serialized as filtered markdown for rendering via `renderMarkdown()`.
8 *
9 * Only three categories are kept — everything else is filtered out:
10 * - **New Features** (✨) — from `### New Features` sections or `feat:` commits
11 * - **Bug Fixes** (🐛) — from `### Bug Fixes` sections or `fix:` commits
12 * - **Performance** (⚡) — from `### Performance` sections or `perf:` commits
13 */
14
15 import { marked, type Token, type Tokens } from "marked";
16 import {
17 compareVersions,
18 getGitHubHeaders,
19 getGitHubReleasesUrl,
20 PRIMARY_UPGRADE_SOURCE,
21 type UpgradeSource,
22 } from ...
1 /**
2 * sentry cli upgrade
3 *
4 * Self-update the Sentry CLI to the latest or a specific version.
5 * After upgrading, spawns the NEW binary with `cli setup` to update
6 * completions, agent skills, and record installation metadata.
7 *
8 * Supports two release channels:
9 * - stable (default): tracks the latest GitHub release
10 * - nightly: tracks the rolling nightly prerelease built from main
11 *
12 * The channel can be set via --channel or by passing "nightly"/"stable"
13 * as the version argument. The choice is persisted in the local database
14 * so that subsequent bare `sentry cli upgrade` calls use the same channel.
15 */
16
17 import { spawn } from "node:child_process";
18 import { homedir } from "node:os";
19 import { dirname, join } from "node:path";
20 import { setTimeout } from "node:timers/promises";
21 import type { SentryContext } from "../../context.js";
...
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_0899789c0001OS072qRR6ZleCG
+ return new Response(JSON.stringify(manifest), { status: 200 });
+ });
+
+ await expect(
+ fetchNightlyManifest("token", undefined, UPGRADE_SOURCES[0])
+ ).resolves.toEqual(manifest);
+ });
+
test("throws UpgradeError on HTTP error", async () => {
mockFetch(async () => new Response("Not Found", { status: 404 }));
await expect(fetchNightlyManifest("token")).rejects.toThrow(UpgradeError);
await expect(fetchNightlyManifest("token")).rejects.toThrow(
'Failed to fetch manifest for tag "nightly": HTTP 404'
);
});
test("throws UpgradeError on network failure", async () => {
mockFetch(async () => {
throw new TypeError("fetch failed");
});
await expect(fetchNightlyManifest("token")).rejects.toThrow(UpgradeError);
await expect(fetchNightlyManifest("token")).rejects.toThrow(
...
501 return new Response(JSON.stringify(manifest), {
502 status: 200,
503 headers: {
504 "Content-Type": "application/vnd.oci.image.manifest.v1+json",
505 },
506 });
507 });
508
509 const result = await fetchManifest("token", "patch-0.13.0");
510 expect(result).toEqual(manifest);
511 });
512
513 test("throws UpgradeError on HTTP 404", async () => {
514 mockFetch(async () => new Response("Not Found", { status: 404 }));
515
516 const error = await fetchManifest("token", "patch-0.13.0").catch(
517 (reason: unknown) => reason
518 );
519 expect(error).toBeInstanceOf(GhcrManifestHttpError);
520 expect(error).toMatchObject({
521 name: "GhcrManifestHttpError",
522 status: 404,
523 message: 'Failed to fetch manifest for tag "patch-0.13.0": HTTP 404',
524 });
525 });
526
527 test("throws UpgradeError ...
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 ...
diff --git a/packages/cli/test/lib/binary.test.ts b/packages/cli/test/lib/binary.test.ts
index 6fcfb3b1a..be462848c 100644
--- a/packages/cli/test/lib/binary.test.ts
+++ b/packages/cli/test/lib/binary.test.ts
@@ -3,86 +3,212 @@
*
* 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,
releaseLock,
...
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,46 +1,52 @@
/**
* 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 ...
7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7 fix(cli): preserve paginated request cancellation 878459c490576dbb7abb76d3a3dc1b5d9fc62c1f fix(cli): fail closed on upgrade metadata e22f0eba66298a7228a988a4adcf1ec5be85227b fix(cli): preserve source and cancellation provenance dcf6719995800d28d6f88a8c709abe4aa3d0b6db test(cli): validate GitHub request origin f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6 fix(cli): preserve pinned upgrade contracts e565b1afb39a341605adc883de8812851de7d581 fix(cli): validate stable upgrade metadata 4a1d5bad105c006193c68459175f5eb3c7f78e08 test(cli): update pinned version fixture 2f9b5d0d26124d445a916b8bfc584ad518720584 fix(cli): isolate pinned upgrade resolution c6030fbf4d0c190f037c86d832edb6680d3e54ab fix(cli): bind nightly manifests to versions b6484e0fb1eb0f8bce68272d538f5c3b26cd5bd5 fix(cli): validate resolved upgrade metadata c8c815bbecf472061ec02ab96d1a31f23f078d01 fix(cli): validate GitHub release pagination 5ffa5cd0fe02c0a5dc5bec9a8a099f4135c0b447 fix(cli): ...
7d62ffa83 fix(cli): preserve paginated request cancellation
packages/cli/src/lib/binary.ts | 3 +++
packages/cli/test/lib/upgrade.test.ts | 21 +++++++++++++++++++++
2 files changed, 24 insertions(+)
commit 7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7
Author: Burak Yigit Kaya <byk@sentry.io>
AuthorDate: Thu Sep 10 04:29:35 2026 +0000
Commit: Burak Yigit Kaya <byk@sentry.io>
CommitDate: Thu Sep 10 04:29:35 2026 +0000
fix(cli): preserve paginated request cancellation
diff --git a/packages/cli/src/lib/binary.ts b/packages/cli/src/lib/binary.ts
index 994626cfd..8a96e9361 100644
--- a/packages/cli/src/lib/binary.ts
+++ b/packages/cli/src/lib/binary.ts
@@ -501,6 +501,9 @@ export async function fetchWithUpgradeError(
try {
return await customFetch(url, init);
} catch (error) {
+ if (init.signal?.aborted) {
+ throw init.signal.reason;
+ }
// Re-throw AbortError as-is so callers can handle it specifically
if (error instanceof Error && ...
878459c49 fix(cli): fail closed on upgrade metadata packages/cli/src/commands/cli/upgrade.ts | 7 +--- packages/cli/src/lib/binary.ts | 16 ++++---- packages/cli/src/lib/errors.ts | 8 ++++ packages/cli/src/lib/ghcr.ts | 7 ++-- packages/cli/src/lib/upgrade.ts | 22 +++++++++-- packages/cli/test/commands/cli/upgrade.test.ts | 55 +++++++++++++++++++++++++- packages/cli/test/lib/ghcr.test.ts | 7 ++-- packages/cli/test/lib/upgrade.test.ts | 33 ++++++++++++++++ 8 files changed, 130 insertions(+), 25 deletions(-) e22f0eba6 fix(cli): preserve source and cancellation provenance packages/cli/src/commands/cli/upgrade.ts | 11 +++------ packages/cli/src/lib/binary.ts | 3 +++ packages/cli/src/lib/ghcr.ts | 6 ++--- packages/cli/src/lib/release-notes.ts | 14 +++++------ packages/cli/src/lib/upgrade.ts | 4 +-- ...
commit 878459c490576dbb7abb76d3a3dc1b5d9fc62c1f
Author: Burak Yigit Kaya <byk@sentry.io>
AuthorDate: Thu Sep 10 04:18:39 2026 +0000
Commit: Burak Yigit Kaya <byk@sentry.io>
CommitDate: Thu Sep 10 04:18:39 2026 +0000
fix(cli): fail closed on upgrade metadata
diff --git a/packages/cli/src/commands/cli/upgrade.ts b/packages/cli/src/commands/cli/upgrade.ts
index 39aa97928..b3039d86b 100644
--- a/packages/cli/src/commands/cli/upgrade.ts
+++ b/packages/cli/src/commands/cli/upgrade.ts
@@ -36,7 +36,7 @@ import {
setReleaseChannel,
} from "../../lib/db/release-channel.js";
import { getVersionCheckInfo } from "../../lib/db/version-check.js";
-import { UpgradeError } from "../../lib/errors.js";
+import { UpgradeError, UpgradeTransportError } from "../../lib/errors.js";
import { formatUpgradeResult } from "../../lib/formatters/human.js";
import { formatBytes } from "../../lib/formatters/numbers.js";
import { CommandOutput } from "../../lib/formatters/output.js";
@@ -222,10 ...
ceec3a6247d023c1f55ac9f7342ad07ee3f14b26 0000000000000000000000000000000000000000 7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7:packages/cli/test/commands/cli/upgrade.test.ts:46:/** Store original fetch for restoration */ 7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7:packages/cli/test/commands/cli/upgrade.test.ts:47:let originalFetch: typeof globalThis.fetch; 7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7:packages/cli/test/commands/cli/upgrade.test.ts:288: originalFetch = globalThis.fetch; 7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7:packages/cli/test/commands/cli/upgrade.test.ts:294: globalThis.fetch = originalFetch; 7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7:packages/cli/test/commands/cli/upgrade.test.ts:655: originalFetch = globalThis.fetch; 7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7:packages/cli/test/commands/cli/upgrade.test.ts:661: globalThis.fetch = originalFetch; 7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7:packages/cli/test/commands/cli/upgrade.test.ts:852: originalFetch = ...
1881 )
1882 ).toBe(true);
1883
1884 // Clean up is fire-and-forget async, so we need to wait a bit
1885 startCleanupOldBinary();
1886 await sleep(50);
1887
1888 // File should be gone
1889 expect(
1890 await access(oldPath).then(
1891 () => true,
1892 () => false
1893 )
1894 ).toBe(false);
1895 });
1896
1897 // Note: cleanupOldBinary intentionally does NOT clean up .download files
1898 // because an upgrade may be in progress in another process. The .download
1899 // cleanup is handled inside the upgrade flow under the exclusive lock.
1900
1901 test("does not throw if files do not exist", () => {
1902 // Ensure files don't exist by attempting cleanup first
1903 startCleanupOldBinary();
1904
1905 // Should not throw when called again
1906 expect(() => startCleanupOldBinary()).not.toThrow();
1907 });
1908 });
1909
...
.github/workflows/publish-codemod.yml
.github/workflows/release.yml
.github/workflows/sentry-release.yml
packages/cli/src/commands/cli/upgrade.ts
packages/cli/src/lib/delta-upgrade.ts
packages/cli/src/lib/upgrade.ts
packages/cli/test/commands/cli/upgrade.test.ts
packages/cli/test/e2e/delta-upgrade.test.ts
packages/cli/test/lib/delta-upgrade.mocked.test.ts
packages/cli/test/lib/delta-upgrade.test.ts
packages/cli/test/lib/upgrade.test.ts
7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7:.github/workflows/ci.yml:470: registry: ghcr.io
7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7:.github/workflows/ci.yml:471: repo: getsentry/cli
7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7:.github/workflows/ci.yml:553: registry: ghcr.io
7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7:.github/workflows/ci.yml:554: repo: getsentry/cli
7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7:.github/workflows/cleanup-nightlies.yml:28: run: echo "${{ secrets.GITHUB_TOKEN }}" | oras login ghcr.io -u ${{ ...
commit f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6
Author: Burak Yigit Kaya <byk@sentry.io>
AuthorDate: Thu Sep 10 03:45:04 2026 +0000
Commit: Burak Yigit Kaya <byk@sentry.io>
CommitDate: Thu Sep 10 03:45:04 2026 +0000
fix(cli): preserve pinned upgrade contracts
diff --git a/packages/cli/src/lib/delta-upgrade.ts b/packages/cli/src/lib/delta-upgrade.ts
index 37f4b7d54..204875dce 100644
--- a/packages/cli/src/lib/delta-upgrade.ts
+++ b/packages/cli/src/lib/delta-upgrade.ts
@@ -30,6 +30,7 @@ import {
type SourceStrategy,
type StableChainInfo,
} from "binpatch";
+import { prerelease as semverPrerelease, valid as semverValid } from "semver";
import {
compareVersions,
getGitHubReleasesUrl,
@@ -71,6 +72,54 @@ export type DeltaResult = {
const log = logger.withTag("delta-upgrade");
+const NORMALIZED_RELEASE_SOURCE = Symbol("normalizedReleaseSource");
+
+/** Stable GitHub releases normalized for one explicit upgrade source. ...
(no output)
commit commit 110d6530e57ca4fbfb2a3f98e52013d2668b5b5e b291f0ba3965767a808fa1eba1129f8dea9b427e b07e44a7bdf5101f1a4e09a23949467e77e5055105c9f0d03d3f293ab9566259 - 17
The source-specific resolver shares one cache keyed only by version range:
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade/packages/cli/src/lib/delta-upgrade.ts:133-135 builds keys from only fromVersion and toVersion..../packages/cli/src/lib/delta-upgrade.ts:172-174 creates one global patch-cache..../packages/cli/src/lib/delta-upgrade.ts:566-575 supplies that cache to every SourceStrategy..../packages/cli/src/lib/delta-upgrade.ts:638-644 selects Toolkit or legacy network resolution while retaining the shared cache..../packages/cli/src/lib/delta-upgrade.ts:696-724 prefetches source-specific chains into the same unbranded cache.A chain prefetched from getsentry/cli can therefore satisfy a later Toolkit-selected upgrade without consulting Toolkit. The cached chain’s own expected SHA authenticates the legacy result, not the artifact selected from Toolkit. This breaks strict source affinity and becomes a supply-chain boundary if the repositories ever publish different artifacts. Automatic transport fallback compounds this because the offline path has no persisted source provenance and defaults to the primary source.
Fix: namespace cache storage by the complete immutable source key (githubRepo, ghcrRepo, and tagPrefix) or store and verify equivalent source provenance in every cached chain. Persist the source alongside cached version information so explicit and automatic offline resolution select the matching cache namespace.
Deterministic regression: using an isolated config directory and a real valid patch fixture:
attemptDeltaUpgrade() for the same pair with Toolkit selected and offline: true.* 🔴 (04:39) User-provided tool output listed commits `110d6530e57ca4fbfb2a3f98e52013d2668b5b5e` and `b291f0ba3965767a808fa1eba1129f8dea9b427e`, followed by `b07e44a7bdf5101f1a4e09a23949467e77e5055105c9f0d03d3f293ab9566259 -` and the exact count `17`. * 🟡 (04:39) Assistant identified a MUST-FIX source-affinity defect in `packages/cli/src/lib/delta-upgrade.ts`: patch-cache keys at lines 133–135 …
* 🔴 (04:35) User-provided `packages/cli/test/lib/upgrade.test.ts` cleanup behavior: `startCleanupOldBinary()` is fire-and-forget, so the test waits `sleep(50)` before verifying `oldPath` is gone; calling it when files do not exist, including a second invocation, must not throw. * 🔴 (04:35) User-provided cleanup contract: `cleanupOldBinary` intentionally does not remove `.download` files because…
* 🔴 (04:35) User-provided commit `878459c490576dbb7abb76d3a3dc1b5d9fc62c1f` (`fix(cli): fail closed on upgrade metadata`), authored and committed by Burak Yigit Kaya `<byk@sentry.io>`, changes 8 files with 130 insertions and 25 deletions. (meaning Sep 10, 2026) * 🔴 (04:35) User-provided commit-size details: `e22f0eba6` changes 10 files with 87 insertions and 38 deletions; `dcf671999` changes 1 …
* 🔴 (04:34) User-provided `packages/cli/test/lib/binary.test.ts` changes update `getBinaryDownloadUrl("1.0.0")` expectations from `/1.0.0/` under `https://github.com/getsentry/cli/releases/download/` to `/cli@1.0.0/` under `https://github.com/getsentry/toolkit/releases/download/`. * 🔴 (04:34) User-provided binary tests add coverage for `getGitHubReleaseByTagUrl`, `resolveUpgradeSource`, `UPGRAD…
* 🔴 (04:34) User-provided changes in `packages/cli/test/commands/cli.test.ts` update stable-release mocks from `{ tag_name: "v0.0.0-dev" }` to an array fixture `[{ tag_name: "cli@1.0.0" }]`. * 🔴 (04:34) User-provided upgrade command test was renamed from `"check mode shows already on target when versions match"` to `"check mode compares the current version with a stable target"`. * 🔴 (04:34) U…
* 🔴 (04:33) User-provided tests add `fetchNightlyManifest("token", undefined, UPGRADE_SOURCES[0])` coverage, confirming an explicitly selected upgrade source can return the nightly OCI manifest. * 🔴 (04:33) User-provided `getNightlyVersion()` tests reject nightly manifest version annotations `"not-semver"`, `"1.2.3"`, and `"1.2.3-dev.foo"` with `"Nightly manifest has invalid version annotation"…
Date: Sep 10, 2026 * 🔴 (04:33) User-provided `sentry cli upgrade` implementation self-updates Sentry CLI to the latest or a specific version, then spawns the new binary with `cli setup` to update completions and agent skills and record installation metadata. * 🔴 (04:33) User-provided upgrade command supports 2 release channels: `stable` (default, tracks the latest GitHub release) and `nightly` …
* 🔴 (04:33) User-provided release-notes parser uses `marked.lexer()` for AST-based section extraction and produces structured data that can be re-serialized as filtered markdown for `renderMarkdown()`. * 🔴 (04:33) User-provided release-notes parser keeps only 3 user-facing categories: **New Features** (✨) from `### New Features` sections or `feat:` commits; **Bug Fixes** (🐛) from `### Bug Fixe…
Date: Sep 10, 2026 * 🔴 (04:32) User-provided package-manager detection logic must always check for Homebrew first because stored install information may be stale. * 🔴 (04:32) User-provided file-waiting logic throws `UpgradeError` when the file never becomes visible or stays empty. * 🔴 (04:32) User-provided GitHub pagination validation requires HTTPS, hostname `api.github.com`, either the selec…
Date: Sep 10, 2026 * 🔴 (04:32) User-provided module is described as “Delta upgrade discovery and application backed by binpatch.” * 🔴 (04:32) User-provided delta-upgrade code imports `join` from `node:path` and uses a namespace import, `import * as Sentry from "@sentry/node-core/light"`, with the comment `biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import`. …
* 🔴 (04:31) User-provided binary-management code specifies that a trailing path separator is stripped, but never from a bare root such as `/`. * 🔴 (04:31) User-provided binary-management code specifies that the caller receives the successful probe response so it never repeats the successful probe request. * 🔴 (04:31) User-provided binary-management module is shared by `setup --install` for fre…
* 🔴 (04:31) User stated package managers always need network to fetch and install packages; `--offline` upgrades are supported only for curl-installed binaries, and `validateMethod()` must throw `UpgradeError("unsupported_operation", "Offline upgrade is only supported for curl-installed binaries.")` when `offline` is true and `method !== "curl"`. * 🔴 (04:31) User requires nightly builds and nig…
Date: September 10, 2026 * 🔴 (04:30) User requires the final security/supply-chain gate for getsentry/cli PR #1569 to be strictly READ-ONLY: use immutable Git objects only, never use the worktree, never modify anything, and never return an empty report. * 🟡 [requested-review] (04:30) User requested review of getsentry/cli PR #1569 at exact base `ec83887a16f780f32fba4b7d710bad262dba3a22` and exa…