Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Toolkit contains the Sentry CLI, the Sentry MCP server, and shared code. Product
- **MCP tool count**: `EXPOSE_ALL_TOOLS` lists every tool and relies on harness tool search. The gateway surface it falls back to (`TOP_LEVEL_TOOL_NAMES`) targets ≤20, hard limit 25.
- **Quality gate**: `pnpm run tsc && pnpm run lint && pnpm run test` must pass before committing.
- **Dependency direction**: `sentry` (cli) → `@sentry/mcp-core` → `@sentry/toolkit-core`, and `@sentry/mcp-cloudflare` → `@sentry/mcp-core`; never the reverse. mcp-core and toolkit-core never import the CLI. Inject CLI-only capabilities (SQLite cache, local-file DSN detection, file uploads) through interfaces defined in mcp-core or toolkit-core. `pnpm run lint` enforces this; see docs/architecture/overview.md#package-dependency-direction.
- **Lint rules**: Write new lint rules for Oxlint: a built-in rule in `.oxlintrc.json`, or a custom rule in `packages/cli/lint-rules/cli-oxlint-plugin.js`. Do not add ast-grep rules. See docs/contributing/coding-guidelines.md#formatting-and-linting.
- **Package CI**: Put checks for one workspace package in its `package.json` scripts and `sentryCi`, which `test.yml` runs per project. Do not add a workflow for them. See docs/operations/github-actions.md#package-ci-checks.

## Repository Structure
Expand Down
4 changes: 4 additions & 0 deletions docs/contributing/coding-guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ product-specific implementation patterns.
- Root, MCP, and CLI files use Oxlint and Oxfmt. The CLI-specific rules live in
`packages/cli/lint-rules/cli-oxlint-plugin.js`, with inline Oxlint suppressions
for intentional exceptions.
- Write new lint rules for Oxlint: turn on a built-in rule in `.oxlintrc.json`
(for example `no-restricted-imports`), or add a custom rule to the Oxlint JS
plugin above and register it in `.oxlintrc.json`. Do not add new ast-grep
rules; the few left in `ast-grep-rules/` predate this.
- Oxlint runs type-aware (`options.typeAware` in `.oxlintrc.json`, backed by
`oxlint-tsgolint`). `typescript/return-await` (`always`) is on repo-wide:
write `return await` in async functions so callers stay in async stack
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,18 +190,18 @@ Edit an issue alert rule
|-------|------|-------------|
| `id` | string | Alert rule ID |
| `name` | string | Alert rule name |
| `organizationId` | string | |
| `createdBy` | string \| null | |
| `dateCreated` | string | |
| `dateUpdated` | string | |
| `triggers` | object \| null | |
| `actionFilters` | array \| null | |
| `environment` | string \| null | |
| `config` | object | |
| `detectorIds` | array \| null | |
| `enabled` | boolean | |
| `lastTriggered` | string \| null | |
| `owner` | string \| null | |
| `organizationId` | string | ID of the organization the alert belongs to |
| `createdBy` | string \| null | ID of the user who created the alert |
| `dateCreated` | string | When the alert was created (ISO 8601) |
| `dateUpdated` | string | When the alert was last updated (ISO 8601) |
| `triggers` | object \| null | Conditions on which the alert triggers |
| `actionFilters` | array \| null | Filters to run before actions fire, each with the actions to fire |
| `environment` | string \| null | Name of the environment the alert is limited to. Null for all environments |
| `config` | object | Configuration of the alert, such as frequency in minutes |
| `detectorIds` | array \| null | IDs of the monitors connected to the alert |
| `enabled` | boolean | Whether the alert is enabled |
| `lastTriggered` | string \| null | When the alert last fired (ISO 8601) |
| `owner` | string \| null | Team or user that owns the alert, such as user:123 or team:456 |
| `org` | string | Organization slug |
| `project` | string | Project slug |
| `status` | string | Rule status (active, disabled) |
Expand Down Expand Up @@ -389,21 +389,21 @@ Edit a metric alert rule

| Field | Type | Description |
|-------|------|-------------|
| `owner` | object \| null | |
| `createdBy` | string \| null | |
| `latestGroup` | object \| null | |
| `description` | string \| null | |
| `owner` | object \| null | Team or user that owns the monitor |
| `createdBy` | string \| null | ID of the user who created the monitor |
| `latestGroup` | object \| null | Latest issue the monitor created |
| `description` | string \| null | Description of the monitor |
| `id` | string | Alert rule ID |
| `projectId` | string \| null | |
| `projectId` | string \| null | ID of the project the monitor belongs to |
| `name` | string | Alert rule name |
| `type` | string | |
| `workflowIds` | array \| null | |
| `dateCreated` | string | |
| `dateUpdated` | string | |
| `dataSources` | array \| null | |
| `conditionGroup` | object \| null | |
| `config` | object | |
| `enabled` | boolean | |
| `type` | string | Type of the monitor, such as metric_issue or error |
| `workflowIds` | array \| null | IDs of the alerts connected to the monitor |
| `dateCreated` | string | When the monitor was created (ISO 8601) |
| `dateUpdated` | string | When the monitor was last updated (ISO 8601) |
| `dataSources` | array \| null | Data sources the monitor evaluates, such as a query subscription |
| `conditionGroup` | object \| null | Conditions that decide the monitor's status |
| `config` | object | Configuration of the monitor, which depends on its type |
| `enabled` | boolean | Whether the monitor is enabled |
| `org` | string | Organization slug |
| `status` | string | Rule status (active, disabled) |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,44 +30,44 @@ View details of one or more events

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | |
| `groupID` | string \| null | |
| `id` | string | ID of the event |
| `groupID` | string \| null | ID of the issue the event belongs to |
| `eventID` | string | UUID-format event ID |
| `projectID` | string | |
| `message` | string \| null | |
| `title` | string | |
| `location` | string \| null | |
| `user` | object \| null | |
| `tags` | array | |
| `platform` | string \| null | |
| `dateReceived` | string \| null | |
| `contexts` | object \| null | |
| `size` | number \| null | |
| `entries` | array | |
| `dist` | string \| null | |
| `sdk` | object \| null | |
| `context` | object \| null | |
| `packages` | object | |
| `type` | string | |
| `metadata` | object | |
| `errors` | array | |
| `projectID` | string | ID of the project the event belongs to |
| `message` | string \| null | Event message |
| `title` | string | Event title |
| `location` | string \| null | Source location of the event, such as a file and line |
| `user` | object \| null | User context of the event |
| `tags` | array | Tags attached to the event |
| `platform` | string \| null | Platform of the event, such as python or javascript |
| `dateReceived` | string \| null | When Sentry received the event (ISO 8601) |
| `contexts` | object \| null | Contexts of the event, such as device, os, browser and runtime |
| `size` | number \| null | Size of the event payload in bytes |
| `entries` | array | Event interfaces, such as exception, breadcrumbs and request, each with a type and data |
| `dist` | string \| null | Distribution of the release the event was sent with |
| `sdk` | object \| null | SDK that sent the event |
| `context` | object \| null | Additional data sent with the event in its extra field |
| `packages` | object | Modules loaded when the event occurred, keyed by module name with their versions |
| `type` | string | Event type, such as error, default or transaction |
| `metadata` | object | Event metadata |
| `errors` | array | Errors Sentry found while processing the event |
| `occurrence` | object \| null | |
| `_meta` | object | |
| `crashFile` | string \| null | |
| `culprit` | string \| null | |
| `dateCreated` | string | |
| `fingerprints` | array | |
| `groupingConfig` | object | |
| `startTimestamp` | number | |
| `endTimestamp` | number | |
| `measurements` | object \| null | |
| `breakdowns` | object \| null | |
| `release` | object \| null | |
| `userReport` | object \| null | |
| `sdkUpdates` | array | |
| `resolvedWith` | array | |
| `nextEventID` | string \| null | |
| `previousEventID` | string \| null | |
| `_meta` | object | Metadata about changes Sentry made to the event's fields, such as data scrubbing or trimming |
| `crashFile` | string \| null | Crash report attachment of the event, if any |
| `culprit` | string \| null | Function or module that caused the event |
| `dateCreated` | string | When the event occurred (ISO 8601) |
| `fingerprints` | array | Grouping hashes calculated for the event |
| `groupingConfig` | object | Grouping configuration used to group the event |
| `startTimestamp` | number | When the transaction started, as a Unix timestamp in seconds |
| `endTimestamp` | number | When the transaction ended, as a Unix timestamp in seconds |
| `measurements` | object \| null | Measurements recorded for the transaction, such as web vitals, keyed by name |
| `breakdowns` | object \| null | Span operation breakdowns for the transaction, keyed by breakdown name |
| `release` | object \| null | Release the event was sent with. Null when the event has no release |
| `userReport` | object \| null | User feedback submitted for the event. Null when there's none |
| `sdkUpdates` | array | Suggested updates for the SDK that sent the event |
| `resolvedWith` | array | How the event's stack frames were resolved during processing, such as which source map lookup method was used |
| `nextEventID` | string \| null | ID of the next event in the issue. Null for the latest event or when adjacentEvents is collapsed |
| `previousEventID` | string \| null | ID of the previous event in the issue. Null for the first event or when adjacentEvents is collapsed |
| `formatted` | object | Event rendered as Markdown, XML, or JSON; only returned for API requests with llmFormat |
| `trace` | object \| null | Trace context, or null when unavailable |
| `attachments` | array | Event attachments; each includes metadata and an absolute authenticated download URL |
Expand Down
36 changes: 10 additions & 26 deletions packages/cli/src/commands/snapshots/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ import {
import { CommandOutput } from "../../lib/formatters/output.js";
import { logger } from "../../lib/logger.js";
import {
findExistingObjects,
type ObjectstoreConfig,
objectExists,
putObject,
putObjects,
} from "../../lib/objectstore.js";
import { resolveOrgAndProject } from "../../lib/resolve-target.js";
import {
Expand All @@ -59,9 +59,6 @@ const log = logger.withTag("snapshots.upload");

const USAGE_HINT = "sentry snapshots upload <path> --app-id <id>";

/** Concurrency for objectstore HEAD/PUT requests. */
const UPLOAD_CONCURRENCY = 8;

/** Flags accepted by `snapshots upload`. */
type UploadFlags = {
"app-id": string;
Expand Down Expand Up @@ -204,17 +201,6 @@ async function resolveSelective(
return { selective, allImageNames };
}

/** Run `fn` over `items` with bounded concurrency. */
async function runPooled<T>(
items: T[],
limit: number,
fn: (item: T) => Promise<void>,
): Promise<void> {
for (let i = 0; i < items.length; i += limit) {
await Promise.all(items.slice(i, i + limit).map(fn));
}
}

/** A per-image manifest metadata entry (width/height override sidecar). */
function imageMetadata(image: CollectedImage): Record<string, unknown> {
return {
Expand Down Expand Up @@ -276,17 +262,15 @@ async function uploadImages(
log.warn(`Duplicate paths encountered, skipping: ${duplicates.join(", ")}`);
}

// HEAD to find objects already present, then PUT only the missing ones.
const existing = new Set<string>();
await runPooled(prepared, UPLOAD_CONCURRENCY, async (item) => {
if (await objectExists(config, item.key)) {
existing.add(item.key);
}
});
// Batched HEADs find objects already present, then only the missing ones are
// uploaded. Batching keeps the request count low enough to stay under the
// Sentry proxy's per-request rate limit.
const existing = await findExistingObjects(
config,
prepared.map((item) => item.key),
);
const missing = prepared.filter((item) => !existing.has(item.key));
await runPooled(missing, UPLOAD_CONCURRENCY, async (item) => {
await putObject(config, item.key, await readFile(item.path));
});
await putObjects(config, missing);

return {
entries,
Expand Down
Loading
Loading