Dashboard › cli › Session 1RVG3swGiqbL
1RVG3swGiqbLRWfPsPerform a read-only, adversarial correctness review of the current uncommitted diff for getsentry/cli issue #1568. Do not edit files. Inspect the actual git diff and relevant surrounding code/tests. Focus on: active credential precedence (stored OAuth vs env sntrys claims, SENTRY_FORCE_ENV_TOKEN); explicit SENTRY_HOST/SENTRY_URL behavior including whitespace; API/control/region routing; manual redirect semantics 301/302/303/307/308; request-body preservation across retries and redirects; authorization and custom header scope; redirect hop cap, malformed/missing Location, timeout/abort behavior; TypeScript/runtime compatibility. Confirm targeted regressions and full suite evidence where available. Return findings first, ordered by severity with exact file:line. For each defect give a deterministic regression. If none, say PASS and name residual test gaps. End with exactly MERGE or DO-NOT-MERGE.
No files found
...output truncated... Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_08af05c27001gOlMcKnNXHBBsb +<!-- lore:01a08336-8dbd-784d-ae91-264c9cc967cf --> +* **Prefers graceful fallback over crashes**: prefer graceful fallback over crashes. -<!-- lore:019f60f0-2bff-7a84-8719-9ad240fffaef --> -* **Call plan\_exit to indicate planning done**: Always call plan\_exit to indicate that planning is done. +<!-- lore:01a088a9-b3b4-785f-9b08-f7006091173a --> +* **Prefers longer command paths over shorter prefixes**: prefers longer command paths over shorter prefixes. -<!-- lore:019fb544-3d62-782c-a2ec-180eef30fd49 --> -* **Diagnose CI failures by reading context thoroughly before applying fixes**: When CI fails on a rebased branch, the user expects a methodical root-cause investigation: read the failing job logs, identify the exact import/module path mismatch (e.g., file moved during rebase but import path not updated, git treating rename as delete+add causing files to ...
M .lore.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/event.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/explore.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/feedback.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/issue.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/log.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/replay.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/span.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/trace.md M packages/cli/src/lib/constants.ts M packages/cli/src/lib/region.ts M packages/cli/src/lib/sentry-client.ts M packages/cli/test/lib/constants.test.ts M packages/cli/test/lib/security/sntrys-claim-mismatch.test.ts M ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentry-client.ts</path>
<type>file</type>
<content>
1: /**
2: * Sentry API Client Configuration
3: *
4: * Provides request configuration for @sentry/api SDK functions,
5: * including authentication, retry logic, timeout, and multi-region support.
6: *
7: * Instead of managing client instances, we pass configuration per-request
8: * through the SDK function options (baseUrl, fetch, headers).
9: */
10:
11: import { setTimeout as sleepMs } from "node:timers/promises";
12: import { getTraceData } from "@sentry/node-core/light";
13: import { maybeWarnEnvTokenIgnored } from "./auth-hint.js";
14: import { computeInvalidationPrefixes } from "./cache-keys.js";
15: import {
16: DEFAULT_SENTRY_URL,
17: getConfiguredSentryUrl,
18: getUserAgent,
19: } from "./constants.js";
20: import {
21: buildTlsErrorDetail,
22: getCustomTlsOptions,
23: ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/constants.ts</path>
<type>file</type>
<content>
1: /**
2: * Runtime constants for the CLI.
3: */
4:
5: import { getEnv } from "./env.js";
6:
7: /** Build-time constant injected by esbuild/bun */
8: declare const SENTRY_CLI_VERSION: string | undefined;
9:
10: /**
11: * Build-time debug ID for sourcemap resolution, injected by esbuild.
12: *
13: * During the build, esbuild's `define` replaces this identifier with a
14: * placeholder UUID string literal. After esbuild finishes, the build
15: * script replaces the placeholder with the real debug ID (derived from
16: * the minified JS + sourcemap content hash). The same-length swap keeps
17: * sourcemap character positions valid.
18: */
19: declare const __SENTRY_DEBUG_ID__: string | undefined;
20:
21: /** Default Sentry SaaS hostname */
22: export const DEFAULT_SENTRY_HOST = "sentry.io";
23:
24: /** ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/token-host.ts</path> <type>file</type> <content> 1: /** 2: * Host-Scoped Token Trust Model 3: * 4: * Tokens (env or stored OAuth) are bound to a specific Sentry host. The fetch 5: * layer (and the `.sentryclirc` / URL-arg entry points) check each request's 6: * destination against the token's recorded host and refuse to attach 7: * credentials when they don't match — so untrusted routing inputs can't leak 8: * credentials to an attacker's host. 9: * 10: * Host equivalence: 11: * - Exact origin match (scheme + host + explicit port). 12: * - SaaS equivalence class: a token scoped to `https://sentry.io` is valid for 13: * any `*.sentry.io` subdomain. Non-SaaS hosts match exactly — no subdomain 14: * suffix matching (a `sentry.acme.com` token does NOT match 15: * `sentry.acme.evil.com`). ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/db/auth.ts</path>
<type>file</type>
<content>
1: /**
2: * Authentication credential storage (single-row table pattern).
3: */
4:
5: import { createHash } from "node:crypto";
6: import { DEFAULT_SENTRY_URL, getConfiguredSentryUrl } from "../constants.js";
7: import { getEnv } from "../env.js";
8: import { getEnvTokenHost } from "../env-token-host.js";
9: import { logger } from "../logger.js";
10: import { normalizeOrigin } from "../sentry-urls.js";
11: import { withDbSpan } from "../telemetry.js";
12: import { getDatabase } from "./index.js";
13: import { clearAllIssueOrgCache } from "./issue-org-cache.js";
14: import { clearTrustedHostState } from "./regions.js";
15: import { runUpsert } from "./utils.js";
16:
17: /** Refresh when less than 10% of token lifetime remains */
18: export const REFRESH_THRESHOLD = 0.1;
19:
20: /** Default token lifetime (1 hour) ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/constants.test.ts</path>
<type>file</type>
<content>
100:
101: test("returns 'production' for stable versions", () => {
102: expect(getCliEnvironment("0.20.0")).toBe("production");
103: expect(getCliEnvironment("1.0.0")).toBe("production");
104: expect(getCliEnvironment("0.23.0")).toBe("production");
105: });
106: });
107:
108: describe("getConfiguredSentryUrl", () => {
109: let originalHost: string | undefined;
110: let originalUrl: string | undefined;
111:
112: beforeEach(() => {
113: originalHost = process.env.SENTRY_HOST;
114: originalUrl = process.env.SENTRY_URL;
115: });
116:
117: afterEach(() => {
118: // Restore original values (set or delete)
119: if (originalHost !== undefined) {
120: process.env.SENTRY_HOST = originalHost;
121: } else {
122: delete process.env.SENTRY_HOST;
123: }
124: ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/custom-headers.ts</path>
<type>file</type>
<content>
1: /**
2: * Custom Headers for Self-Hosted Sentry
3: *
4: * Parses `SENTRY_CUSTOM_HEADERS` env var (or `defaults.headers` from SQLite)
5: * and injects user-specified HTTP headers into all requests to self-hosted
6: * Sentry instances. Designed for environments behind reverse proxies
7: * (e.g., Google IAP, Cloudflare Access) that require extra headers.
8: *
9: * Format: semicolon-separated `Name: Value` pairs (newlines also accepted).
10: *
11: * @example
12: * ```bash
13: * # Single header
14: * SENTRY_CUSTOM_HEADERS="X-IAP-Token: abc123"
15: *
16: * # Multiple headers
17: * SENTRY_CUSTOM_HEADERS="X-IAP-Token: abc123; X-Forwarded-For: 10.0.0.1"
18: *
19: * # Via defaults command
20: * sentry cli defaults headers "X-IAP-Token: abc123"
21: * ```
22: */
23:
24: import { ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/sentry-client.test.ts</path>
<type>file</type>
<content>
1: /**
2: * Tests for the authenticated fetch retry + timeout behavior — CLI-1D6
3: * regression coverage.
4: */
5:
6: import { afterEach, beforeEach, describe, expect, test } from "vitest";
7: import { setAuthToken } from "../../src/lib/db/auth.js";
8: import { TimeoutError } from "../../src/lib/errors.js";
9: import {
10: __injectTimeoutOverrideForTests,
11: __resolveRequestTimeoutMsForTests,
12: getSdkConfig,
13: resetAuthenticatedFetch,
14: } from "../../src/lib/sentry-client.js";
15: import { mockFetch, useTestConfigDir } from "../helpers.js";
16:
17: useTestConfigDir("sentry-client-");
18:
19: let originalFetch: typeof globalThis.fetch;
20: const REGION_URL = "https://us.sentry.io";
21:
22: beforeEach(async () => {
23: originalFetch = globalThis.fetch;
24: // Non-expiring token — ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/errors.ts</path> <type>file</type> <content> 1: /** 2: * CLI Error Hierarchy 3: * 4: * Unified error classes for consistent error handling across the CLI. 5: * 6: * ## Exit Code Ranges 7: * 8: * Each error class maps to a semantic exit code so scripts and agents can 9: * react to failure categories without parsing stderr. Codes are grouped 10: * into decades inspired by HTTP status semantics: 11: * 12: * | Range | Category | HTTP Analogy | 13: * |-------|-------------------|----------------------| 14: * | 0 | Success | 200 OK | 15: * | 1 | General error | 500 Internal | 16: * | 10–19 | Auth & identity | 401/403 | 17: * | 20–29 | Input & config | 400/404/422 | 18: * | 30–39 | API & network | 502/503/504 | 19: * | 40–49 | Feature/billing | ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/package.json</path>
<type>file</type>
<content>
1: {
2: "name": "sentry",
3: "version": "0.40.0-dev.0",
4: "description": "Sentry CLI - A command-line interface for using Sentry built by robots and humans for robots and humans",
5: "repository": {
6: "type": "git",
7: "url": "git+https://github.com/getsentry/cli.git"
8: },
9: "main": "./dist/index.cjs",
10: "type": "module",
11: "types": "./dist/index.d.cts",
12: "exports": {
13: ".": {
14: "import": {
15: "types": "./dist/index.d.mts",
16: "default": "./dist/index.mjs"
17: },
18: "require": {
19: "types": "./dist/index.d.cts",
20: "default": "./dist/index.cjs"
21: }
22: }
23: },
24: "bin": {
25: "sentry": "./dist/bin.cjs"
26: },
27: "files": [
28: "dist/bin.cjs",
29: "dist/index.cjs",
30: ...
{"streamLocked":false,"redirectBodyUsed":false}
{"secondConstruction":"ok","actualBodyUsed":false}
x TypeError: Body is unusable: Body has already been read
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/env-token-host.ts</path> <type>file</type> <content> 1: /** 2: * Env-Token Host Snapshot 3: * 4: * Captures the host an env-var auth token (`SENTRY_AUTH_TOKEN` / 5: * `SENTRY_TOKEN`) is scoped to, BEFORE any post-boot code path can mutate 6: * `env.SENTRY_HOST`/`env.SENTRY_URL` (specifically before 7: * `applySentryCliRcEnvShim` writes from a `.sentryclirc` file). 8: * 9: * Trust model for the snapshot source: 10: * 11: * - `SENTRY_HOST`/`SENTRY_URL` from env are NOT unconditionally trusted. 12: * In layered CI environments (e.g. GitHub Actions `$GITHUB_ENV`), a 13: * low-privilege step can write env vars that a later high-privilege step 14: * inherits — without having read access to `SENTRY_AUTH_TOKEN`. So 15: * env-host and env-token may have different integrity levels. ...
DEPRECATED `test.poolOptions` was removed in Vitest 4. All previous `poolOptions` are now top-level options. Please, refer to the migration guide: https://vitest.dev/guide/migration#pool-rework
[info] [auth] Detected SENTRY_AUTH_TOKEN env var but using stored login for stored OAuth user.
Set SENTRY_FORCE_ENV_TOKEN=1 to prefer the env var.
RUN v4.1.10 /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli
[info] [auth] Detected SENTRY_AUTH_TOKEN env var but using stored login for stored OAuth user.
Set SENTRY_FORCE_ENV_TOKEN=1 to prefer the env var.
Test Files 3 passed (3)
Tests 45 passed (45)
Start at 10:54:49
Duration 10.48s (transform 4.17s, setup 282ms, import 5.01s, tests 9.06s, environment 0ms)
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/region.ts</path>
<type>file</type>
<content>
1: /**
2: * Region resolution for multi-region Sentry support.
3: *
4: * Provides utilities to resolve the correct region URL for an organization,
5: * using cached data when available or fetching from the API when needed.
6: */
7:
8: import { getOrganization } from "@sentry/api";
9: import { getConfiguredSentryUrl } from "./constants.js";
10: import { getOrgByNumericId, getOrgRegion, setOrgRegion } from "./db/regions.js";
11: import { stripDsnOrgPrefix } from "./dsn/index.js";
12: import { withAuthGuard } from "./errors.js";
13: import { getApiBaseUrl, getSdkConfig } from "./sentry-client.js";
14: import { isSentrySaasUrl } from "./sentry-urls.js";
15:
16: /**
17: * Promise cache for org region resolution, keyed by orgSlug. ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentry-urls.ts</path>
<type>file</type>
<content>
1: /**
2: * Sentry URL Utilities
3: *
4: * Utilities for constructing Sentry web URLs.
5: * Supports self-hosted instances via SENTRY_URL environment variable.
6: */
7:
8: import {
9: DEFAULT_SENTRY_HOST,
10: DEFAULT_SENTRY_URL,
11: getConfiguredSentryUrl,
12: normalizeUrl,
13: } from "./constants.js";
14:
15: /**
16: * Get the Sentry web base URL.
17: * Supports self-hosted instances via SENTRY_URL env var.
18: */
19: export function getSentryBaseUrl(): string {
20: return getConfiguredSentryUrl() ?? DEFAULT_SENTRY_URL;
21: }
22:
23: /**
24: * Build the org-scoped base URL using the subdomain pattern.
25: * E.g. "https://sentry.io" + "my-org" → "https://my-org.sentry.io"
26: *
27: * @param orgSlug - Organization slug
28: * @returns Origin URL with org as subdomain
29: */
30: export ...
(no output)
{"redirectBodyUsed":true}
TypeError: Response body object should not be disturbed or locked
{"duplex":"half","method":"POST"}
{"redirectedDuplex":"half","body":"payload"}
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/api/infrastructure.ts</path>
<type>file</type>
<content>
1: /**
2: * API Client Infrastructure
3: *
4: * Shared helpers, types, constants, and raw request functions used by
5: * all domain-specific API modules. This is the foundation layer that
6: * other modules in `src/lib/api/` import from.
7: */
8:
9: import { promisify } from "node:util";
10: import { zstdCompress as zstdCompressCb } from "node:zlib";
11: import { parseSentryLinkHeader } from "@sentry/api";
12: // biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import
13: import * as Sentry from "@sentry/node-core/light";
14: import type { z } from "zod";
15:
16: import { extractRequiredScopes } from "../api-scope.js";
17: import { getActiveEnvVarName, isEnvTokenActive } from "../db/auth.js";
18: import { getEnv } from "../env.js";
19: import { ApiError, AuthError, ...
Found 14 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/region.test.ts:
Line 12: isMultiRegionEnabled,
Line 57: describe("isMultiRegionEnabled", () => {
Line 60: expect(isMultiRegionEnabled()).toBe(true);
Line 65: expect(isMultiRegionEnabled()).toBe(true);
Line 70: expect(isMultiRegionEnabled()).toBe(true);
Line 75: expect(isMultiRegionEnabled()).toBe(false);
Line 80: expect(isMultiRegionEnabled()).toBe(false);
Line 86: expect(isMultiRegionEnabled()).toBe(false);
Line 91: expect(isMultiRegionEnabled()).toBe(false);
Line 96: expect(isMultiRegionEnabled()).toBe(false);
Line 101: expect(isMultiRegionEnabled()).toBe(false);
Line 106: expect(isMultiRegionEnabled()).toBe(false);
Line 112: ...
RUN v4.1.10 /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli
DEPRECATED `test.poolOptions` was removed in Vitest 4. All previous `poolOptions` are now top-level options. Please, refer to the migration guide: https://vitest.dev/guide/migration#pool-rework
[info] [auth] Detected SENTRY_AUTH_TOKEN env var but using stored login for stored OAuth user.
Set SENTRY_FORCE_ENV_TOKEN=1 to prefer the env var.
Test Files 1 passed (1)
Tests 23 passed (23)
Start at 10:58:05
Duration 9.90s (transform 2.19s, setup 107ms, import 2.91s, tests 6.67s, environment 0ms)
Checked 6 files in 1712ms. No fixes applied.
M .lore.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/event.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/explore.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/feedback.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/issue.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/log.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/replay.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/span.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/trace.md M packages/cli/src/lib/constants.ts M packages/cli/src/lib/region.ts M packages/cli/src/lib/sentry-client.ts M packages/cli/test/lib/constants.test.ts M packages/cli/test/lib/security/sntrys-claim-mismatch.test.ts M ...
MUST-FIX — packages/cli/src/lib/sentry-client.ts:410-411, 356-357
A valid fetch(Request, { body: ReadableStream }) request cannot follow 307/308. The redirect snapshot and initial fetch share init.body; native fetch consumes it, then constructing the redirected request from the snapshot throws because its stream is disturbed.
Regression: mock native consumption with new Request(input, init).text(), return a same-origin 307, and assert a second POST receives the original stream payload. Current code rejects instead.
MUST-FIX — packages/cli/src/lib/sentry-client.ts:285, 359-363, 709-711
After a POST receives 301/302, or any non-GET/HEAD request receives 303, the redirect correctly becomes GET but drops every caller header. If that GET receives a retryable response, the outer retry restores the original POST/body while retaining the transformed GET’s headers. It therefore loses Content-Type and other caller headers, commonly causing a 415.
Regression: POST JSON with Content-Type: application/json; return same-origin 302, then 503 from the redirected GET, then assert the retried original POST has its JSON body and original Content-Type. Current code omits the header.
sntrys_ claim; SENTRY_FORCE_ENV_TOKEN switches both routing and bearer token.SENTRY_HOST falls back to SENTRY_URL.pnpm exec tsc --noEmit and targeted Biome checks passed.HEAD 605e8318d1f316bd28f53763f71ed716d1869616, diff SHA-256 70e2e3d4be2d13a4c54badf81fc4f930e403feb2ccd123d5b1099ecb3791facf. ...