Skip to content

Commit e72074a

Browse files
betegonclaude
andcommitted
merge: resolve conflicts from main into feat/span-commands
Move listSpans from monolithic api-client.ts into api/traces.ts module to align with main's refactored re-export structure. Keep span_id display without null coalescing (TraceSpan.span_id is required). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2 parents 1111a40 + 224e6dc commit e72074a

File tree

127 files changed

+13184
-6261
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

127 files changed

+13184
-6261
lines changed

AGENTS.md

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,31 @@ Use traditional unit tests only when:
589589
- Testing error messages or specific output formatting
590590
- Integration with external systems (E2E tests)
591591

592+
### Avoiding Unit/Property Test Duplication
593+
594+
When a `*.property.test.ts` file exists for a module, **do not add unit tests that re-check the same invariants** with hardcoded examples. Before adding a unit test, check whether the companion property file already generates random inputs for that invariant.
595+
596+
**Unit tests that belong alongside property tests:**
597+
- Edge cases outside the property generator's range (e.g., self-hosted DSNs when the arbitrary only produces SaaS ones)
598+
- Specific output format documentation (exact strings, column layouts, rendered vs plain mode)
599+
- Concurrency/timing behavior that property tests cannot express
600+
- Integration tests exercising multiple functions together (e.g., `writeJsonList` envelope shape)
601+
602+
**Unit tests to avoid when property tests exist:**
603+
- "returns true for valid input" / "returns false for invalid input"the property test already covers this with random inputs
604+
- Basic round-trip assertionsproperty tests check `decode(encode(x)) === x` for all `x`
605+
- Hardcoded examples of invariants like idempotency, symmetry, or subset relationships
606+
607+
When adding property tests for a function that already has unit tests, **remove the unit tests that become redundant**. Add a header comment to the unit test file noting which invariants live in the property file:
608+
609+
```typescript
610+
/**
611+
* Note: Core invariants (round-trips, validation, ordering) are tested via
612+
* property-based tests in foo.property.test.ts. These tests focus on edge
613+
* cases and specific output formatting not covered by property generators.
614+
*/
615+
```
616+
592617
```typescript
593618
import { describe, expect, test, mock } from "bun:test";
594619

@@ -628,6 +653,9 @@ mock.module("./some-module", () => ({
628653
629654
### Architecture
630655
656+
<!-- lore:019ce2be-39f1-7ad9-a4c5-4506b62f689c -->
657+
* **api-client.ts split into domain modules under src/lib/api/**: The original monolithic \`src/lib/api-client.ts\` (1,977 lines) was split into 12 focused domain modules under \`src/lib/api/\`: infrastructure.ts (shared helpers, types, raw requests), organizations.ts, projects.ts, teams.ts, repositories.ts, issues.ts, events.ts, traces.ts, logs.ts, seer.ts, trials.ts, users.ts. The original \`api-client.ts\` was converted to a ~100-line barrel re-export file preserving all existing import paths. The \`biome.jsonc\` override for \`noBarrelFile\` already includes \`api-client.ts\`. When adding new API functions, place them in the appropriate domain module under \`src/lib/api/\`, not in the barrel file.
658+
631659
<!-- lore:019cb8ea-c6f0-75d8-bda7-e32b4e217f92 -->
632660
* **CLI telemetry DSN is public write-only — safe to embed in install script**: The CLI's Sentry DSN (\`SENTRY\_CLI\_DSN\` in \`src/lib/constants.ts\`) is a public write-only ingest key already baked into every binary. Safe to hardcode in install scripts. Opt-out: \`SENTRY\_CLI\_NO\_TELEMETRY=1\`.
633661
@@ -643,10 +671,10 @@ mock.module("./some-module", () => ({
643671
<!-- lore:019c972c-9f0f-75cd-9e24-9bdbb1ac03d6 -->
644672
* **Numeric issue ID resolution returns org:undefined despite API success**: Numeric issue ID resolution in \`resolveNumericIssue()\`: (1) try DSN/env/config for org, (2) if found use \`getIssueInOrg(org, id)\` with region routing, (3) else fall back to unscoped \`getIssue(id)\`, (4) extract org from \`issue.permalink\` via \`parseSentryUrl\` as final fallback. \`parseSentryUrl\` handles path-based (\`/organizations/{org}/...\`) and subdomain-style URLs. \`matchSubdomainOrg()\` filters region subdomains by requiring slug length > 2. Self-hosted uses path-based only.
645673
646-
### Decision
674+
<!-- lore:019ce0bb-f35d-7380-b661-8dc56f9938cf -->
675+
* **Seer trial prompt uses middleware layering in bin.ts error handling chain**: The CLI's error recovery middlewares in \`bin.ts\` are layered: \`main() → executeWithAutoAuth() → executeWithSeerTrialPrompt() → runCommand()\`. Seer trial prompts (for \`no\_budget\`/\`not\_enabled\` errors) are caught by the inner wrapper; auth errors bubble up to the outer wrapper. After successful auth login retry, the retry also goes through \`executeWithSeerTrialPrompt\` (not \`runCommand\` directly) so the full middleware chain applies. Trial check API: \`GET /api/0/customers/{org}/\`\`productTrials\[]\` (prefer \`seerUsers\`, fallback \`seerAutofix\`). Start trial: \`PUT /api/0/customers/{org}/product-trial/\`. The \`/customers/\` endpoint is getsentry SaaS-only; self-hosted 404s gracefully. \`ai\_disabled\` errors are excluded (admin's explicit choice). \`startSeerTrial\` accepts \`category\` from the trial object — don't hardcode it.
647676
648-
<!-- lore:019cc2ef-9be5-722d-bc9f-b07a8197eeed -->
649-
* **All view subcommands should use \<target> \<id> positional pattern**: All \`\* view\` subcommands should follow a consistent \`\<target> \<id>\` positional argument pattern where target is the optional \`org/project\` specifier. During migration, use opportunistic argument swapping with a stderr warning when args are in wrong order. This is an instance of the broader CLI UX auto-correction pattern: safe when input is already invalid, correction is unambiguous, warning goes to stderr. Normalize at command level, keep parsers pure. Model after \`gh\` CLI conventions.
677+
### Decision
650678
651679
<!-- lore:019c99d5-69f2-74eb-8c86-411f8512801d -->
652680
* **Raw markdown output for non-interactive terminals, rendered for TTY**: Markdown-first output pipeline: custom renderer in \`src/lib/formatters/markdown.ts\` walks \`marked\` tokens to produce ANSI-styled output. Commands build CommonMark using helpers (\`mdKvTable()\`, \`mdRow()\`, \`colorTag()\`, \`escapeMarkdownCell()\`, \`safeCodeSpan()\`) and pass through \`renderMarkdown()\`. \`isPlainOutput()\` precedence: \`SENTRY\_PLAIN\_OUTPUT\` > \`NO\_COLOR\` > \`FORCE\_COLOR\` > \`!isTTY\`. \`--json\` always outputs JSON. Colors defined in \`COLORS\` object in \`colors.ts\`. Tests run non-TTY so assertions match raw CommonMark; use \`stripAnsi()\` helper for rendered-mode assertions.
@@ -659,15 +687,9 @@ mock.module("./some-module", () => ({
659687
<!-- lore:019c8ab6-d119-7365-9359-98ecf464b704 -->
660688
* **@sentry/api SDK passes Request object to custom fetch — headers lost on Node.js**: @sentry/api SDK calls \`\_fetch(request)\` with no init object. In \`authenticatedFetch\`, \`init\` is undefined so \`prepareHeaders\` creates empty headers — on Node.js this strips Content-Type (HTTP 415). Fix: fall back to \`input.headers\` when \`init\` is undefined. Use \`unwrapPaginatedResult\` (not \`unwrapResult\`) to access the Response's Link header for pagination. \`per\_page\` is not in SDK types; cast query to pass it at runtime.
661689
662-
<!-- lore:019cc484-f0e1-7016-a851-177fb9ad2cc4 -->
663-
* **AGENTS.md must be excluded from markdown linters**: AGENTS.md is auto-managed by lore and uses \`\*\` list markers and long lines that violate typical remark-lint rules (unordered-list-marker-style, maximum-line-length). When a project uses remark with \`--frail\` (warnings become errors), AGENTS.md will fail CI. Fix: add \`AGENTS.md\` to \`.remarkignore\`. This applies to any lore-managed project with markdown linting.
664-
665690
<!-- lore:019c9e98-7af4-7e25-95f4-fc06f7abf564 -->
666691
* **Bun binary build requires SENTRY\_CLIENT\_ID env var**: The build script (\`script/bundle.ts\`) requires \`SENTRY\_CLIENT\_ID\` environment variable and exits with code 1 if missing. When building locally, use \`bun run --env-file=.env.local build\` or set the env var explicitly. The binary build (\`bun run build\`) also needs it. Without it you get: \`Error: SENTRY\_CLIENT\_ID environment variable is required.\`
667692
668-
<!-- lore:019cc40e-e56e-71e9-bc5d-545f97df732b -->
669-
* **Consola prompt cancel returns truthy Symbol, not false**: When a user cancels a \`consola\` / \`@clack/prompts\` confirmation prompt (Ctrl+C), the return value is \`Symbol(clack:cancel)\`, not \`false\`. Since Symbols are truthy in JavaScript, checking \`!confirmed\` will be \`false\` and the code falls through as if the user confirmed. Fix: use \`confirmed !== true\` (strict equality) instead of \`!confirmed\` to correctly handle cancel, false, and any other non-true values.
670-
671693
<!-- lore:019c9776-e3dd-7632-88b8-358a19506218 -->
672694
* **GitHub immutable releases prevent rolling nightly tag pattern**: getsentry/cli has immutable GitHub releases — assets can't be modified and tags can NEVER be reused. Nightly builds publish to GHCR with versioned tags like \`nightly-0.14.0-dev.1772661724\`, not GitHub Releases or npm. \`fetchManifest()\` throws \`UpgradeError("network\_error")\` for both network failures and non-200 — callers must check message for HTTP 404/403. Craft with no \`preReleaseCommand\` silently skips \`bump-version.sh\` if only target is \`github\`.
673695
@@ -680,9 +702,6 @@ mock.module("./some-module", () => ({
680702
<!-- lore:019c9741-d78e-73b1-87c2-e360ef6c7475 -->
681703
* **useTestConfigDir without isolateProjectRoot causes DSN scanning of repo tree**: \`useTestConfigDir()\` creates temp dirs under \`.test-tmp/\` in the repo tree. Without \`{ isolateProjectRoot: true }\`, \`findProjectRoot\` walks up and finds the repo's \`.git\`, causing DSN detection to scan real source code and trigger network calls against test mocks (timeouts). Always pass \`isolateProjectRoot: true\` when tests exercise \`resolveOrg\`, \`detectDsn\`, or \`findProjectRoot\`.
682704
683-
<!-- lore:019cc303-e397-75b9-9762-6f6ad108f50a -->
684-
* **Zod z.coerce.number() converts null to 0 silently**: Zod gotchas in this codebase: (1) \`z.coerce.number()\` passes input through \`Number()\`, so \`null\` silently becomes \`0\`. Be aware if \`null\` vs \`0\` distinction matters. (2) Zod v4 \`.default({})\` short-circuits — it returns the default value without parsing through inner schema defaults. So \`.object({ enabled: z.boolean().default(true) }).default({})\` returns \`{}\`, not \`{ enabled: true }\`. Fix: provide fully-populated default objects. This affected nested config sections in src/config.ts during the v3→v4 upgrade.
685-
686705
### Pattern
687706
688707
<!-- lore:019c972c-9f11-7c0d-96ce-3f8cc2641175 -->

DEVELOPMENT.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,11 @@ When creating your Sentry OAuth application:
6767

6868
## Environment Variables
6969

70-
| Variable | Description | Default |
71-
| ------------------ | ------------------------------------ | -------------------- |
72-
| `SENTRY_CLIENT_ID` | Sentry OAuth app client ID | (required) |
73-
| `SENTRY_URL` | Sentry instance URL (for self-hosted)| `https://sentry.io` |
70+
| Variable | Description | Default |
71+
| ------------------ | ----------------------------------------------------- | -------------------- |
72+
| `SENTRY_CLIENT_ID` | Sentry OAuth app client ID | (required) |
73+
| `SENTRY_HOST` | Sentry instance URL (for self-hosted, takes precedence) | `https://sentry.io` |
74+
| `SENTRY_URL` | Alias for `SENTRY_HOST` | `https://sentry.io` |
7475

7576
## Building
7677

biome.jsonc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@
5656
},
5757
{
5858
// db/index.ts exports db connection utilities - not a barrel file but triggers the rule
59-
"includes": ["src/lib/db/index.ts"],
59+
// api-client.ts is a barrel that re-exports from src/lib/api/ domain modules
60+
// to preserve the existing import path for all consumers
61+
"includes": ["src/lib/db/index.ts", "src/lib/api-client.ts"],
6062
"linter": {
6163
"rules": {
6264
"performance": {

docs/src/content/docs/commands/api.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,20 @@ sentry api /organizations/ \
8585
--header "X-Custom-Header:value"
8686
```
8787

88-
### Show Response Headers
88+
### Verbose Mode
8989

9090
```bash
91-
sentry api /organizations/ --include
91+
sentry api /organizations/ --verbose
9292
```
9393

94-
```
95-
HTTP/2 200
96-
content-type: application/json
97-
x-sentry-rate-limit-remaining: 95
94+
Request and response metadata is logged to stderr:
9895

96+
```
97+
> GET /api/0/organizations/
98+
>
99+
< HTTP 200
100+
< content-type: application/json
101+
<
99102
[{"slug": "my-org", ...}]
100103
```
101104

docs/src/content/docs/commands/auth.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -84,18 +84,18 @@ sentry auth refresh
8484

8585
This is typically handled automatically when tokens expire.
8686

87-
## Configuration
87+
## Credential Storage
8888

89-
Credentials are stored in `~/.sentry/config.json` with restricted file permissions (mode 600).
89+
Credentials are stored in a SQLite database at `~/.sentry/cli.db` with restricted file permissions (mode 600).
9090

91-
**Config structure:**
91+
Use `sentry auth token` to retrieve your current access token, or `sentry auth status` to check authentication state.
9292

93-
```json
94-
{
95-
"auth": {
96-
"token": "...",
97-
"refreshToken": "...",
98-
"expiresAt": "2024-12-31T00:00:00Z"
99-
}
100-
}
101-
```
93+
### Environment Variable Precedence
94+
95+
The CLI checks for auth tokens in the following order, using the first one found:
96+
97+
1. `SENTRY_AUTH_TOKEN` environment variable (legacy)
98+
2. `SENTRY_TOKEN` environment variable
99+
3. The stored token in the SQLite database
100+
101+
When a token comes from an environment variable, the CLI skips expiry checks and automatic refresh.

docs/src/content/docs/configuration.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,22 @@ The Sentry CLI can be configured through environment variables and a local datab
77

88
## Environment Variables
99

10-
### `SENTRY_URL`
10+
### `SENTRY_HOST`
1111

1212
Base URL of your Sentry instance. **Only needed for [self-hosted Sentry](./self-hosted/).** SaaS users (sentry.io) should not set this.
1313

1414
```bash
15-
export SENTRY_URL=https://sentry.example.com
15+
export SENTRY_HOST=https://sentry.example.com
1616
```
1717

1818
When set, all API requests (including OAuth login) are directed to this URL instead of `https://sentry.io`. The CLI also sets this automatically when you pass a self-hosted Sentry URL as a command argument.
1919

20+
`SENTRY_HOST` takes precedence over `SENTRY_URL`. Both work identically — use whichever you prefer.
21+
22+
### `SENTRY_URL`
23+
24+
Alias for `SENTRY_HOST`. If both are set, `SENTRY_HOST` takes precedence.
25+
2026
### `SENTRY_ORG`
2127

2228
Default organization slug. Skips organization auto-detection.
@@ -138,9 +144,11 @@ The `sentry api` command also uses `--verbose` to show full HTTP request/respons
138144

139145
## Credential Storage
140146

141-
Credentials are stored in a SQLite database at `~/.sentry/` (or the path set by `SENTRY_CONFIG_DIR`) with restricted file permissions (mode 600) for security. The database also caches:
147+
We store credentials and caches in a SQLite database (`cli.db`) inside the config directory (`~/.sentry/` by default, overridable via `SENTRY_CONFIG_DIR`). The database file and its WAL side-files are created with restricted permissions (mode 600) so that only the current user can read them. The database also caches:
142148

143149
- Organization and project defaults
144150
- DSN resolution results
145151
- Region URL mappings
146152
- Project aliases (for monorepo support)
153+
154+
See [Credential Storage](./commands/auth/#credential-storage) in the auth command docs for more details.

docs/src/content/docs/self-hosted.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ title: Self-Hosted Sentry
33
description: Using the Sentry CLI with a self-hosted Sentry instance
44
---
55

6-
The CLI works with self-hosted Sentry instances. Set the `SENTRY_URL` environment variable to point at your instance:
6+
The CLI works with self-hosted Sentry instances. Set the `SENTRY_HOST` (or `SENTRY_URL`) environment variable to point at your instance:
77

88
```bash
9-
export SENTRY_URL=https://sentry.example.com
9+
export SENTRY_HOST=https://sentry.example.com
1010
```
1111

1212
## Authenticating
@@ -27,14 +27,14 @@ The OAuth device flow requires **Sentry 26.1.0 or later** and a public OAuth app
2727
Pass your instance URL and the client ID:
2828

2929
```bash
30-
SENTRY_URL=https://sentry.example.com SENTRY_CLIENT_ID=your-client-id sentry auth login
30+
SENTRY_HOST=https://sentry.example.com SENTRY_CLIENT_ID=your-client-id sentry auth login
3131
```
3232

3333
:::tip
3434
You can export both variables in your shell profile so every CLI invocation picks them up:
3535

3636
```bash
37-
export SENTRY_URL=https://sentry.example.com
37+
export SENTRY_HOST=https://sentry.example.com
3838
export SENTRY_CLIENT_ID=your-client-id
3939
```
4040
:::
@@ -48,7 +48,7 @@ If your instance is on an older version or you prefer not to create an OAuth app
4848
3. Pass it to the CLI:
4949

5050
```bash
51-
SENTRY_URL=https://sentry.example.com sentry auth login --token YOUR_TOKEN
51+
SENTRY_HOST=https://sentry.example.com sentry auth login --token YOUR_TOKEN
5252
```
5353

5454
## After Login
@@ -66,7 +66,8 @@ If you pass a self-hosted Sentry URL as a command argument (e.g., an issue or ev
6666

6767
| Variable | Description |
6868
|----------|-------------|
69-
| `SENTRY_URL` | Base URL of your Sentry instance |
69+
| `SENTRY_HOST` | Base URL of your Sentry instance (takes precedence over `SENTRY_URL`) |
70+
| `SENTRY_URL` | Alias for `SENTRY_HOST` |
7071
| `SENTRY_CLIENT_ID` | Client ID of your public OAuth application |
7172
| `SENTRY_ORG` | Default organization slug |
7273
| `SENTRY_PROJECT` | Default project slug (supports `org/project` format) |

0 commit comments

Comments
 (0)