Repository navigation
feat(seer): Report an unconnected SCM account or missing repo on lookups - #130316
joseph-sentry wants to merge 1 commit into
Conversation
407ea53 to
b34660e
Compare
b34660e to
f948379
Compare
we're adding more types of failures on repo lookups: - missing repo - unconnected account (there's no integration that matches the provided SCM org) this is so when the seer agent calls these tools with these misconfigurations we can offer accurate information to the user: - missing repo: are you sure your integration has access to this repo, or is the agent using the wrong slug - unconnected account: please make sure to install the app on this org, or again correct the agent's call arg
f948379 to
84b7d4b
Compare
| repo_segments = _name_segments(repo_full_name) | ||
| # A name with no owner can't be under any account, so there's nothing to say about it. | ||
| if len(repo_segments) < 2: | ||
| return None | ||
| under_connected_account = any( | ||
| _is_under_account(repo_segments, integration) for integration in integrations | ||
| ) | ||
| return RepositoryDefinitionFailure( | ||
| failure_reason=( | ||
| "repo_not_in_sentry" if under_connected_account else "scm_account_not_connected" |
There was a problem hiding this comment.
🤖 Classifier normalizes names more loosely than the lookup, so misses get wrong reasons
_name_segments strips whitespace around each / and casefolds. _find_repository does neither: it matches name exactly and compares a url suffix. So a lookup that misses only because of how the name is written still gets a confident failure reason, and the reason is wrong:
- GitLab display name without the spaces. Take
My Group/Sub/App, where Sentry storesMy Group / Sub / Appwith URL pathmy-group/sub/app._find_repositorymisses it, becauseQ(name=...)is exact and the URL doesn't end with that string. Here,["my group", "sub", "app"]starts with the integration nameMy Group / Sub, so the response isrepo_not_in_sentry, but the repo is in Sentry. This input is realistic. Seer's own_normalize_repo_full_name(seer/automation/explorer/tools/utils.py) says the LLM passes the stripped form, and that Seer only recovers from it when it already has anexternal_idhint. - Host-qualified name. Take
github.com/acme-labs/app.url__iendswithmatches the repo as a candidate, but the Python check rejects it. Then["github.com", "acme-labs", "app"]doesn't start withacme-labs, so the response isscm_account_not_connected. The agent would then ask the user to install the app on an account where it's already installed, for a repo Sentry already has. That is the misleading advice this PR is meant to prevent.
Fix: normalize the requested name once and use the result for both the lookup and the classification.
- Whitespace:
_find_repositorycould also tryQ(name__iexact=" / ".join(segments)), since GitLab'sname_with_namespacealways joins with/, and compare segment-normalized names in its Python check. - Hosts: strip a leading scheme, and a first segment equal to a connected integration's host, before the lookup.
- Smaller fix: if that's out of scope here, make
_repository_lookup_failurereturnNonewhen the first segment is a connected integration's host. In that case the reason isn't actually known, andNonematches the function's "when it's known" contract.
Written by an AI agent and included in this review by its author.
| failure_reason=( | ||
| "repo_not_in_sentry" if under_connected_account else "scm_account_not_connected" | ||
| ), | ||
| connected_accounts=[integration.name for integration in integrations], |
There was a problem hiding this comment.
🤖 connected_accounts gives GitLab display names, duplicates and no provider
The description says this list should help the agent correct its call argument. As written, it doesn't help much:
- GitLab names: for GitLab,
integration.nameis the group's display name (My Group / Sub). It isn't the path (my-group/sub), which is whatget_repository_definitionreturns asowneron success and what a retry should pass. An agent that buildsMy Group / Sub/appfrom this list still misses in_find_repository. - Duplicates: an account appears once per integration. The new test at
test_tools.py:3228compares asetbecause the list holdsacme-labsthree times. GitHub and Cursor Origin connected to the same account is a plausible real setup. - No provider: with the provider dropped, GitHub's
acme-labsand Cursor Origin'sacme-labslook the same.
Fix: report the prefix the agent should pass: the URL path for GitLab, and integration.name elsewhere. Don't derive it from domain_name for Cursor Origin, whose path is codebase/<slug>. Then deduplicate the list, add the provider if you want it, and make the test assert the exact list rather than a set.
Written by an AI agent and included in this review by its author.
| failure_reason=( | ||
| "repo_not_in_sentry" if under_connected_account else "scm_account_not_connected" | ||
| ), | ||
| connected_accounts=[integration.name for integration in integrations], |
There was a problem hiding this comment.
🤖 GitLab connected_accounts gives the agent a display name it can't reuse
connected_accounts lists integration.name. For GitLab that is the group's full_name, a display name like My Group / Sub (see gitlab/integration.py:799). The point of this field is to let the agent fix its slug and retry. The form it can retry with is the URL path (my-group/sub): RepositoryDefinitionResponse builds owner/name from get_repo_url_path, and _find_repository matches display names only by exact, case-sensitive name=. So My Group / Sub/app and my group / sub / app both still miss.
For GitHub, GHE and Cursor Origin, name already equals the path segment, so only GitLab is affected. You can't simply switch every provider to the path taken from domain_name, though. Cursor Origin's domain_name is https://cursor.com/codebase/<slug>, so the generic parse on L944 yields codebase/<slug> there. That also means the URL-path branch of _is_under_account never matches for Cursor Origin, which is classified through its name alone. Options:
- report the group path for GitLab (
metadata["domain_name"].partition("/")[2], the same parseGitlabIntegration.source_url_matchesuses), or - report both forms, e.g.
connected_accounts=[{"name": ..., "path": ...}], so the user-facing message can show the display name and the agent can retry with the path.
Seer doesn't read this field yet (no report_failure_reason callers on seer main), so the shape is still cheap to change.
Written by an AI agent and included in this review by its author.
| for repo_name in ("my-group/other/app", "my-group/app"): | ||
| result = self._lookup(self.organization, repo_name) | ||
| assert result.failure_reason == "scm_account_not_connected" |
There was a problem hiding this comment.
🤖 Not-connected GitLab cases repeat test_failure_for_each_provider
These two cases don't catch any mutant that test_failure_for_each_provider misses. Its GitLab subtest already checks my-group/sub-2/app → scm_account_not_connected. That fails on the same wrong implementations these would catch: comparing only the first segment, dropping the last account segment, or a plain startswith on the joined strings. The two repo_not_in_sentry cases above them do add coverage (the display-name form tests .strip(), and nested/app tests deeper repos), so keep those and drop this loop.
The same applies to the final other-org/app assertion in test_failure_checks_every_connected_account (L3230–3231). That test earns its place with the first assertion, which fails if only one integration is checked.
Written by an AI agent and included in this review by its author.
we're adding more types of failures on repo lookups:
this is so when the seer agent calls these tools with these misconfigurations we can offer accurate information to the user: