Skip to content

feat(cli): Name filter for project, team, repo, monitor and integration lists - #1575

Open
sentry-junior[bot] wants to merge 8 commits into
mainfrom
feat/cli-list-name-filter
Open

sentry-junior[bot] wants to merge 8 commits into
mainfrom
feat/cli-list-name-filter

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Brings sentry org list [filter]'s name filter (glob, or fuzzy text through filterByName) to the other org-scoped list commands. It's an optional second positional, following the sentry dashboard list [<org/project>] [<name-glob>] pattern. Refs #1554, from this review thread.

sentry project list my-org/ 'web-*'   # glob on slug or name
sentry team list my-org/ backnd       # fuzzy: finds "backend"
sentry project list 'web-*'           # lone glob: auto-detected org(s)
sentry repo list my-org/ '*-sdk'      # * also matches / (repo names are owner/name)

How it works

  • OrgListConfig.namesOf opts a buildOrgListCommand command in. That covers team, repo, monitor, and integration list. With a filter, the default handlers wrap the config's fetchers with filterByName. entityPlural becomes teams matching 'x', so progress, empty-result, and count messages say what was matched.
  • project list overrides every dispatch mode, so it gets the filter as ctx.filter and applies it in its own handlers, together with --platform.
  • Pagination: matching and fuzzy ranking need the whole set. With a filter, <org>/ fetches every page instead of one cursor page, and --cursor with a filter is rejected with a ValidationError. --limit applies to the matches, as in org list. Tips that point at the unfiltered paginated <org>/ listing are left out when a filter is set.
  • Argument grammar: splitListTargetAndFilter treats a lone glob (web-*) or <org>/<glob> as a filter, since no slug contains glob characters. A lone bare name (sentry project list web) is still a project search, so nothing existing changes meaning.
  • Glob * now also matches / (picomatch bash: true). Names aren't paths, and repository names are owner/name.

Not in this PR: dashboard list still uses its own glob-only title filter, applied while it pages through server results. Moving it to filterByName would add fuzzy matching but changes its paging behaviour, so it's left for a follow-up under #1554.

Testing: new tests cover splitListTargetAndFilter, the dispatcher's filter path (fetch everything, fuzzy ranking, --limit, project scope, --cursor rejection, ctx.filter for overrides), project list end to end (glob, fuzzy, <org>/<glob>, --platform, --limit, empty hint, --cursor), and team list wiring. tsc, oxlint, and oxfmt are clean. Generated command docs and skills are regenerated. The full local test/lib + test/commands run fails only in tests that also fail locally without this change (sandbox permissions and the require-shim loader); the check-* scripts pass when run directly.

via BYK.

--

View Junior Session [Sentry]

@vercel

vercel Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
sentry-local Skipped Skipped Oct 24, 2026 1:59am UTC

Request Review

@github-actions github-actions Bot added the risk: medium PR risk score: medium label Oct 21, 2026

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/cli/src/lib/org-list.ts Outdated
Comment thread packages/cli/src/lib/org-list.ts Outdated
BYK added a commit that referenced this pull request Oct 23, 2026
…1594)

Refs #1564. This is the first slice: the mechanism plus five commands.

A `[<org>/][<project>]` positional now owns both parsing and resolution.
The command picks a parser from `src/lib/target-positional.ts`, and that
parser comes with its resolver:

```ts
parse: orgOptionalTarget,                    // resolveOrgOptionalTarget
parse: projectBoundTarget,                   // resolveProjectBoundTarget
parse: orgProjectTarget(resolveViewTargets), // command-specific resolver
```

`func` gets a `LazyPositional` with three members: `target.parsed` (the
`ParsedOrgProject`), `target.resolved` (a promise of the resolver's
result), and `target.raw` (what the user typed):

```ts
async *func(this: SentryContext, flags: ListFlags, target: OrgOptionalTarget) {
  const resolved = await target.resolved;
```

`func` never calls `parseOrgProjectArg` or a resolver, never picks which
resolver to use, and needs no default for an omitted target. An omitted
optional target arrives as auto-detect. `project view` moves its target
switch into `resolveViewTargets` and reads `target.parsed.type`.

- **No command names to pass.** Resolvers read the command path from
`this.commandPrefix`, which Stricli's `forCommand` sets and the SDK
invoker now sets from its command path. The `COMMAND_NAME` constants
these commands used are gone. A context without `commandPrefix` makes
resolution throw rather than print a hint with a blank command name;
test mock contexts set it.
- **Destructive commands guard automatically.** `buildDeleteCommand`
calls `markDestructive` on its lazy positionals, and the target resolver
then runs `requireExplicitTarget`. `project delete` uses plain
`projectBoundTarget`. Once #1626's `destructive` flag lands, the call
can move into `buildCommand` so `issue merge` and `trial start` get it
too.
- **Lint rule.** `sentry-cli/require-target-resolution` fails a command
function that takes `OrgOptionalTarget`, `ProjectBoundTarget` or
`OrgProjectTarget<R>` but never reads `.resolved`, since skipping it
skips the resolver and the destructive guard.

**When things run.** Both steps are lazy (`LazyPositional` in
`src/lib/positional-parse.ts`, a plain class with memoized getters). The
parse runs on the first `parsed` read and the resolver on the first
`resolved` read; each runs once. Both happen inside `func`, so the order
matches main:

- The auth guard and rc URL check run first. A bad target with no login
still reports "Not authenticated" (exit 10).
- Parse errors keep their `CliError` semantics. `sentry project view
@latest` exits 21 with the `ValidationError`, not Stricli's `Failed to
parse …` (exit -4). A parse error also rejects `resolved`.
- `parseOrgProjectArg`'s URL side effect still runs after those checks.
For a pasted Sentry URL it calls `applySentryUrlContext`, which can
switch `SENTRY_URL`/`SENTRY_HOST`, save that host as the default, or
throw `HostScopeError`. Running it at Stricli scan time would have let
the auth guard and rc URL check run against the host taken from the URL.

Resolution stays a promise because resolvers need auth, config, DSN
detection, and API calls. Shared code that handles any positional
(`buildListCommand`'s plural-alias interception, `help` recovery,
`setArgsContext`) reads the raw input with `rawPositional(value)`, an
`instanceof LazyPositional` check. Stricli calls parsers with the
command context as `this`; the resolver gets that context plus
`commandName`.

**Callers that skip Stricli.** The typed SDK (`buildInvoker`) and tests
that call `command.loader()` pass raw strings. `buildCommand` runs
`applyLazyPositionals` on them, and on omitted optional positionals, so
every caller keeps passing what the user typed and `func` always gets
the same shape. When plural-alias interception drops the target,
`buildListCommand` runs it again so the dropped target becomes
auto-detect. `sdk.project.view({ orgProject: "@latest" })` is rejected
instead of silently auto-detecting. The SDK invoker also sets
`directInvoke`, which turns off `help` positional recovery: a failing
SDK call whose target is `help` throws its error, as on main, instead of
returning help text.

**Shell completion is unchanged.** Completion kinds come from a tag on
the parser (`withCompletionTarget`), shared with `parseOrgTarget` and
`parseOrgProjectTarget`. The generated targets file matches main.

Migrated: `trace list`, `replay list`, `feedback list`, `project view`,
`project delete`.

Not in this PR:
- `LIST_TARGET_POSITIONAL` / `buildOrgListCommand`. #1575
(`splitListTargetAndFilter`) reads the raw target and splits it before
parsing (for example `acme/web-*`). These should move after it lands,
using `target.raw`.
- Issue and dashboard positionals. #1572 adds `parseIssueTarget` /
`parseDashboardTarget` in the same files. When one of the two PRs
rebases, its parsers should switch from the `PARSER_TARGETS` map to
`withCompletionTarget`.
- `[<org>/<project>/]<id>` positionals (`trace view`, `span list`,
`event view`, `log view`, …). Stricli parses each argument on its own,
but what each one means depends on how many were given. Those keep
parsing in `func`.
- Release `org/version` positionals. They already share
`resolveReleaseTarget`.

<!-- junior-request-attribution:start -->
via **BYK**.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:slack%3AC010884QHLG%3A1792667769.623489 -->

--

[View Junior
Session](https://junior-prod.sentry.dev/conversations/slack%3AC010884QHLG%3A1792667769.623489)
[[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AC010884QHLG%3A1792667769.623489/?project=4510944073809921)

<!-- junior-session-footer:end -->

---------

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Co-authored-by: Burak Yigit Kaya <[email protected]>
sentry-junior Bot and others added 2 commits October 23, 2026 23:18
…on lists

Bring `sentry org list [filter]`'s name filter to the other org-scoped
list commands, as an optional second positional:

  sentry project list my-org/ 'web-*'   # glob on slug or name
  sentry team list my-org/ backnd       # fuzzy text
  sentry project list 'web-*'           # lone glob: auto-detected org(s)

- `OrgListConfig.namesOf` opts a `buildOrgListCommand` command in (team,
  repo, monitor, integration); its default handlers filter each fetched
  set with `filterByName`. `project list` applies it in its own handlers,
  together with `--platform`.
- Matching and fuzzy ranking need the whole set, so `<org>/` with a filter
  fetches every page instead of one cursor page, and `--cursor` with a
  filter is rejected. `--limit` applies to the matches.
- `splitListTargetAndFilter` treats a lone glob (or `<org>/<glob>`) as a
  filter, since no slug contains glob characters. A lone bare name is
  still a project search.
- Glob `*` now also matches `/`, so `*-sdk` matches repository names like
  `getsentry/sentry-python-sdk`.

Refs #1554

Co-Authored-By: byk <[email protected]>

Co-Authored-By: BYK <[email protected]>
A name filter on `<org>/` went through `listForOrg`, which for teams,
repositories and integrations returns only the first API page, so later
matches were dropped. It also used `fetchOrgSafe`, which turns a missing
org, 403 or network failure into an empty "no matches" result.

With a filter, the default handlers now walk every page of
`listPaginated`, and org-all mode lets API errors propagate as the
unfiltered `<org>/` listing does.

Co-Authored-By: BYK <[email protected]>
@sentry-junior
sentry-junior Bot force-pushed the feat/cli-list-name-filter branch from 1e97a53 to 4b33e4b Compare October 23, 2026 23:19

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/cli/src/lib/org-list.ts
Auto-detect tips (`<cmd> <org>/`) and project list's JSON pagination
hint dropped the name filter, so following them listed a different set.
Comment thread packages/cli/src/commands/integration/list.ts
Comment thread packages/cli/src/commands/project/list.ts Outdated
Comment thread packages/cli/src/commands/project/list.ts Outdated
Comment thread packages/cli/src/lib/org-list.ts
Review follow-ups for the list name filter:

- Narrow name filters with the API's `?query=` (case-insensitive
  substring on name/slug) for projects, teams, repos and monitors
  instead of reading every page. A glob sends its longest literal
  part (`web-*` -> `web-`). Plain text the server finds nothing for
  falls back to fuzzy matching over every page. Integrations have no
  name search and still read every page.
- `<org>/<name>` tries a direct match first, then fuzzy: project list
  falls back to matching project names when no such project exists;
  team list does the same when the project 404s; repo, monitor and
  integration lists match entity names and, when nothing matches,
  keep listing the whole org with the org-scoped note.
- Document `<org>/<glob>` as the primary form and move the filter
  examples out of free-text `fullDescription` into `docs.examples`.

Co-Authored-By: BYK <[email protected]>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f9b38fe. Configure here.

Comment thread packages/cli/src/commands/project/list.ts
Comment thread packages/cli/src/lib/org-list.ts
- project list: only a 404 from the project lookup falls back to
  name matching; 403s, 5xx and network errors keep the plain
  not-found hint instead of listing other projects.
- team list: when the project is missing and no team matches the
  name either, say both in the hint, since empty results don't
  render the header note.
Comment thread packages/cli/src/commands/integration/list.ts Outdated
Comment thread packages/cli/src/commands/project/list.ts
Comment thread packages/cli/src/commands/project/list.ts
Comment thread packages/cli/src/commands/project/list.ts
- An `<org>/<glob>` with no matches stays empty instead of falling
  back to the whole org: a glob is never a project slug.
- `project list <org>/<name>`: a failing fuzzy fallback (e.g. a bad
  org) keeps the not-found hint rather than throwing, and fuzzy
  matches removed by --platform say so in the hint.
- Integration name filters also match the provider (`GitHub`), since
  `name` is the install label (e.g. a GitHub org).
- Plural aliases (`sentry projects list my-org/`) shift positionals
  when dropping the subcommand token, so the target is not read as
  the new name filter.
Comment thread packages/cli/src/commands/repo/list.ts Outdated
Comment thread packages/cli/src/lib/list-filter.ts
- Repository name filters also match the name without its owner
  (`sentry-python` for `getsentry/sentry-python`), so a typo in the
  short name fuzzy-matches instead of falling back to the whole org.
- A lone `/web-*` is `web-*` (a filter on the detected org) rather than
  target `/`, which the target grammar rejects.
Comment thread packages/cli/src/lib/org-list.ts
The name filter ranks matches within each org or project fetch, but
auto-detect and project-search concatenated those lists and applied
--limit, so a weaker match from an earlier org could hide a better
one from a later org. The filtered config now re-ranks merged items
(by the names of the entity behind each) before the limit.
BYK added a commit that referenced this pull request Oct 24, 2026
Refs #1564. This continues #1594 with the one remaining
`[<org>/][<project>]` target that nothing blocks.

`explore` declared its target with `String` and called
`resolveOrgOptionalFromArg` inside `func`. It now uses
`orgOptionalTarget` and awaits `target.resolved`, the same way `feedback
list` and `replay list` do. Resolution is unchanged: it still goes
through `resolveOrgOptionalTarget` with the same command name.

User-facing change: the parser carries the org/project completion tag,
so `sentry explore <TAB>` now completes `org/`, `org/project` and
project aliases. Before this, `explore` had no target completion.

Other users of the `*FromArg` helpers are left alone on purpose:
- `agent-conversation list` uses `LIST_TARGET_POSITIONAL`, which waits
on #1575.
- `log list`, `replay view` and `replay download` take variadic
positionals. What each argument means depends on how many were given, so
they keep parsing in `func`.

<!-- junior-request-attribution:start -->
via **BYK**.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:local%3Aweb%3Aa3773d7739de1b873375a6f1 -->

--

[View Junior Session in
Sentry](https://sentry.sentry.io/explore/conversations/local%3Aweb%3Aa3773d7739de1b873375a6f1/?project=4510944073809921)

<!-- junior-session-footer:end -->

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Co-authored-by: BYK <[email protected]>

This branch was previously deployed

1 inactive deployment
Preview — ce69f198 Deployed Oct 24, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: medium PR risk score: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant