Dashboard › cli › Session 1cmTSVKbRpJe
1cmTSVKbRpJeOPjc1READ-ONLY correctness 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 every changed file/test. Verify all prior blockers: exact pins bypass latest; target-driven Homebrew/npm routing; one-off pinned nightly preserves tracking channel while nightly changelog/source; stable semantic prereleases excluded in latest/delta/recent/changelog for Toolkit+legacy; branded normalized prefetch provenance structural source key; raw cross-source data rejected; exact stable/nightly metadata; 404-only/affinity/pagination/GHCR arbitrary? abort any reason, redirect auth, SHA/delta/cache/offline/API compatibility. 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.
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 +++- packages/cli/src/lib/release-notes.ts | 132 +++-- ...
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_0896c5cec001cf2q007zUcPJY0
<!-- lore:01a02480-839e-7ba6-9717-24d7ddc0e709 -->
* **Large artifacts pipeline createWriteStream**: Large artifacts are always written with \`pipeline()\` into \`createWriteStream()\`, rather than buffering the complete payload in memory. Buffering looks simpler and may work for small fixtures, but it creates avoidable memory peaks and weakens backpressure for shipped CLI workloads.
<!-- lore:019f03b9-97f4-7bb6-aeba-27fa5aeca79b -->
* **Node version pinning convention: workflow-level env vars NODE\_VERSION\_22 / NODE\_VERSION\_24**: As of PR #1145, all GitHub Actions workflows in sentry-cli (TypeScript) centralize Node version pins as workflow-level \`env\` vars: \`NODE\_VERSION\_22: "22.23.1"\` and \`NODE\_VERSION\_24: "24.18.0"\`. All \`actions/setup-node\` steps reference \`${{ env.NODE\_VERSION\_22 }}\` or \`${{ env.NODE\_VERSION\_24 }}\` — no bare ...
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. ...
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,
...
diff --git a/packages/cli/src/lib/release-notes.ts b/packages/cli/src/lib/release-notes.ts index ddf617535..43d238abe 100644 --- a/packages/cli/src/lib/release-notes.ts +++ b/packages/cli/src/lib/release-notes.ts @@ -1,102 +1,108 @@ /** * 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()`. ...
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,
...
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(
...
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,103 +1,229 @@
/**
* 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,
...
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
@@ -67,95 +67,95 @@ function mockFetch(
}
// ============================================================================
// Helpers
// ============================================================================
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
// ...
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,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 ...
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
@@ -98,201 +98,350 @@ const { spawnImpl } = vi.hoisted(() => ({
fn: (() => {
// placeholder — replaced per-test
}) as (cmd: string, args: string[], opts: object) => FakeProc,
},
}));
// 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,
...
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/commands/cli/upgrade.ts:307: if (!(await versionExists(lookupMethod, target))) {
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/commands/cli/upgrade.ts:898: return fetchChangelog({
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/lib/delta-upgrade.ts:237:export async function fetchRecentReleases(
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/lib/delta-upgrade.ts:387: (await client.listTags(opts.token, PATCH_TAG_PREFIX, opts.signal));
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/lib/delta-upgrade.ts:401: chainTags.map((tag) => client.fetchManifest(opts.token, tag, opts.signal))
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/lib/delta-upgrade.ts:618:export function attemptDeltaUpgrade(
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/src/lib/ghcr.ts:228:export async function ...
300 /** Fetch implementation used for the probe. Defaults to the CLI CA-aware fetch. */
301 readonly fetch?: typeof fetch;
302 /** Optional cancellation signal shared by every source probe. */
303 readonly signal?: AbortSignal;
304 /** Ordered sources to probe. Defaults to all configured upgrade sources. */
305 readonly sources?: readonly UpgradeSource[];
306 };
307
308 async function fetchUpgradeProbe(
309 source: UpgradeSource,
310 options: ResolveUpgradeSourceOptions
311 ): Promise<Response> {
312 try {
313 return await (options.fetch ?? customFetch)(options.getProbeUrl(source), {
314 headers: getGitHubHeaders(),
315 signal: options.signal,
316 });
317 } catch (error) {
318 if (error instanceof Error && error.name === "AbortError") {
319 throw error;
320 }
321 if (error instanceof Error && isTlsCertError(error)) {
322 throw new ...
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/test/commands/cli/upgrade.test.ts:216: if (urlStr.includes("getsentry/toolkit/releases?per_page=100")) {
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/test/commands/cli/upgrade.test.ts:358: test("resolves a pinned check target from its exact source", async () => {
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/test/commands/cli/upgrade.test.ts:373: if (request.includes("getsentry/cli/releases?per_page=30")) {
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/test/commands/cli/upgrade.test.ts:395: "https://api.github.com/repos/getsentry/cli/releases?per_page=30"
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/test/commands/cli/upgrade.test.ts:398: "https://api.github.com/repos/getsentry/toolkit/releases?per_page=30"
f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6:packages/cli/test/commands/cli/upgrade.test.ts:401: requests.every((request) => ...
Your prior result was empty and cannot satisfy the merge gate. Continue the SAME read-only review of exact base ec83887a16f780f32fba4b7d710bad262dba3a22 and head f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6 using immutable Git objects only. Return a substantive non-empty findings-first report now. If no defects, state PASS with exact head file:line evidence for each requested boundary and residual test gaps. If tools fail, state BLOCKED with the exact command/error. Classify each point PASS/CONCERN/MUST-FIX/BLOCKED. Final line exactly MERGE or DO-NOT-MERGE. Never return empty.
f04818a2473a139b9ba1a0a0c94bb9fb80e9cc23788d06cf666c1deabd365f6e -
880
881 /**
882 * Start a best-effort changelog fetch in parallel with the binary download.
883 *
884 * Returns a promise that resolves to the changelog or undefined. Never
885 * throws — errors are swallowed so the upgrade is not blocked.
886 */
887 function startChangelogFetch(options: {
888 channel: ReleaseChannel;
889 currentVersion: string;
890 targetVersion: string;
891 offline: OfflineMode;
892 source?: UpgradeSource;
893 }): Promise<ChangelogSummary | undefined> {
894 const { channel, currentVersion, targetVersion, offline, source } = options;
895 if (offline || currentVersion === targetVersion) {
896 return Promise.resolve(undefined);
897 }
898 return fetchChangelog({
899 channel,
900 fromVersion: currentVersion,
901 toVersion: targetVersion,
902 source,
903 })
904 .then((result) => ...
The patch preserves arbitrary abort reasons for direct blob downloads, but not for GHCR token/manifest/tag requests or GitHub source probes.
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade/packages/cli/src/lib/ghcr.ts:135-140 converts every non-Error rejection into a new Error before comparing it with externalSignal.reason. If a caller uses controller.abort("cancelled") or another non-Error reason, the identity check can never succeed. The code may retry and finally throws UpgradeError instead of the caller’s reason./home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade/packages/cli/src/lib/binary.ts:317-327 preserves only errors named AbortError. An external abort with Error("cancelled"), a string, or an object is wrapped as a network failure./home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade/packages/cli/src/lib/upgrade.ts:594-596 replaces an already-aborted signal’s reason with a new AbortError.rethrowExternalAbort() before conversion at ghcr.ts:400-405 and ghcr.ts:442-448.Impact: cancellation does not always propagate unchanged and can be misclassified as a network failure or retried. This violates the requested “abort any reason” contract and can delay shutdown.
Fix: before normalizing caught values, check externalSignal?.aborted and throw externalSignal.reason unchanged. Apply the same rule in fetchUpgradeProbe(). For an already-aborted signal, throw signal.reason rather than constructing AbortError.
Deterministic regressions:
* 🔴 (03:52) User directed: “Switch to nightly channel and update.” * 🟡 (03:52) Assistant classified the merge gate as `MUST-FIX` because caller abort reasons are not preserved across all discovery paths: direct blob downloads preserve arbitrary reasons, but GHCR token/manifest/tag requests and GitHub source probes may normalize, retry, or wrap them. * 🟡 (03:52) Assistant identified `/home/byk/…
* 🔴 (03:51) [requested-review] User required continuation of the SAME read-only merge-gate review comparing exact base `ec83887a16f780f32fba4b7d710bad262dba3a22` with head `f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6`, using immutable Git objects only. * 🔴 (03:51) User required a substantive, non-empty, findings-first report because the prior empty result could not satisfy the merge gate. * 🔴 (03…
* 🔴 (03:50) User’s source-resolution design returns the successful probe `Response` to the caller so it never repeats the successful probe request. * 🔴 (03:50) User specified that CA-file handling “Never throws — a missing CA file shouldn't crash the CLI.” * 🔴 (03:50) User’s `fetchUpgradeProbe(source, options)` uses `options.fetch ?? customFetch`, calls `options.getProbeUrl(source)`, supplies …
* 🔴 (03:49) User expanded `packages/cli/test/lib/upgrade.test.ts`, including source-aware stable and nightly upgrade coverage. * 🔴 (03:49) User added `UPGRADE_SOURCES` to imports from `../../src/lib/binary.js` in `packages/cli/test/lib/upgrade.test.ts`. * 🔴 (03:49) User added `resolveExistingUpgradeVersion` to the dynamically imported functions from `../../src/lib/upgrade.js`. * 🔴 (03:49) Use…
* 🔴 (03:47) User updated `packages/cli/test/lib/delta-upgrade.mocked.test.ts` stable-delta fixtures so release tags use Toolkit package prefixes: `"cli@0.14.0"` and `"cli@0.13.0"` replace `"0.14.0"` and `"0.13.0"`. * 🔴 (03:47) User changed the expected stable-release API repository in `packages/cli/test/lib/delta-upgrade.mocked.test.ts` from `api.github.com/repos/getsentry/cli/releases` to `api…
* 🔴 (03:47) User supplied expanded tests in `packages/cli/test/lib/binary.test.ts`, increasing the shown binary-management test section from 103 to 229 lines. * 🔴 (03:47) User added imports for `getGitHubReleaseByTagUrl`, `resolveUpgradeSource`, `UPGRADE_SOURCES`, and `UpgradeSourceNotFoundError` from `../../src/lib/binary.js`. * 🔴 (03:47) User changed `getBinaryDownloadUrl("1.0.0")` expectati…
* 🔴 (03:47) User supplied changes to `packages/cli/test/commands/cli.test.ts` covering the upgraded source-aware GitHub release behavior, nightly channel behavior, version validation, installation methods, command execution, and error handling. * 🔴 (03:47) User updated the `upgradeCommand.func` test mock from a single GitHub release object `{ tag_name: "v0.0.0-dev" }` to a release-list response…
* 🔴 (03:47) User supplied changes to `packages/cli/src/lib/upgrade.ts`, expanding the upgrade module from 149 to 238 lines in the shown initial hunk and adding source-aware GitHub upgrade handling, release pagination/filtering, nightly-version handling, and binary-download verification. * 🔴 (03:47) User added `prerelease as semverPrerelease` and `valid as semverValid` imports from `semver`, plu…
* 🔴 (03:46) User supplied changes to `packages/cli/src/lib/release-notes.ts` replacing the fixed `GITHUB_RELEASES_URL` dependency with source-aware imports `getGitHubReleasesUrl`, `PRIMARY_UPGRADE_SOURCE`, and `UpgradeSource` from `./binary.js`. * 🔴 (03:46) User supplied changes to `packages/cli/src/lib/release-notes.ts` importing `isNormalizedForSource` and `normalizeStableReleases` alongside …
Date: Sep 10, 2026 * 🔴 (03:46) User supplied changes to `packages/cli/src/lib/delta-upgrade.ts` adding source-aware delta-upgrade behavior: imports now include `getGitHubReleasesUrl`, `PRIMARY_UPGRADE_SOURCE`, and `UpgradeSource` from `./binary.js`; the fixed `GITHUB_RELEASES_URL` and `GHCR_REPO` imports were removed. * 🔴 (03:46) User supplied changes importing `prerelease as semverPrerelease` …
* 🔴 (03:46) User stated package managers always need network access to fetch and install packages; offline upgrade mode is supported only for curl-installed binaries. * 🔴 (03:46) User stated nightly builds are GitHub-only, so nightly version existence checks always use the curl/GitHub lookup regardless of the current installation method. * 🔴 (03:46) User stated trailing path separators should …
Date: Sep 10, 2026 * 🔴 [requested-review] (03:45) User requested a READ-ONLY correctness merge gate for getsentry/cli PR #1569 at exact base `ec83887a16f780f32fba4b7d710bad262dba3a22` and head `f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6`, using repo `/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade`. * 🔴 [enforced-workflow] (03:45) User requ…